Capture · Roots: roots drop w_full; hosts give the width
145 component roots drop w_full, the early returns included. Three field rows keep it, Input, InputGroup and TagInput: stretched without a width, taffy 0.9 laid the field's flex_1 share at no width in the template picker at 280px, and the placeholder drew nothing. Container keeps it, since mx_auto needs a width to fill. Hosts that relied on a root's width now give it: MessageList boxes each row at the list's width, the splash gives its bar the column's, and test stages that mount a component as the window's root sit in a size_full box. Tests hold a narrow row across the list and the splash bar across its column; each goes red without its fix. The gallery gains --narrow <px>, which lays each page in a padded card at that width and shoots it without its script. The page header wraps: the title takes the room and the theme switcher drops below, where a long summary used to push it past the edge. A file panel step waits for the panel to become key instead of a fixed pause, which lost a race in a capture.
This commit is contained in:
1 parent
3be809a660
commit
81e73b79c1
126 files changed
+275
-276
No files matched your search
@@ -15,11 +15,17 @@ use crate::{
|
||||
const SETTLE: Duration = Duration::from_millis(700);
|
||||
const SCROLL_SETTLE: Duration = Duration::from_millis(250);
|
||||
|
||||
/// Shoots one page, or all, in both modes, then quits. Exits 1 on failure.
|
||||
pub fn run(window: WindowHandle<Gallery>, dir: PathBuf, only: Option<usize>, cx: &mut App) {
|
||||
/// Shoots one page, or all, in both modes, then quits; exits 1 on failure.
|
||||
pub fn run(
|
||||
window: WindowHandle<Gallery>,
|
||||
dir: PathBuf,
|
||||
only: Option<usize>,
|
||||
scripted: bool,
|
||||
cx: &mut App,
|
||||
) {
|
||||
Theme::update(cx, |theme| theme.reduced_motion = true);
|
||||
cx.spawn(async move |cx| {
|
||||
let outcome = shoot_all(window, &dir, only, cx).await;
|
||||
let outcome = shoot_all(window, &dir, only, scripted, cx).await;
|
||||
if let Err(error) = outcome.and_then(|()| cx.update(|cx| cx.quit())) {
|
||||
log::error!("capture failed: {error:#}");
|
||||
std::process::exit(1);
|
||||
@@ -32,6 +38,7 @@ async fn shoot_all(
|
||||
window: WindowHandle<Gallery>,
|
||||
dir: &Path,
|
||||
only: Option<usize>,
|
||||
scripted: bool,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<()> {
|
||||
std::fs::create_dir_all(dir).with_context(|| format!("create {}", dir.display()))?;
|
||||
@@ -70,7 +77,7 @@ async fn shoot_all(
|
||||
window.update(cx, |gallery, _, cx| gallery.scroll_to(offset, cx))?;
|
||||
cx.background_executor().timer(SCROLL_SETTLE).await;
|
||||
}
|
||||
if !page.script.is_empty() {
|
||||
if scripted && !page.script.is_empty() {
|
||||
window.update(cx, |gallery, _, cx| gallery.select(ix, cx))?;
|
||||
cx.background_executor().timer(SETTLE).await;
|
||||
let shot = |step: &str| dir.join(format!("{}-{step}-{name}.png", page.slug));
|
||||
|
||||
@@ -68,12 +68,14 @@ fn choose(window: WindowHandle<shell::Gallery>, choice: shell::Choice, cx: &mut
|
||||
struct Args {
|
||||
page: Option<usize>,
|
||||
capture: Option<PathBuf>,
|
||||
narrow: Option<f32>,
|
||||
}
|
||||
|
||||
fn parse_args() -> Result<Args> {
|
||||
let mut args = Args {
|
||||
page: None,
|
||||
capture: None,
|
||||
narrow: None,
|
||||
};
|
||||
let mut iter = std::env::args().skip(1);
|
||||
while let Some(flag) = iter.next() {
|
||||
@@ -85,6 +87,13 @@ fn parse_args() -> Result<Args> {
|
||||
args.page = Some(pages::find(&value).with_context(|| format!("no page {value}"))?)
|
||||
}
|
||||
"--capture" => args.capture = Some(PathBuf::from(value)),
|
||||
"--narrow" => {
|
||||
args.narrow = Some(
|
||||
value
|
||||
.parse()
|
||||
.with_context(|| format!("bad width {value}"))?,
|
||||
)
|
||||
}
|
||||
other => bail!("unknown flag {other}"),
|
||||
}
|
||||
}
|
||||
@@ -118,13 +127,15 @@ fn main() -> Result<()> {
|
||||
};
|
||||
let window = cx
|
||||
.open_window(options, |window, cx| {
|
||||
cx.new(|cx| shell::Gallery::new(args.page.unwrap_or(0), window, cx))
|
||||
cx.new(|cx| {
|
||||
shell::Gallery::new(args.page.unwrap_or(0), args.narrow, window, cx)
|
||||
})
|
||||
})
|
||||
.expect("gallery window failed to open");
|
||||
menu_actions(window, cx);
|
||||
cx.activate(true);
|
||||
if let Some(dir) = args.capture {
|
||||
capture::run(window, dir, args.page, cx);
|
||||
capture::run(window, dir, args.page, args.narrow.is_none(), cx);
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
|
||||
@@ -114,7 +114,6 @@ const SCRIPT: &[Step] = &[
|
||||
Step::Shot("hotkey"),
|
||||
Step::DownAt("path", 380.0, 16.0),
|
||||
Step::UpAt("path", 380.0, 16.0),
|
||||
Step::Wait(1200),
|
||||
Step::CancelPanel,
|
||||
Step::Wait(600),
|
||||
Step::DownAt("checks", 40.0, 66.0),
|
||||
@@ -242,7 +241,6 @@ const SCRIPT: &[Step] = &[
|
||||
Step::Shot("gradient"),
|
||||
Step::DownAt("file-one", 100.0, 14.0),
|
||||
Step::UpAt("file-one", 100.0, 14.0),
|
||||
Step::Wait(1200),
|
||||
Step::CancelPanel,
|
||||
Step::Wait(600),
|
||||
Step::Shot("files"),
|
||||
|
||||
@@ -53,6 +53,8 @@ pub enum Step {
|
||||
}
|
||||
|
||||
const FRAME: Duration = Duration::from_millis(120);
|
||||
/// How long an out-of-process file panel may take to open.
|
||||
const PANEL: Duration = Duration::from_secs(10);
|
||||
const DRAG_STEPS: u32 = 10;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
@@ -218,7 +220,15 @@ pub async fn play(
|
||||
let number = window.update(cx, |_, window, _| number(window))??;
|
||||
post_key(number, true, text, code)?;
|
||||
}
|
||||
Step::CancelPanel => cancel_panel()?,
|
||||
Step::CancelPanel => {
|
||||
let mut waited = Duration::ZERO;
|
||||
while !cancel_panel()? {
|
||||
anyhow::ensure!(waited < PANEL, "no file panel became key in {PANEL:?}");
|
||||
cx.background_executor().timer(FRAME).await;
|
||||
waited += FRAME;
|
||||
}
|
||||
log::info!("script: file panel cancelled after {waited:?}");
|
||||
}
|
||||
Step::ExpectClosed(key) => {
|
||||
let handle = opened(key, cx)?;
|
||||
let open = cx.update(|cx| cx.windows().contains(&handle))?;
|
||||
@@ -363,7 +373,8 @@ fn post_key(_: u32, _: bool, _: &str, _: u16) -> Result<()> {
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn cancel_panel() -> Result<()> {
|
||||
/// Cancels a key file panel; false while none is key.
|
||||
fn cancel_panel() -> Result<bool> {
|
||||
use cocoa::{
|
||||
appkit::NSApp,
|
||||
base::{BOOL, NO, id, nil},
|
||||
@@ -371,16 +382,20 @@ fn cancel_panel() -> Result<()> {
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
unsafe {
|
||||
let key: id = msg_send![NSApp(), keyWindow];
|
||||
anyhow::ensure!(key != nil, "no key window to cancel");
|
||||
if key == nil {
|
||||
return Ok(false);
|
||||
}
|
||||
let panel: BOOL = msg_send![key, isKindOfClass: class!(NSSavePanel)];
|
||||
anyhow::ensure!(panel != NO, "the key window is not a file panel");
|
||||
if panel == NO {
|
||||
return Ok(false);
|
||||
}
|
||||
let _: () = msg_send![key, cancel: nil];
|
||||
}
|
||||
Ok(())
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn cancel_panel() -> Result<()> {
|
||||
fn cancel_panel() -> Result<bool> {
|
||||
anyhow::bail!("file panels need macOS")
|
||||
}
|
||||
|
||||
|
||||
+63
-30
@@ -5,9 +5,9 @@ use ely_gpui_component::{
|
||||
theme::{ActiveTheme, ControlSize, Mode, Radius, TextSize, Theme},
|
||||
};
|
||||
use gpui::{
|
||||
Bounds, Context, FocusHandle, FontWeight, InteractiveElement, IntoElement, ParentElement,
|
||||
Pixels, Render, ScrollHandle, SharedString, StatefulInteractiveElement, Styled, Subscription,
|
||||
Window, div, point, prelude::*, px,
|
||||
AnyElement, App, Bounds, Context, FocusHandle, FontWeight, InteractiveElement, IntoElement,
|
||||
ParentElement, Pixels, Render, ScrollHandle, SharedString, StatefulInteractiveElement, Styled,
|
||||
Subscription, Window, div, point, prelude::*, px,
|
||||
};
|
||||
|
||||
use crate::pages;
|
||||
@@ -26,11 +26,18 @@ pub struct Gallery {
|
||||
choice: Choice,
|
||||
focus: FocusHandle,
|
||||
scroll: ScrollHandle,
|
||||
/// Narrow check width, if any.
|
||||
narrow: Option<Pixels>,
|
||||
_appearance: Subscription,
|
||||
}
|
||||
|
||||
impl Gallery {
|
||||
pub fn new(page: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
pub fn new(
|
||||
page: usize,
|
||||
narrow: Option<f32>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let appearance = cx.observe_window_appearance(window, |gallery, window, cx| {
|
||||
if gallery.choice == Choice::System {
|
||||
Theme::set_mode(window.appearance().into(), cx);
|
||||
@@ -44,6 +51,7 @@ impl Gallery {
|
||||
choice: Choice::Light,
|
||||
focus,
|
||||
scroll: ScrollHandle::new(),
|
||||
narrow: narrow.map(px),
|
||||
_appearance: appearance,
|
||||
}
|
||||
}
|
||||
@@ -174,6 +182,7 @@ impl Gallery {
|
||||
];
|
||||
let colors = &cx.theme().colors;
|
||||
div()
|
||||
.flex_none()
|
||||
.flex()
|
||||
.gap_0p5()
|
||||
.p_0p5()
|
||||
@@ -198,6 +207,24 @@ impl Gallery {
|
||||
}
|
||||
}
|
||||
|
||||
impl Gallery {
|
||||
/// Header and body; a padded card when narrow.
|
||||
fn card(&self, header: impl IntoElement, body: AnyElement, cx: &App) -> AnyElement {
|
||||
let content = div().child(header).child(body);
|
||||
match self.narrow {
|
||||
// Width plus padding and hairlines.
|
||||
Some(width) => div()
|
||||
.w(width + px(34.0))
|
||||
.p_4()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors.border)
|
||||
.child(content)
|
||||
.into_any_element(),
|
||||
None => content.into_any_element(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Gallery {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let page = &pages::ALL[self.page];
|
||||
@@ -229,32 +256,38 @@ impl Render for Gallery {
|
||||
.pt(px(56.0))
|
||||
.pb(px(96.0))
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_start()
|
||||
.justify_between()
|
||||
.gap_6()
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.text_size(theme.text_size(TextSize::Xxl))
|
||||
.font_weight(FontWeight::SEMIBOLD)
|
||||
.child(page.title),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_size(theme.text_size(TextSize::Md))
|
||||
.text_color(colors.fg_muted)
|
||||
.child(page.summary),
|
||||
),
|
||||
)
|
||||
.child(switcher),
|
||||
)
|
||||
.child(body),
|
||||
self.card(
|
||||
div()
|
||||
.flex()
|
||||
.flex_wrap()
|
||||
.items_start()
|
||||
.justify_between()
|
||||
.gap_6()
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.min_w(px(200.0))
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.text_size(theme.text_size(TextSize::Xxl))
|
||||
.font_weight(FontWeight::SEMIBOLD)
|
||||
.child(page.title),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_size(theme.text_size(TextSize::Md))
|
||||
.text_color(colors.fg_muted)
|
||||
.child(page.summary),
|
||||
),
|
||||
)
|
||||
.child(switcher),
|
||||
body,
|
||||
cx,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user