diff --git a/crates/gpui/src/taffy.rs b/crates/gpui/src/taffy.rs index 4844748d6c767d..eb3c391dc5e2b4 100644 --- a/crates/gpui/src/taffy.rs +++ b/crates/gpui/src/taffy.rs @@ -111,6 +111,36 @@ 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..f65a696a8e93a3 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -2823,8 +2823,16 @@ 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 +2845,11 @@ 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 +6325,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.))); + } +}