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.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-09-28 13:07:22 -04:00
1 parent 486f618ecc
commit 5779daef82
126 files changed
+275 -276

No files matched your search

+3 -2
View File
@@ -22,6 +22,7 @@ Ely GPUI Component. A component library for GPUI, in light and dark.
- `cargo run --example gallery` opens the gallery. `-- --page <slug>` starts on a page. - `cargo run --example gallery` opens the gallery. `-- --page <slug>` starts on a page.
- `cargo run --example gallery -- --capture <dir>` writes PNGs of every page, light and dark, top to bottom, then each page's scripted states, including windows the demos open. macOS only. - `cargo run --example gallery -- --capture <dir>` writes PNGs of every page, light and dark, top to bottom, then each page's scripted states, including windows the demos open. macOS only.
- `cargo run --example gallery -- --narrow 280 --capture <dir>` lays each page, header and body, at that width in a padded card and shoots it without its script, whose aims are measured at full width. Demos that set their own width run past the card.
- `cargo test --lib --features test-support`, `cargo clippy --all-targets --features test-support -- -D warnings`, `cargo fmt --check`. `scripts/check.sh` runs them with the house rules. - `cargo test --lib --features test-support`, `cargo clippy --all-targets --features test-support -- -D warnings`, `cargo fmt --check`. `scripts/check.sh` runs them with the house rules.
- `rm -rf target/debug/incremental` and `cargo clean -p ely-gpui-component` after each task item keep the disk lean. Cargo keeps every old build of the crate and its tests under a new hash; by T25 they had filled the disk. - `rm -rf target/debug/incremental` and `cargo clean -p ely-gpui-component` after each task item keep the disk lean. Cargo keeps every old build of the crate and its tests under a new hash; by T25 they had filled the disk.
- `scripts/icons.sh <lucide-name>...` adds icons. Then add a line to `IconName` in `src/primitives/icon.rs`. - `scripts/icons.sh <lucide-name>...` adds icons. Then add a line to `IconName` in `src/primitives/icon.rs`.
@@ -111,7 +112,7 @@ Ely GPUI Component. A component library for GPUI, in light and dark.
- Trackpad momentum arrives as `TouchPhase::Moved` after `Ended`, so a gesture acts only between `Started` and `Ended` (`lists::SwipeableListItem`). - Trackpad momentum arrives as `TouchPhase::Moved` after `Ended`, so a gesture acts only between `Started` and `Ended` (`lists::SwipeableListItem`).
- Quartz scroll events carry no window. The harness hands them to the window itself, placed in window points (`Step::Swipe`). - Quartz scroll events carry no window. The harness hands them to the window itself, placed in window points (`Step::Swipe`).
- A chevron that opens and closes is `primitives::Disclosure`. - A chevron that opens and closes is `primitives::Disclosure`.
- `uniform_list` does not stretch its rows; each row sets `w_full`, or `min_w_full` when the same row also sits in a flex column, where it stretches (`tables::DataTable`). - `uniform_list` does not stretch its rows; each row sets `w_full`, or `min_w_full` when the same row also sits in a flex column, where it stretches (`tables::DataTable`). gpui's `list` lays rows at their content's width too; `chat::MessageList` boxes each row at the list's width.
- Chapters import only earlier chapters, so a select of tree nodes lives in `lists` (`TreeSelect`) and reaches `forms::listing`. - Chapters import only earlier chapters, so a select of tree nodes lives in `lists` (`TreeSelect`) and reaches `forms::listing`.
- Script clicks aim at a probe's corner, so a scripted target must fit in the window; a tall probe puts its top off screen. The log names each probe's bounds as the script aims, and a probe around content with no width of its own measures smaller than what it draws, so a scripted demo sets its width. - Script clicks aim at a probe's corner, so a scripted target must fit in the window; a tall probe puts its top off screen. The log names each probe's bounds as the script aims, and a probe around content with no width of its own measures smaller than what it draws, so a scripted demo sets its width.
- `scripts/check.sh` fails by its exit status; a pipe into `tail` hides it. - `scripts/check.sh` fails by its exit status; a pipe into `tail` hides it.
@@ -152,7 +153,7 @@ Ely GPUI Component. A component library for GPUI, in light and dark.
- A row that holds text beside fixed parts lets the text give way: the text takes `flex_1().min_w_0()` and an `Ellipsis`; counts, icons, dates and chevrons take `flex_none`. A label that must stay whole wraps in that block instead, as `forms::Checkbox`'s does. - A row that holds text beside fixed parts lets the text give way: the text takes `flex_1().min_w_0()` and an `Ellipsis`; counts, icons, dates and chevrons take `flex_none`. A label that must stay whole wraps in that block instead, as `forms::Checkbox`'s does.
- A mark over a text field reads the field at paint time, from a canvas laid over it (`collab::RemoteCursor`): by then `TextInput::bounds_for` holds this frame's layout, and a still page never renders again to catch up. - A mark over a text field reads the field at paint time, from a canvas laid over it (`collab::RemoteCursor`): by then `TextInput::bounds_for` holds this frame's layout, and a still page never renders again to catch up.
- A callback field holds a named `type` alias; clippy's type_complexity rejects `Option<Rc<dyn Fn(..)>>`. A chapter shares one alias per shape. - A callback field holds a named `type` alias; clippy's type_complexity rejects `Option<Rc<dyn Fn(..)>>`. A chapter shares one alias per shape.
- A component that fills its container takes no width on its root: a block or a flex column stretches it. Percents fill badly in taffy 0.9. `w_full` in a flex column that a block measures by content keeps its content's width: taffy lays the column out once at unknown width and keeps that layout when the width comes out the same. `min_w_full` resolves against a block's outer width, padding and all (`forms::FormField`). In a flex row the host gives it `flex_1`; as a window's root it sits in a `size_full` box. Roots made before T30d still say `w_full`; T44 takes it off. - A component that fills its container takes no width on its root: a block or a flex column stretches it. Percents fill badly in taffy 0.9. `w_full` in a flex column that a block measures by content keeps its content's width: taffy lays the column out once at unknown width and keeps that layout when the width comes out the same. `min_w_full` resolves against a block's outer width, padding and all (`forms::FormField`). In a flex row the host gives it `flex_1`; as a window's root it sits in a `size_full` box. A root centered under a cap keeps `w_full`, since `mx_auto` needs a width to fill (`layout::Container`). A row that hands a text field a `flex_1` share keeps `w_full` too: stretched without a width, taffy 0.9 can keep a pass that laid the share at no width, and the field draws nothing (`forms::Input`, `InputGroup`, `TagInput`).
- A field's `Highlight` sets color, wash, weight, slant and strike per span; measuring and painting share one set of runs, so wrapped heights match. - A field's `Highlight` sets color, wash, weight, slant and strike per span; measuring and painting share one set of runs, so wrapped heights match.
- A block editor keeps one undo for the document: text and structure together, as snapshots through `forms::History`; it takes Undo and Redo before its fields do. - A block editor keeps one undo for the document: text and structure together, as snapshots through `forms::History`; it takes Undo and Redo before its fields do.
- gpui calls focus listeners only while the window is active. A test that needs focus events activates its window with `window.activate_window()`. - gpui calls focus listeners only while the window is active. A test that needs focus events activates its window with `window.activate_window()`.
+1 -1
View File
@@ -150,7 +150,7 @@ Per-component lines live in `tasks/`. Tags there:
- [x] T43a Tooling · Workbench — `tasks/ch31-43.md` - [x] T43a Tooling · Workbench — `tasks/ch31-43.md`
- [x] T43b Tooling · Inspection — `tasks/ch31-43.md` - [x] T43b Tooling · Inspection — `tasks/ch31-43.md`
- [x] T43c Tooling · Catalogs — `tasks/ch31-43.md` - [x] T43c Tooling · Catalogs — `tasks/ch31-43.md`
- [ ] T44a Capture · Roots: roots still on `w_full` drop it; every section checked at 280px and in a padded card - [ ] T44a Capture · Roots: roots still on `w_full` drop it, checked at 280px and in padded cards
- [ ] T44b Capture · Sections: every component's section shot on its own, light and dark, with a manifest the site reads - [ ] T44b Capture · Sections: every component's section shot on its own, light and dark, with a manifest the site reads
- [ ] T44c Capture · Motion: clips of each page's scripted states in motion, for the site - [ ] T44c Capture · Motion: clips of each page's scripted states in motion, for the site
- [ ] T45 Website: `frontend/`, Vite 8, pnpm, light and dark: a home page in motion (three.js, smooth scroll), and a components page with every component, categories on the left and the chosen one's detail on the right, after astryx.atmeta.com - [ ] T45 Website: `frontend/`, Vite 8, pnpm, light and dark: a home page in motion (three.js, smooth scroll), and a components page with every component, categories on the left and the chosen one's detail on the right, after astryx.atmeta.com
+11 -4
View File
@@ -15,11 +15,17 @@ use crate::{
const SETTLE: Duration = Duration::from_millis(700); const SETTLE: Duration = Duration::from_millis(700);
const SCROLL_SETTLE: Duration = Duration::from_millis(250); const SCROLL_SETTLE: Duration = Duration::from_millis(250);
/// Shoots one page, or all, in both modes, then quits. Exits 1 on failure. /// 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) { pub fn run(
window: WindowHandle<Gallery>,
dir: PathBuf,
only: Option<usize>,
scripted: bool,
cx: &mut App,
) {
Theme::update(cx, |theme| theme.reduced_motion = true); Theme::update(cx, |theme| theme.reduced_motion = true);
cx.spawn(async move |cx| { 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())) { if let Err(error) = outcome.and_then(|()| cx.update(|cx| cx.quit())) {
log::error!("capture failed: {error:#}"); log::error!("capture failed: {error:#}");
std::process::exit(1); std::process::exit(1);
@@ -32,6 +38,7 @@ async fn shoot_all(
window: WindowHandle<Gallery>, window: WindowHandle<Gallery>,
dir: &Path, dir: &Path,
only: Option<usize>, only: Option<usize>,
scripted: bool,
cx: &mut AsyncApp, cx: &mut AsyncApp,
) -> Result<()> { ) -> Result<()> {
std::fs::create_dir_all(dir).with_context(|| format!("create {}", dir.display()))?; 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))?; window.update(cx, |gallery, _, cx| gallery.scroll_to(offset, cx))?;
cx.background_executor().timer(SCROLL_SETTLE).await; 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))?; window.update(cx, |gallery, _, cx| gallery.select(ix, cx))?;
cx.background_executor().timer(SETTLE).await; cx.background_executor().timer(SETTLE).await;
let shot = |step: &str| dir.join(format!("{}-{step}-{name}.png", page.slug)); let shot = |step: &str| dir.join(format!("{}-{step}-{name}.png", page.slug));
+13 -2
View File
@@ -68,12 +68,14 @@ fn choose(window: WindowHandle<shell::Gallery>, choice: shell::Choice, cx: &mut
struct Args { struct Args {
page: Option<usize>, page: Option<usize>,
capture: Option<PathBuf>, capture: Option<PathBuf>,
narrow: Option<f32>,
} }
fn parse_args() -> Result<Args> { fn parse_args() -> Result<Args> {
let mut args = Args { let mut args = Args {
page: None, page: None,
capture: None, capture: None,
narrow: None,
}; };
let mut iter = std::env::args().skip(1); let mut iter = std::env::args().skip(1);
while let Some(flag) = iter.next() { 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}"))?) args.page = Some(pages::find(&value).with_context(|| format!("no page {value}"))?)
} }
"--capture" => args.capture = Some(PathBuf::from(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}"), other => bail!("unknown flag {other}"),
} }
} }
@@ -118,13 +127,15 @@ fn main() -> Result<()> {
}; };
let window = cx let window = cx
.open_window(options, |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"); .expect("gallery window failed to open");
menu_actions(window, cx); menu_actions(window, cx);
cx.activate(true); cx.activate(true);
if let Some(dir) = args.capture { 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(()) Ok(())
-2
View File
@@ -114,7 +114,6 @@ const SCRIPT: &[Step] = &[
Step::Shot("hotkey"), Step::Shot("hotkey"),
Step::DownAt("path", 380.0, 16.0), Step::DownAt("path", 380.0, 16.0),
Step::UpAt("path", 380.0, 16.0), Step::UpAt("path", 380.0, 16.0),
Step::Wait(1200),
Step::CancelPanel, Step::CancelPanel,
Step::Wait(600), Step::Wait(600),
Step::DownAt("checks", 40.0, 66.0), Step::DownAt("checks", 40.0, 66.0),
@@ -242,7 +241,6 @@ const SCRIPT: &[Step] = &[
Step::Shot("gradient"), Step::Shot("gradient"),
Step::DownAt("file-one", 100.0, 14.0), Step::DownAt("file-one", 100.0, 14.0),
Step::UpAt("file-one", 100.0, 14.0), Step::UpAt("file-one", 100.0, 14.0),
Step::Wait(1200),
Step::CancelPanel, Step::CancelPanel,
Step::Wait(600), Step::Wait(600),
Step::Shot("files"), Step::Shot("files"),
+21 -6
View File
@@ -53,6 +53,8 @@ pub enum Step {
} }
const FRAME: Duration = Duration::from_millis(120); 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; const DRAG_STEPS: u32 = 10;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
@@ -218,7 +220,15 @@ pub async fn play(
let number = window.update(cx, |_, window, _| number(window))??; let number = window.update(cx, |_, window, _| number(window))??;
post_key(number, true, text, code)?; 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) => { Step::ExpectClosed(key) => {
let handle = opened(key, cx)?; let handle = opened(key, cx)?;
let open = cx.update(|cx| cx.windows().contains(&handle))?; 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")] #[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::{ use cocoa::{
appkit::NSApp, appkit::NSApp,
base::{BOOL, NO, id, nil}, base::{BOOL, NO, id, nil},
@@ -371,16 +382,20 @@ fn cancel_panel() -> Result<()> {
use objc::{class, msg_send, sel, sel_impl}; use objc::{class, msg_send, sel, sel_impl};
unsafe { unsafe {
let key: id = msg_send![NSApp(), keyWindow]; 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)]; 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]; let _: () = msg_send![key, cancel: nil];
} }
Ok(()) Ok(true)
} }
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
fn cancel_panel() -> Result<()> { fn cancel_panel() -> Result<bool> {
anyhow::bail!("file panels need macOS") anyhow::bail!("file panels need macOS")
} }
+63 -30
View File
@@ -5,9 +5,9 @@ use ely_gpui_component::{
theme::{ActiveTheme, ControlSize, Mode, Radius, TextSize, Theme}, theme::{ActiveTheme, ControlSize, Mode, Radius, TextSize, Theme},
}; };
use gpui::{ use gpui::{
Bounds, Context, FocusHandle, FontWeight, InteractiveElement, IntoElement, ParentElement, AnyElement, App, Bounds, Context, FocusHandle, FontWeight, InteractiveElement, IntoElement,
Pixels, Render, ScrollHandle, SharedString, StatefulInteractiveElement, Styled, Subscription, ParentElement, Pixels, Render, ScrollHandle, SharedString, StatefulInteractiveElement, Styled,
Window, div, point, prelude::*, px, Subscription, Window, div, point, prelude::*, px,
}; };
use crate::pages; use crate::pages;
@@ -26,11 +26,18 @@ pub struct Gallery {
choice: Choice, choice: Choice,
focus: FocusHandle, focus: FocusHandle,
scroll: ScrollHandle, scroll: ScrollHandle,
/// Narrow check width, if any.
narrow: Option<Pixels>,
_appearance: Subscription, _appearance: Subscription,
} }
impl Gallery { 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| { let appearance = cx.observe_window_appearance(window, |gallery, window, cx| {
if gallery.choice == Choice::System { if gallery.choice == Choice::System {
Theme::set_mode(window.appearance().into(), cx); Theme::set_mode(window.appearance().into(), cx);
@@ -44,6 +51,7 @@ impl Gallery {
choice: Choice::Light, choice: Choice::Light,
focus, focus,
scroll: ScrollHandle::new(), scroll: ScrollHandle::new(),
narrow: narrow.map(px),
_appearance: appearance, _appearance: appearance,
} }
} }
@@ -174,6 +182,7 @@ impl Gallery {
]; ];
let colors = &cx.theme().colors; let colors = &cx.theme().colors;
div() div()
.flex_none()
.flex() .flex()
.gap_0p5() .gap_0p5()
.p_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 { impl Render for Gallery {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let page = &pages::ALL[self.page]; let page = &pages::ALL[self.page];
@@ -229,32 +256,38 @@ impl Render for Gallery {
.pt(px(56.0)) .pt(px(56.0))
.pb(px(96.0)) .pb(px(96.0))
.child( .child(
div() self.card(
.flex() div()
.items_start() .flex()
.justify_between() .flex_wrap()
.gap_6() .items_start()
.child( .justify_between()
div() .gap_6()
.flex() .child(
.flex_col() div()
.gap_2() .flex_1()
.child( .min_w(px(200.0))
div() .flex()
.text_size(theme.text_size(TextSize::Xxl)) .flex_col()
.font_weight(FontWeight::SEMIBOLD) .gap_2()
.child(page.title), .child(
) div()
.child( .text_size(theme.text_size(TextSize::Xxl))
div() .font_weight(FontWeight::SEMIBOLD)
.text_size(theme.text_size(TextSize::Md)) .child(page.title),
.text_color(colors.fg_muted) )
.child(page.summary), .child(
), div()
) .text_size(theme.text_size(TextSize::Md))
.child(switcher), .text_color(colors.fg_muted)
) .child(page.summary),
.child(body), ),
)
.child(switcher),
body,
cx,
),
),
), ),
) )
} }
-2
View File
@@ -119,7 +119,6 @@ impl RenderOnce for FileChangeCard {
let theme = cx.theme(); let theme = cx.theme();
let colors = theme.colors.clone(); let colors = theme.colors.clone();
div() div()
.w_full()
.rounded(theme.radius(Radius::Md)) .rounded(theme.radius(Radius::Md))
.border_1() .border_1()
.border_color(colors.border) .border_color(colors.border)
@@ -270,7 +269,6 @@ impl RenderOnce for MultiFileDiffReview {
) )
}); });
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_2() .gap_2()
-2
View File
@@ -88,7 +88,6 @@ impl RenderOnce for HumanInputRequest {
let theme = cx.theme(); let theme = cx.theme();
let colors = theme.colors.clone(); let colors = theme.colors.clone();
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_3() .gap_3()
@@ -177,7 +176,6 @@ impl RenderOnce for CostBreakdown {
) )
}); });
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_3() .gap_3()
-1
View File
@@ -117,7 +117,6 @@ impl RenderOnce for SandboxStatus {
let live = matches!(self.state, SandboxState::Starting | SandboxState::Busy); let live = matches!(self.state, SandboxState::Starting | SandboxState::Busy);
let color = self.state.color(&colors); let color = self.state.color(&colors);
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_3() .gap_3()
-1
View File
@@ -84,7 +84,6 @@ impl RenderOnce for MemoryPanel {
) )
}); });
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_3() .gap_3()
-1
View File
@@ -137,7 +137,6 @@ impl RenderOnce for PermissionPrompt {
}) })
}; };
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_3() .gap_3()
-2
View File
@@ -118,7 +118,6 @@ impl RenderOnce for AgentPlan {
.filter(|(_, state)| *state == StepState::Done) .filter(|(_, state)| *state == StepState::Done)
.count(); .count();
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_2() .gap_2()
@@ -284,7 +283,6 @@ impl RenderOnce for AgentProgress {
let theme = cx.theme(); let theme = cx.theme();
let colors = theme.colors.clone(); let colors = theme.colors.clone();
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_2() .gap_2()
-1
View File
@@ -112,7 +112,6 @@ impl RenderOnce for ToolRegistry {
let groups = grouped(&self.tools, &found(&self.tools, &self.query)); let groups = grouped(&self.tools, &found(&self.tools, &self.query));
let empty = groups.is_empty(); let empty = groups.is_empty();
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_4() .gap_4()
-2
View File
@@ -109,7 +109,6 @@ impl RenderOnce for ToolCallCard {
.child(text) .child(text)
}; };
div() div()
.w_full()
.rounded(theme.radius(Radius::Md)) .rounded(theme.radius(Radius::Md))
.border_1() .border_1()
.border_color(colors.border) .border_color(colors.border)
@@ -217,7 +216,6 @@ impl RenderOnce for ToolCallGroup {
let theme = cx.theme(); let theme = cx.theme();
let colors = theme.colors.clone(); let colors = theme.colors.clone();
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_2() .gap_2()
-2
View File
@@ -142,7 +142,6 @@ impl RenderOnce for AgendaView {
if owners.is_empty() { if owners.is_empty() {
return div() return div()
.debug_selector(|| "agenda-none".into()) .debug_selector(|| "agenda-none".into())
.w_full()
.py_6() .py_6()
.flex() .flex()
.justify_center() .justify_center()
@@ -172,7 +171,6 @@ impl RenderOnce for AgendaView {
}) })
}; };
div() div()
.w_full()
.child( .child(
sections sections
.selected(picked.read(cx).clone()) .selected(picked.read(cx).clone())
-1
View File
@@ -119,7 +119,6 @@ impl RenderOnce for AllDayRow {
}); });
div() div()
.relative() .relative()
.w_full()
.h(pad * 2.0 + pitch * lanes as f32 - gap) .h(pad * 2.0 + pitch * lanes as f32 - gap)
.border_b_1() .border_b_1()
.border_color(theme.colors.border) .border_color(theme.colors.border)
-1
View File
@@ -93,7 +93,6 @@ impl RenderOnce for EventChip {
.flex() .flex()
.items_center() .items_center()
.gap_1() .gap_1()
.w_full()
.h(theme.calendar().chip) .h(theme.calendar().chip)
.px_1p5() .px_1p5()
.rounded(round) .rounded(round)
-1
View File
@@ -420,7 +420,6 @@ impl RenderOnce for CalendarMonthView {
.collect(); .collect();
let title = shown.strftime("%B %Y").to_string(); let title = shown.strftime("%B %Y").to_string();
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_3() .gap_3()
-1
View File
@@ -173,7 +173,6 @@ impl RenderOnce for AttendeeList {
}) })
.collect(); .collect();
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_1() .gap_1()
-1
View File
@@ -300,7 +300,6 @@ impl RenderOnce for YearView {
}) })
.collect(); .collect();
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_4() .gap_4()
-2
View File
@@ -74,7 +74,6 @@ impl RenderOnce for QuoteReply {
let colors = theme.colors.clone(); let colors = theme.colors.clone();
let line = self.text.lines().next().unwrap_or_default().to_string(); let line = self.text.lines().next().unwrap_or_default().to_string();
div() div()
.w_full()
.flex() .flex()
.items_center() .items_center()
.gap_2() .gap_2()
@@ -261,7 +260,6 @@ impl RenderOnce for MessageEditor {
self.on_cancel, self.on_cancel,
); );
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_2() .gap_2()
-2
View File
@@ -105,7 +105,6 @@ impl RenderOnce for SourceCard {
let open = self.on_open; let open = self.on_open;
div() div()
.id(self.id.clone()) .id(self.id.clone())
.w_full()
.p_2p5() .p_2p5()
.rounded(theme.radius(Radius::Md)) .rounded(theme.radius(Radius::Md))
.border_1() .border_1()
@@ -222,7 +221,6 @@ impl RenderOnce for SourceList {
.take(3) .take(3)
.collect(); .collect();
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_2() .gap_2()
-1
View File
@@ -94,7 +94,6 @@ impl RenderOnce for CodeBlock {
let lines = code.lines().count(); let lines = code.lines().count();
let toggle = open.clone(); let toggle = open.clone();
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.rounded(theme.radius(Radius::Md)) .rounded(theme.radius(Radius::Md))
-1
View File
@@ -179,7 +179,6 @@ impl RenderOnce for ConversationItem {
.id(self.id.clone()) .id(self.id.clone())
.group(group.clone()) .group(group.clone())
.track_focus(&focus) .track_focus(&focus)
.w_full()
.flex() .flex()
.items_center() .items_center()
.gap_1() .gap_1()
+14 -3
View File
@@ -105,7 +105,16 @@ impl RenderOnce for MessageList {
div() div()
.relative() .relative()
.size_full() .size_full()
.child(list(state, move |ix, window, cx| render(ix, window, cx)).size_full()) // gpui's list lays rows at content width.
.child(
list(state, move |ix, window, cx| {
div()
.w_full()
.child(render(ix, window, cx))
.into_any_element()
})
.size_full(),
)
.child( .child(
canvas( canvas(
move |_, window, cx| { move |_, window, cx| {
@@ -234,8 +243,10 @@ impl RenderOnce for DateSeparator {
let theme = cx.theme(); let theme = cx.theme();
let colors = theme.colors.clone(); let colors = theme.colors.clone();
let rule = || div().flex_1().h_px().bg(colors.border); let rule = || div().flex_1().h_px().bg(colors.border);
let name = day_name(self.day, self.today);
let shown = format!("date-separator-{name}");
div() div()
.w_full() .debug_selector(move || shown)
.flex() .flex()
.items_center() .items_center()
.gap_3() .gap_3()
@@ -245,7 +256,7 @@ impl RenderOnce for DateSeparator {
div() div()
.text_size(theme.text_size(TextSize::Xs)) .text_size(theme.text_size(TextSize::Xs))
.text_color(colors.fg_subtle) .text_color(colors.fg_subtle)
.child(day_name(self.day, self.today)), .child(name),
) )
.child(rule()) .child(rule())
} }
-3
View File
@@ -72,7 +72,6 @@ impl RenderOnce for ImageMessage {
let widest = theme.prose_width().to_pixels(window.rem_size()); let widest = theme.prose_width().to_pixels(window.rem_size());
let open = self.on_open; let open = self.on_open;
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_1() .gap_1()
@@ -235,7 +234,6 @@ impl RenderOnce for FileMessage {
let theme = cx.theme(); let theme = cx.theme();
let colors = theme.colors.clone(); let colors = theme.colors.clone();
div() div()
.w_full()
.max_w(theme.prose_width()) .max_w(theme.prose_width())
.flex() .flex()
.items_center() .items_center()
@@ -339,7 +337,6 @@ impl RenderOnce for LinkPreviewCard {
let open = self.on_open; let open = self.on_open;
div() div()
.id(self.id.clone()) .id(self.id.clone())
.w_full()
.max_w(theme.container_width(ContainerSize::Sm)) .max_w(theme.container_width(ContainerSize::Sm))
.flex() .flex()
.gap_3() .gap_3()
-2
View File
@@ -81,7 +81,6 @@ impl RenderOnce for ParameterPanel {
let theme = cx.theme(); let theme = cx.theme();
let colors = theme.colors.clone(); let colors = theme.colors.clone();
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_4() .gap_4()
@@ -274,7 +273,6 @@ impl RenderOnce for SystemPromptEditor {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let theme = cx.theme(); let theme = cx.theme();
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_2() .gap_2()
-1
View File
@@ -97,7 +97,6 @@ impl RenderOnce for AudioMessage {
let playing = self.playing; let playing = self.playing;
let tall = theme.control_height(ControlSize::Md); let tall = theme.control_height(ControlSize::Md);
div() div()
.w_full()
.max_w(theme.prose_width()) .max_w(theme.prose_width())
.flex() .flex()
.items_center() .items_center()
-2
View File
@@ -62,7 +62,6 @@ impl RenderOnce for ProjectList {
let theme = cx.theme(); let theme = cx.theme();
let colors = theme.colors.clone(); let colors = theme.colors.clone();
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_0p5() .gap_0p5()
@@ -163,7 +162,6 @@ impl RenderOnce for ProjectKnowledgePanel {
let used: u64 = self.files.iter().map(|(_, bytes)| bytes).sum(); let used: u64 = self.files.iter().map(|(_, bytes)| bytes).sum();
let share = used as f32 / self.capacity as f32; let share = used as f32 / self.capacity as f32;
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_3() .gap_3()
-2
View File
@@ -164,7 +164,6 @@ impl RenderOnce for WebResultCard {
let open = self.on_open; let open = self.on_open;
div() div()
.id(self.id.clone()) .id(self.id.clone())
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_0p5() .gap_0p5()
@@ -273,7 +272,6 @@ impl RenderOnce for DocumentChunkPreview {
..HighlightStyle::default() ..HighlightStyle::default()
}; };
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_1p5() .gap_1p5()
-3
View File
@@ -112,7 +112,6 @@ impl RenderOnce for RateLimitNotice {
None => countdown, None => countdown,
}; };
div() div()
.w_full()
.flex() .flex()
.items_center() .items_center()
.gap_3() .gap_3()
@@ -198,7 +197,6 @@ impl RenderOnce for FeedbackForm {
let ready = !self.picked.is_empty() || !self.note.read(cx).text().trim().is_empty(); let ready = !self.picked.is_empty() || !self.note.read(cx).text().trim().is_empty();
let (pick, send) = (self.on_pick, self.on_send); let (pick, send) = (self.on_pick, self.on_send);
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_3() .gap_3()
@@ -356,7 +354,6 @@ impl RenderOnce for ThinkingBlock {
ThinkingDuration::new(self.took).into_any_element() ThinkingDuration::new(self.took).into_any_element()
}; };
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_2() .gap_2()
+28 -1
View File
@@ -13,7 +13,8 @@ mod history;
mod welcome; mod welcome;
use super::{ use super::{
ImageMessage, MessageAvatar, MessageBubble, MessageEditor, MessageList, Role, ThinkingBlock, DateSeparator, ImageMessage, MessageAvatar, MessageBubble, MessageEditor, MessageList, Role,
ThinkingBlock,
}; };
use crate::{forms, forms::TextInput, theme::Theme}; use crate::{forms, forms::TextInput, theme::Theme};
@@ -361,3 +362,29 @@ fn the_reasoning_opens_from_the_keyboard(cx: &mut TestAppContext) {
"Tab and Space open it: {closed:?} to {open:?}" "Tab and Space open it: {closed:?} to {open:?}"
); );
} }
struct Dated;
impl Render for Dated {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
let day = jiff::civil::date(2026, 9, 28);
div()
.size_full()
.child(MessageList::new("dated", 1, move |_, _, _| {
DateSeparator::new(day, day).into_any_element()
}))
}
}
/// A row no wider than its words still spans the list.
#[gpui::test]
fn a_narrow_row_spans_the_list(cx: &mut TestAppContext) {
cx.update(Theme::init);
let (_, cx) = cx.add_window_view(|_, _| Dated);
settle(cx);
let row = cx
.debug_bounds("date-separator-Today")
.expect("the separator draws");
let width = cx.update(|window, _| window.viewport_size().width);
assert_eq!(row.size.width, width);
}
-2
View File
@@ -180,7 +180,6 @@ impl RenderOnce for FollowUpSuggestions {
let theme = cx.theme(); let theme = cx.theme();
let colors = theme.colors.clone(); let colors = theme.colors.clone();
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.children( .children(
@@ -253,7 +252,6 @@ impl RenderOnce for CapabilityCards {
let theme = cx.theme(); let theme = cx.theme();
let colors = theme.colors.clone(); let colors = theme.colors.clone();
div() div()
.w_full()
.grid() .grid()
.grid_cols(3) .grid_cols(3)
.gap_3() .gap_3()
+17 -1
View File
@@ -12,12 +12,28 @@ fn open(
forms::bind_keys(cx); forms::bind_keys(cx);
crate::documents::bind_keys(cx); crate::documents::bind_keys(cx);
}); });
let (editor, cx) = cx.add_window_view(|window, cx| BlockEditor::new(blocks, window, cx)); let (full, cx) =
cx.add_window_view(|window, cx| Full(cx.new(|cx| BlockEditor::new(blocks, window, cx))));
let editor = full.read_with(cx, |full, _| full.0.clone());
cx.update(|window, _| window.activate_window()); cx.update(|window, _| window.activate_window());
settle(cx); settle(cx);
(editor, cx) (editor, cx)
} }
/// The editor as a window's content, in the box that fills the window.
struct Full(Entity<BlockEditor>);
impl gpui::Render for Full {
fn render(
&mut self,
_: &mut gpui::Window,
_: &mut gpui::Context<Self>,
) -> impl gpui::IntoElement {
use gpui::{ParentElement, Styled};
gpui::div().size_full().child(self.0.clone())
}
}
fn settle(cx: &mut VisualTestContext) { fn settle(cx: &mut VisualTestContext) {
cx.run_until_parked(); cx.run_until_parked();
cx.update(|window, _| window.refresh()); cx.update(|window, _| window.refresh());
-1
View File
@@ -253,7 +253,6 @@ impl Render for BlockEditor {
let start = cx.entity(); let start = cx.entity();
div() div()
.id(id.clone()) .id(id.clone())
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.capture_action(cx.listener(|editor, _: &Undo, window, cx| { .capture_action(cx.listener(|editor, _: &Undo, window, cx| {
-1
View File
@@ -108,7 +108,6 @@ impl RenderOnce for PageCover {
.id(self.id.clone()) .id(self.id.clone())
.group(group.clone()) .group(group.clone())
.relative() .relative()
.w_full()
.h(theme.page_cover()) .h(theme.page_cover())
.child( .child(
Image::new((self.id.clone(), "picture"), source(&self.source)) Image::new((self.id.clone(), "picture"), source(&self.source))
+16 -20
View File
@@ -208,27 +208,23 @@ impl RenderOnce for ReadingProgress {
let theme = cx.theme(); let theme = cx.theme();
let (track, bar) = (theme.colors.border, theme.colors.accent); let (track, bar) = (theme.colors.border, theme.colors.accent);
let scroll = self.scroll; let scroll = self.scroll;
div() div().h(theme.progress_thickness()).bg(track).child(
.w_full() canvas(
.h(theme.progress_thickness()) |_, _, _| {},
.bg(track) move |bounds, _, window, _| {
.child( let share = read_share(
canvas( f32::from(scroll.offset().y),
|_, _, _| {}, f32::from(scroll.max_offset().height),
move |bounds, _, window, _| { );
let share = read_share( let filled = Bounds::new(
f32::from(scroll.offset().y), bounds.origin,
f32::from(scroll.max_offset().height), size(bounds.size.width * share, bounds.size.height),
); );
let filled = Bounds::new( window.paint_quad(fill(filled, bar));
bounds.origin, },
size(bounds.size.width * share, bounds.size.height),
);
window.paint_quad(fill(filled, bar));
},
)
.size_full(),
) )
.size_full(),
)
} }
} }
-1
View File
@@ -166,7 +166,6 @@ impl RenderOnce for Banner {
.items_center() .items_center()
.justify_center() .justify_center()
.gap_2() .gap_2()
.w_full()
.min_h(theme.control_height(ControlSize::Lg)) .min_h(theme.control_height(ControlSize::Lg))
.py_1() .py_1()
.px_10() .px_10()
-1
View File
@@ -212,7 +212,6 @@ impl RenderOnce for DuplicateFinder {
}); });
div() div()
.debug_selector(|| "duplicate-finder".into()) .debug_selector(|| "duplicate-finder".into())
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_4() .gap_4()
-1
View File
@@ -278,7 +278,6 @@ impl RenderOnce for FileExplorer {
}; };
div() div()
.debug_selector(|| "file-explorer".into()) .debug_selector(|| "file-explorer".into())
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_3() .gap_3()
-1
View File
@@ -177,7 +177,6 @@ impl RenderOnce for FileGrid {
.id(self.id) .id(self.id)
.debug_selector(|| "file-grid".into()) .debug_selector(|| "file-grid".into())
.relative() .relative()
.w_full()
.flex() .flex()
.flex_wrap() .flex_wrap()
.gap(gap) .gap(gap)
-1
View File
@@ -174,7 +174,6 @@ impl RenderOnce for FileOperationProgress {
); );
div() div()
.debug_selector(|| "file-operation".into()) .debug_selector(|| "file-operation".into())
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_2() .gap_2()
-1
View File
@@ -122,7 +122,6 @@ impl RenderOnce for FilePreview {
}; };
div() div()
.debug_selector(|| "file-preview".into()) .debug_selector(|| "file-preview".into())
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_3() .gap_3()
-1
View File
@@ -140,7 +140,6 @@ impl RenderOnce for RecentFiles {
}); });
div() div()
.debug_selector(|| "recent-files".into()) .debug_selector(|| "recent-files".into())
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_4() .gap_4()
-1
View File
@@ -122,7 +122,6 @@ impl RenderOnce for FileSearch {
}; };
div() div()
.debug_selector(|| "file-search".into()) .debug_selector(|| "file-search".into())
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_3() .gap_3()
-2
View File
@@ -80,7 +80,6 @@ impl RenderOnce for TransferQueue {
let theme = cx.theme(); let theme = cx.theme();
div() div()
.debug_selector(|| "transfer-queue".into()) .debug_selector(|| "transfer-queue".into())
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_1() .gap_1()
@@ -192,7 +191,6 @@ impl RenderOnce for DownloadManager {
}); });
div() div()
.debug_selector(|| "download-manager".into()) .debug_selector(|| "download-manager".into())
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_1() .gap_1()
+5 -8
View File
@@ -138,13 +138,9 @@ impl RenderOnce for FontPicker {
if let Some(on_change) = self.on_change { if let Some(on_change) = self.on_change {
combobox = combobox.on_change(move |family, window, cx| on_change(family, window, cx)); combobox = combobox.on_change(move |family, window, cx| on_change(family, window, cx));
} }
div() div().flex().flex_col().gap_2().child(combobox).when_some(
.flex() self.selected,
.flex_col() |picker, family| {
.gap_2()
.w_full()
.child(combobox)
.when_some(self.selected, |picker, family| {
picker.child( picker.child(
div() div()
.font_family(family) .font_family(family)
@@ -152,7 +148,8 @@ impl RenderOnce for FontPicker {
.text_color(theme.colors.fg_muted) .text_color(theme.colors.fg_muted)
.child("The quick brown fox jumps over the lazy dog"), .child("The quick brown fox jumps over the lazy dog"),
) )
}) },
)
} }
} }
-2
View File
@@ -173,7 +173,6 @@ impl RenderOnce for CodeInput {
"a code input is one line; JsonInput takes several" "a code input is one line; JsonInput takes several"
); );
div() div()
.w_full()
.font_family(cx.theme().mono_family.clone()) .font_family(cx.theme().mono_family.clone())
.child(Input::new(&self.state).size(self.size)) .child(Input::new(&self.state).size(self.size))
} }
@@ -205,7 +204,6 @@ impl RenderOnce for JsonInput {
.flex() .flex()
.flex_col() .flex_col()
.gap_1() .gap_1()
.w_full()
.child( .child(
div() div()
.font_family(theme.mono_family.clone()) .font_family(theme.mono_family.clone())
-1
View File
@@ -275,7 +275,6 @@ impl RenderOnce for GradientEditor {
.flex() .flex()
.flex_col() .flex_col()
.gap_2() .gap_2()
.w_full()
.child( .child(
div() div()
.flex() .flex()
-1
View File
@@ -375,7 +375,6 @@ impl RenderOnce for ColorPicker {
.flex() .flex()
.flex_col() .flex_col()
.gap_3() .gap_3()
.w_full()
.px(knob * 0.5) .px(knob * 0.5)
.pt(knob * 0.5) .pt(knob * 0.5)
.child(plane) .child(plane)
-1
View File
@@ -214,7 +214,6 @@ impl RenderOnce for Combobox {
div() div()
.id(self.id.clone()) .id(self.id.clone())
.relative() .relative()
.w_full()
.capture_action(move |_: &Up, _, cx| { .capture_action(move |_: &Up, _, cx| {
if open { if open {
cx.stop_propagation(); cx.stop_propagation();
-1
View File
@@ -276,7 +276,6 @@ impl RenderOnce for ExpressionInput {
.flex() .flex()
.flex_col() .flex_col()
.gap_1() .gap_1()
.w_full()
.child( .child(
div().font_family(mono).child( div().font_family(mono).child(
Input::new(&self.state) Input::new(&self.state)
-1
View File
@@ -58,7 +58,6 @@ impl RenderOnce for HotkeyInput {
.items_center() .items_center()
.justify_between() .justify_between()
.gap_2() .gap_2()
.w_full()
.h(theme.control_height(ControlSize::Md)) .h(theme.control_height(ControlSize::Md))
.px(theme.control_padding(ControlSize::Md)) .px(theme.control_padding(ControlSize::Md))
.rounded(theme.radius(Radius::Md)) .rounded(theme.radius(Radius::Md))
-1
View File
@@ -129,7 +129,6 @@ impl RenderOnce for InlineEdit {
let (escape, enter) = (state.clone(), state.clone()); let (escape, enter) = (state.clone(), state.clone());
return div() return div()
.id(self.id) .id(self.id)
.w_full()
.on_key_down(move |event, window, cx| { .on_key_down(move |event, window, cx| {
if event.keystroke.key == "escape" { if event.keystroke.key == "escape" {
cx.stop_propagation(); cx.stop_propagation();
-1
View File
@@ -270,7 +270,6 @@ impl RenderOnce for PhoneInput {
); );
div() div()
.relative() .relative()
.w_full()
.child(MaskedInput::new(self.id.clone(), &self.state, mask).prefix(code)) .child(MaskedInput::new(self.id.clone(), &self.state, mask).prefix(code))
.child( .child(
canvas( canvas(
-1
View File
@@ -153,7 +153,6 @@ impl RenderOnce for MultiSelect {
.flex_wrap() .flex_wrap()
.items_center() .items_center()
.gap_1() .gap_1()
.w_full()
.min_h(theme.control_height(self.size)) .min_h(theme.control_height(self.size))
.pl_1() .pl_1()
.pr(theme.control_padding(self.size)) .pr(theme.control_padding(self.size))
-1
View File
@@ -158,7 +158,6 @@ impl RenderOnce for FieldArray {
.flex() .flex()
.flex_col() .flex_col()
.gap_2() .gap_2()
.w_full()
.children(lines) .children(lines)
.child( .child(
div().flex().child( div().flex().child(
-1
View File
@@ -136,7 +136,6 @@ impl RenderOnce for RegexInput {
.flex() .flex()
.flex_col() .flex_col()
.gap_1() .gap_1()
.w_full()
.child( .child(
div().font_family(mono).child( div().font_family(mono).child(
Input::new(&self.state) Input::new(&self.state)
-1
View File
@@ -81,7 +81,6 @@ impl RenderOnce for SearchInput {
}); });
div() div()
.relative() .relative()
.w_full()
.child( .child(
Input::new(&self.state) Input::new(&self.state)
.size(self.size) .size(self.size)
-1
View File
@@ -260,7 +260,6 @@ impl RenderOnce for SignaturePad {
.flex_col() .flex_col()
.items_end() .items_end()
.gap_1() .gap_1()
.w_full()
.child(pad) .child(pad)
.child( .child(
Button::new((self.id, "clear"), "Clear") Button::new((self.id, "clear"), "Clear")
-3
View File
@@ -55,7 +55,6 @@ impl RenderOnce for Form {
.flex() .flex()
.flex_col() .flex_col()
.gap_8() .gap_8()
.w_full()
.when_some(self.on_submit, |form, submit| { .when_some(self.on_submit, |form, submit| {
form.capture_action(move |_: &Submit, window, cx| { form.capture_action(move |_: &Submit, window, cx| {
cx.stop_propagation(); cx.stop_propagation();
@@ -102,7 +101,6 @@ impl RenderOnce for FormSection {
.flex() .flex()
.flex_col() .flex_col()
.gap_4() .gap_4()
.w_full()
.child( .child(
div() div()
.flex() .flex()
@@ -304,7 +302,6 @@ impl RenderOnce for InlineForm {
.flex_wrap() .flex_wrap()
.items_end() .items_end()
.gap_3() .gap_3()
.w_full()
.children(self.children) .children(self.children)
} }
} }
+20 -16
View File
@@ -1,6 +1,6 @@
use gpui::{ use gpui::{
Context, Hsla, IntoElement, Modifiers, MouseButton, Render, TestAppContext, Window, point, px, Context, Hsla, IntoElement, Modifiers, MouseButton, ParentElement, Render, Styled,
rgb, rgba, TestAppContext, Window, div, point, px, rgb, rgba,
}; };
use super::setup; use super::setup;
@@ -13,14 +13,16 @@ struct Picked {
impl Render for Picked { impl Render for Picked {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let view = cx.entity(); let view = cx.entity();
ColorPicker::new("picked", self.color) div()
.opaque() .size_full()
.on_change(move |color, _, cx| { .child(ColorPicker::new("picked", self.color).opaque().on_change(
view.update(cx, |view, cx| { move |color, _, cx| {
view.color = color; view.update(cx, |view, cx| {
cx.notify(); view.color = color;
}) cx.notify();
}) })
},
))
} }
} }
@@ -41,12 +43,14 @@ struct Gradient {
impl Render for Gradient { impl Render for Gradient {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let view = cx.entity(); let view = cx.entity();
GradientEditor::new("gradient", self.stops.clone()).on_change(move |next, _, cx| { div().size_full().child(
view.update(cx, |view, cx| { GradientEditor::new("gradient", self.stops.clone()).on_change(move |next, _, cx| {
view.stops = next.to_vec(); view.update(cx, |view, cx| {
cx.notify(); view.stops = next.to_vec();
}) cx.notify();
}) })
}),
)
} }
} }
+10 -6
View File
@@ -48,12 +48,16 @@ struct Pad {
impl Render for Pad { impl Render for Pad {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let view = cx.entity(); let view = cx.entity();
SignaturePad::new("pad", self.strokes.clone()).on_change(move |next, _, cx| { div()
view.update(cx, |view, cx| { .size_full()
view.strokes = next.to_vec(); .child(
cx.notify(); SignaturePad::new("pad", self.strokes.clone()).on_change(move |next, _, cx| {
}) view.update(cx, |view, cx| {
}) view.strokes = next.to_vec();
cx.notify();
})
}),
)
} }
} }
-1
View File
@@ -358,7 +358,6 @@ impl Render for TextInput {
.id("ely-text-input") .id("ely-text-input")
.key_context(CONTEXT) .key_context(CONTEXT)
.track_focus(&self.focus) .track_focus(&self.focus)
.w_full()
.cursor(if self.disabled { .cursor(if self.disabled {
CursorStyle::OperationNotAllowed CursorStyle::OperationNotAllowed
} else { } else {
-1
View File
@@ -181,7 +181,6 @@ impl RenderOnce for TransferList {
div() div()
.flex() .flex()
.gap_3() .gap_3()
.w_full()
.child(side("left", available_title, available, left, cx)) .child(side("left", available_title, available, left, cx))
.child(buttons) .child(buttons)
.child(side("right", chosen_title, picked, right, cx)) .child(side("right", chosen_title, picked, right, cx))
-1
View File
@@ -152,7 +152,6 @@ impl RenderOnce for AudioGenerationPlayer {
} }
}; };
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_3() .gap_3()
-1
View File
@@ -80,7 +80,6 @@ impl RenderOnce for MaskBrush {
}) })
}; };
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_3() .gap_3()
-1
View File
@@ -168,7 +168,6 @@ impl RenderOnce for ABCompareView {
.collect(), .collect(),
}; };
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_3() .gap_3()
+1 -1
View File
@@ -256,7 +256,7 @@ impl RenderOnce for ModelDownloadManager {
) )
.children(bar) .children(bar)
}); });
div().w_full().flex().flex_col().children(rows) div().flex().flex_col().children(rows)
} }
} }
+1 -7
View File
@@ -313,12 +313,6 @@ impl RenderOnce for EmbeddingVisualizer {
cx.notify(); cx.notify();
}) })
}); });
div() div().flex().flex_col().gap_3().child(legend).child(area)
.w_full()
.flex()
.flex_col()
.gap_3()
.child(legend)
.child(area)
} }
} }
+1 -1
View File
@@ -207,6 +207,6 @@ impl RenderOnce for GenerationGrid {
.children((0..short).map(|_| div().flex_1())), .children((0..short).map(|_| div().flex_1())),
); );
} }
div().w_full().flex().flex_col().gap_2().children(rows) div().flex().flex_col().gap_2().children(rows)
} }
} }
+6 -4
View File
@@ -45,8 +45,11 @@ impl HardwareMonitor {
impl RenderOnce for HardwareMonitor { impl RenderOnce for HardwareMonitor {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let spark = cx.theme().spark_size(); let spark = cx.theme().spark_size();
div().w_full().flex().flex_col().gap_3().children( div()
self.readings.into_iter().enumerate().map(|(ix, reading)| { .flex()
.flex_col()
.gap_3()
.children(self.readings.into_iter().enumerate().map(|(ix, reading)| {
div() div()
.flex() .flex()
.items_end() .items_end()
@@ -71,7 +74,6 @@ impl RenderOnce for HardwareMonitor {
.child(Sparkline::new(reading.history).area()), .child(Sparkline::new(reading.history).area()),
) )
}) })
}), }))
)
} }
} }
-2
View File
@@ -95,7 +95,6 @@ impl RenderOnce for ModelCard {
}) })
}); });
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_3() .gap_3()
@@ -260,7 +259,6 @@ impl RenderOnce for ModelStatus {
_ => None, _ => None,
}; };
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_2() .gap_2()
-1
View File
@@ -154,7 +154,6 @@ impl RenderOnce for PromptPlayground {
) )
}); });
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_3() .gap_3()
-1
View File
@@ -199,7 +199,6 @@ impl RenderOnce for PromptEnhancer {
), ),
); );
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.child(Input::new(&self.field)) .child(Input::new(&self.field))
-1
View File
@@ -76,7 +76,6 @@ impl RenderOnce for AspectRatioPicker {
let colors = theme.colors.clone(); let colors = theme.colors.clone();
let glyph = theme.icon_size(IconSize::Md); let glyph = theme.icon_size(IconSize::Md);
div() div()
.w_full()
.flex() .flex()
.flex_wrap() .flex_wrap()
.gap_2() .gap_2()
-1
View File
@@ -75,7 +75,6 @@ impl RenderOnce for StylePresetPicker {
let colors = theme.colors.clone(); let colors = theme.colors.clone();
let side = theme.generative().preset; let side = theme.generative().preset;
div() div()
.w_full()
.flex() .flex()
.flex_wrap() .flex_wrap()
.gap_3() .gap_3()
-1
View File
@@ -185,7 +185,6 @@ impl RenderOnce for FineTuneJobCard {
.h(theme.generative().loss) .h(theme.generative().loss)
}); });
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_3() .gap_3()
-1
View File
@@ -120,7 +120,6 @@ impl RenderOnce for VariationPicker {
}) })
}; };
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_3() .gap_3()
-2
View File
@@ -84,7 +84,6 @@ impl RenderOnce for PromptDiff {
Some((range, style)) Some((range, style))
}); });
div() div()
.w_full()
.text_size(theme.text_size(TextSize::Sm)) .text_size(theme.text_size(TextSize::Sm))
.text_color(colors.fg) .text_color(colors.fg)
.child(StyledText::new(text).with_highlights(styles)) .child(StyledText::new(text).with_highlights(styles))
@@ -251,7 +250,6 @@ impl RenderOnce for PromptVersionHistory {
}, },
); );
div() div()
.w_full()
.flex() .flex()
.flex_wrap() .flex_wrap()
.items_start() .items_start()
-1
View File
@@ -287,7 +287,6 @@ impl RenderOnce for VideoGenerationTimeline {
}) })
}); });
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_2() .gap_2()
+1 -1
View File
@@ -224,6 +224,6 @@ impl RenderOnce for TTSVoicePicker {
.children(voice.tags.into_iter().map(Badge::new)), .children(voice.tags.into_iter().map(Badge::new)),
) )
}); });
div().w_full().flex().flex_col().gap_0p5().children(rows) div().flex().flex_col().gap_0p5().children(rows)
} }
} }
-1
View File
@@ -87,7 +87,6 @@ impl RenderOnce for SimpleGrid {
let columns = columns_for(*width.read(cx), self.min, self.gap); let columns = columns_for(*width.read(cx), self.min, self.gap);
self.base self.base
.relative() .relative()
.w_full()
.grid() .grid()
.grid_cols(columns) .grid_cols(columns)
.gap(self.gap) .gap(self.gap)
+1 -1
View File
@@ -115,6 +115,6 @@ impl RenderOnce for Sections {
}), }),
) )
}); });
div().w_full().flex().flex_col().gap_4().children(sections) div().flex().flex_col().gap_4().children(sections)
} }
} }
+7 -10
View File
@@ -2,7 +2,7 @@ use std::rc::Rc;
use gpui::{ use gpui::{
App, ElementId, InteractiveElement, IntoElement, ParentElement, RenderOnce, SharedString, App, ElementId, InteractiveElement, IntoElement, ParentElement, RenderOnce, SharedString,
Styled, Window, div, Window, div,
}; };
use super::labels::hue_dot; use super::labels::hue_dot;
@@ -173,14 +173,11 @@ impl RenderOnce for MailboxList {
sections.section(title.clone(), rows) sections.section(title.clone(), rows)
}, },
); );
div() div().debug_selector(|| "mailbox-list".into()).child(
.debug_selector(|| "mailbox-list".into()) sections
.w_full() .selected(self.open)
.child( .on_select(open.clone())
sections .on_activate(open),
.selected(self.open) )
.on_select(open.clone())
.on_activate(open),
)
} }
} }
-1
View File
@@ -330,7 +330,6 @@ impl RenderOnce for MailComposer {
}); });
div() div()
.debug_selector(|| "mail-composer".into()) .debug_selector(|| "mail-composer".into())
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_3() .gap_3()
-1
View File
@@ -240,7 +240,6 @@ impl RenderOnce for LabelPicker {
let (up, down, chosen) = (finding.clone(), finding.clone(), finding); let (up, down, chosen) = (finding.clone(), finding.clone(), finding);
div() div()
.id(self.id.clone()) .id(self.id.clone())
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_2() .gap_2()
-2
View File
@@ -290,7 +290,6 @@ impl RenderOnce for MailList {
if self.mails.is_empty() { if self.mails.is_empty() {
return div() return div()
.debug_selector(|| "mail-none".into()) .debug_selector(|| "mail-none".into())
.w_full()
.px_3() .px_3()
.py_4() .py_4()
.text_size(theme.text_size(TextSize::Sm)) .text_size(theme.text_size(TextSize::Sm))
@@ -320,7 +319,6 @@ impl RenderOnce for MailList {
let (id, on_select) = (self.id.clone(), self.on_select); let (id, on_select) = (self.id.clone(), self.on_select);
div() div()
.debug_selector(|| "mail-list".into()) .debug_selector(|| "mail-list".into())
.w_full()
.child( .child(
list.selected(self.selected) list.selected(self.selected)
.on_change(move |keys, window, cx| { .on_change(move |keys, window, cx| {
-1
View File
@@ -344,7 +344,6 @@ impl RenderOnce for MailReader {
let theme = cx.theme(); let theme = cx.theme();
div() div()
.debug_selector(|| "mail-reader".into()) .debug_selector(|| "mail-reader".into())
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_4() .gap_4()
-1
View File
@@ -124,7 +124,6 @@ impl RenderOnce for SignatureEditor {
let words = input.read(cx).text().to_string(); let words = input.read(cx).text().to_string();
let theme = cx.theme(); let theme = cx.theme();
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_3() .gap_3()
-1
View File
@@ -199,7 +199,6 @@ impl RenderOnce for MailThreadView {
); );
div() div()
.debug_selector(|| "mail-thread".into()) .debug_selector(|| "mail-thread".into())
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_3() .gap_3()
-1
View File
@@ -428,7 +428,6 @@ impl RenderOnce for ImageAnnotator {
}) })
}); });
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_3() .gap_3()
-2
View File
@@ -145,7 +145,6 @@ impl RenderOnce for AudioWaveform {
.id(self.id.clone()) .id(self.id.clone())
.debug_selector(|| "audio-waveform".into()) .debug_selector(|| "audio-waveform".into())
.relative() .relative()
.w_full()
.h(sizes.waveform) .h(sizes.waveform)
.border_1() .border_1()
.border_color(transparent_black()) .border_color(transparent_black())
@@ -226,7 +225,6 @@ impl RenderOnce for AudioSpectrum {
let (color, round) = (theme.colors.fg_muted, theme.radius(Radius::Sm)); let (color, round) = (theme.colors.fg_muted, theme.radius(Radius::Sm));
div() div()
.debug_selector(|| "audio-spectrum".into()) .debug_selector(|| "audio-spectrum".into())
.w_full()
.h(theme.media().waveform) .h(theme.media().waveform)
.flex() .flex()
.items_end() .items_end()
-1
View File
@@ -92,7 +92,6 @@ impl RenderOnce for VolumeControl {
None => slider, None => slider,
}; };
div() div()
.w_full()
.flex() .flex()
.items_center() .items_center()
.gap_2() .gap_2()
-1
View File
@@ -200,7 +200,6 @@ impl RenderOnce for ImageCropper {
div() div()
.id(self.id.clone()) .id(self.id.clone())
.relative() .relative()
.w_full()
.p(side / 2.0) .p(side / 2.0)
.border_1() .border_1()
.border_color(transparent_black()) .border_color(transparent_black())
-1
View File
@@ -251,7 +251,6 @@ impl RenderOnce for AudioPlayer {
}; };
let left = format!("-{}", clock(self.length - self.at)); let left = format!("-{}", clock(self.length - self.at));
div() div()
.w_full()
.flex() .flex()
.flex_col() .flex_col()
.gap_3() .gap_3()
-1
View File
@@ -255,7 +255,6 @@ impl RenderOnce for MicLevelMeter {
let (unlit, round) = (colors.border, theme.radius(Radius::Sm)); let (unlit, round) = (colors.border, theme.radius(Radius::Sm));
div() div()
.debug_selector(|| "mic-level".into()) .debug_selector(|| "mic-level".into())
.w_full()
.h(theme.meter_track()) .h(theme.meter_track())
.flex() .flex()
.gap_0p5() .gap_0p5()
-1
View File
@@ -264,7 +264,6 @@ impl RenderOnce for Scrubber {
div() div()
.id(self.id.clone()) .id(self.id.clone())
.debug_selector(|| "scrubber".into()) .debug_selector(|| "scrubber".into())
.w_full()
.px(thumb / 2.0) .px(thumb / 2.0)
.rounded(theme.radius(Radius::Sm)) .rounded(theme.radius(Radius::Sm))
.border_1() .border_1()
-1
View File
@@ -251,7 +251,6 @@ impl RenderOnce for VideoThumbnailStrip {
let (pressed_strip, moved_strip) = (strip.clone(), strip.clone()); let (pressed_strip, moved_strip) = (strip.clone(), strip.clone());
div() div()
.id(self.id.clone()) .id(self.id.clone())
.w_full()
.px(handle / 2.0) .px(handle / 2.0)
.border_1() .border_1()
.border_color(transparent_black()) .border_color(transparent_black())
Loaded 100 of 126 files, more files were not shown because too many files have changed in this diff. Show more