From 9af14a494b99d9ac38a806d14ad9251d615b3a5d Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 9 Jul 2026 23:38:27 -0700 Subject: [PATCH 1/2] gpui: Fill auto-sized window roots to the viewport Window roots with an auto size now stretch to fill the window, the way the root element on the web fills the initial containing block. Explicitly styled root dimensions are preserved, and tooltips, drags, and deferred draws keep their shrink-wrap semantics. --- crates/gpui/src/taffy.rs | 32 ++++++++++++++ crates/gpui/src/window.rs | 91 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 122 insertions(+), 1 deletion(-) diff --git a/crates/gpui/src/taffy.rs b/crates/gpui/src/taffy.rs index 4844748d6c767d..8db3bbf9da7bfc 100644 --- a/crates/gpui/src/taffy.rs +++ b/crates/gpui/src/taffy.rs @@ -111,6 +111,38 @@ impl TaffyLayoutEngine { .into() } + /// Treats any `auto` dimension of the given node's style as filling `size`. + /// + /// This is applied to window roots before layout so they behave like the + /// root element on the web, which stretches to fill the initial containing + /// block (the viewport) unless given an explicit size. Explicitly styled + /// dimensions are preserved. + pub fn stretch_auto_size_to_fill( + &mut self, + id: LayoutId, + size: Size, + scale_factor: f32, + ) { + let style = self.taffy.style(id.0).expect(EXPECT_MESSAGE); + let stretch_width = style.size.width.is_auto(); + let stretch_height = style.size.height.is_auto(); + if !stretch_width && !stretch_height { + return; + } + let mut style = style.clone(); + if stretch_width { + style.size.width = + taffy::style::Dimension::length(round_to_device_pixel(size.width.0, scale_factor)); + } + if stretch_height { + style.size.height = taffy::style::Dimension::length(round_to_device_pixel( + size.height.0, + scale_factor, + )); + } + self.taffy.set_style(id.0, style).expect(EXPECT_MESSAGE); + } + // Used to understand performance #[allow(dead_code)] fn count_all_children(&self, parent: LayoutId) -> anyhow::Result { diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 7059cf863a60ff..212fa8b4d3a5f2 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -2823,8 +2823,17 @@ impl Window { } }; - // Layout all root elements. + // Layout all root elements. Like the root element on the web, which + // stretches to fill the viewport unless explicitly sized, window roots + // fill the window when their size is `auto`. + let scale_factor = self.scale_factor(); let mut root_element = self.root.as_ref().unwrap().clone().into_any_element(); + let root_layout_id = root_element.request_layout(self, cx); + self.layout_engine.as_mut().unwrap().stretch_auto_size_to_fill( + root_layout_id, + root_size, + scale_factor, + ); root_element.prepaint_as_root(Point::default(), root_size.into(), self, cx); #[cfg(any(feature = "inspector", debug_assertions))] @@ -2837,6 +2846,12 @@ impl Window { let mut tooltip_element = None; if let Some(prompt) = self.prompt.take() { let mut element = prompt.view.any_view().into_any_element(); + let prompt_layout_id = element.request_layout(self, cx); + self.layout_engine.as_mut().unwrap().stretch_auto_size_to_fill( + prompt_layout_id, + root_size, + scale_factor, + ); element.prepaint_as_root(Point::default(), root_size.into(), self, cx); prompt_element = Some(element); self.prompt = Some(prompt); @@ -6312,3 +6327,77 @@ pub fn outline( border_style, } } + +#[cfg(test)] +mod tests { + use crate::{ + AppContext as _, Bounds, Context, IntoElement, ParentElement as _, Pixels, Render, + Styled as _, TestAppContext, Window, canvas, div, px, size, + }; + use std::{cell::Cell, rc::Rc}; + + struct RootView { + explicit_size: bool, + child_bounds: Rc>>, + } + + impl Render for RootView { + fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { + let child_bounds = self.child_bounds.clone(); + let root = div().flex().flex_col().child( + canvas( + move |bounds, _, _| child_bounds.set(bounds), + |_, _, _, _| {}, + ) + .size_full(), + ); + if self.explicit_size { + root.w(px(300.)).h(px(200.)) + } else { + root + } + } + } + + #[test] + fn auto_sized_window_root_fills_the_window() { + let mut cx = TestAppContext::single(); + let child_bounds = Rc::new(Cell::new(Bounds::default())); + let window = cx.add_window({ + let child_bounds = child_bounds.clone(); + move |_, _| RootView { + explicit_size: false, + child_bounds, + } + }); + + let viewport_size = cx + .update_window(window.into(), |_, window, cx| { + window.draw(cx).clear(); + window.viewport_size() + }) + .unwrap(); + + assert_eq!(child_bounds.get().size, viewport_size); + } + + #[test] + fn explicitly_sized_window_root_keeps_its_size() { + let mut cx = TestAppContext::single(); + let child_bounds = Rc::new(Cell::new(Bounds::default())); + let window = cx.add_window({ + let child_bounds = child_bounds.clone(); + move |_, _| RootView { + explicit_size: true, + child_bounds, + } + }); + + cx.update_window(window.into(), |_, window, cx| { + window.draw(cx).clear(); + }) + .unwrap(); + + assert_eq!(child_bounds.get().size, size(px(300.), px(200.))); + } +} From 8104d469ad5d348487fc3e48af58e6156b78aae5 Mon Sep 17 00:00:00 2001 From: "zed-zippy[bot]" <234243425+zed-zippy[bot]@users.noreply.github.com> Date: Fri, 10 Jul 2026 06:53:15 +0000 Subject: [PATCH 2/2] Autofix --- crates/gpui/src/taffy.rs | 6 ++---- crates/gpui/src/window.rs | 18 ++++++++---------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/crates/gpui/src/taffy.rs b/crates/gpui/src/taffy.rs index 8db3bbf9da7bfc..eb3c391dc5e2b4 100644 --- a/crates/gpui/src/taffy.rs +++ b/crates/gpui/src/taffy.rs @@ -135,10 +135,8 @@ impl TaffyLayoutEngine { taffy::style::Dimension::length(round_to_device_pixel(size.width.0, scale_factor)); } if stretch_height { - style.size.height = taffy::style::Dimension::length(round_to_device_pixel( - size.height.0, - scale_factor, - )); + style.size.height = + taffy::style::Dimension::length(round_to_device_pixel(size.height.0, scale_factor)); } self.taffy.set_style(id.0, style).expect(EXPECT_MESSAGE); } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 212fa8b4d3a5f2..f65a696a8e93a3 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -2829,11 +2829,10 @@ impl Window { let scale_factor = self.scale_factor(); let mut root_element = self.root.as_ref().unwrap().clone().into_any_element(); let root_layout_id = root_element.request_layout(self, cx); - self.layout_engine.as_mut().unwrap().stretch_auto_size_to_fill( - root_layout_id, - root_size, - scale_factor, - ); + self.layout_engine + .as_mut() + .unwrap() + .stretch_auto_size_to_fill(root_layout_id, root_size, scale_factor); root_element.prepaint_as_root(Point::default(), root_size.into(), self, cx); #[cfg(any(feature = "inspector", debug_assertions))] @@ -2847,11 +2846,10 @@ impl Window { if let Some(prompt) = self.prompt.take() { let mut element = prompt.view.any_view().into_any_element(); let prompt_layout_id = element.request_layout(self, cx); - self.layout_engine.as_mut().unwrap().stretch_auto_size_to_fill( - prompt_layout_id, - root_size, - scale_factor, - ); + self.layout_engine + .as_mut() + .unwrap() + .stretch_auto_size_to_fill(prompt_layout_id, root_size, scale_factor); element.prepaint_as_root(Point::default(), root_size.into(), self, cx); prompt_element = Some(element); self.prompt = Some(prompt);