diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index b571c0afd2..4f038aa2cc 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -491,14 +491,11 @@ pub struct Window { sprite_atlas: Arc, text_system: Arc, rem_size: Pixels, - /// An override value for the window's rem size. + /// The stack of override values for the window's rem size. /// /// This is used by `with_rem_size` to allow rendering an element tree with /// a given rem size. - /// - /// Note: Right now we only allow for a single override value at a time, but - /// this could likely be changed to be a stack of rem sizes. - rem_size_override: Option, + rem_size_override_stack: SmallVec<[Pixels; 8]>, pub(crate) viewport_size: Size, layout_engine: Option, pub(crate) root_view: Option, @@ -771,7 +768,7 @@ impl Window { sprite_atlas, text_system, rem_size: px(16.), - rem_size_override: None, + rem_size_override_stack: SmallVec::new(), viewport_size: content_size, layout_engine: Some(TaffyLayoutEngine::new()), root_view: None, @@ -1212,7 +1209,9 @@ impl<'a> WindowContext<'a> { /// UI to scale, just like zooming a web page. pub fn rem_size(&self) -> Pixels { self.window - .rem_size_override + .rem_size_override_stack + .last() + .copied() .unwrap_or(self.window.rem_size) } @@ -1238,9 +1237,9 @@ impl<'a> WindowContext<'a> { ); if let Some(rem_size) = rem_size { - self.window.rem_size_override = Some(rem_size.into()); + self.window.rem_size_override_stack.push(rem_size.into()); let result = f(self); - self.window.rem_size_override.take(); + self.window.rem_size_override_stack.pop(); result } else { f(self) diff --git a/crates/storybook/src/stories.rs b/crates/storybook/src/stories.rs index 7777af2aa3..b824235b00 100644 --- a/crates/storybook/src/stories.rs +++ b/crates/storybook/src/stories.rs @@ -7,6 +7,7 @@ mod picker; mod scroll; mod text; mod viewport_units; +mod with_rem_size; pub use auto_height_editor::*; pub use cursor::*; @@ -17,3 +18,4 @@ pub use picker::*; pub use scroll::*; pub use text::*; pub use viewport_units::*; +pub use with_rem_size::*; diff --git a/crates/storybook/src/stories/with_rem_size.rs b/crates/storybook/src/stories/with_rem_size.rs new file mode 100644 index 0000000000..11add24955 --- /dev/null +++ b/crates/storybook/src/stories/with_rem_size.rs @@ -0,0 +1,61 @@ +use gpui::{AnyElement, Hsla, Render}; +use story::Story; + +use ui::{prelude::*, WithRemSize}; + +pub struct WithRemSizeStory; + +impl Render for WithRemSizeStory { + fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { + Story::container().child( + Example::new(16., gpui::red()) + .child( + Example::new(24., gpui::green()) + .child(Example::new(8., gpui::blue())) + .child(Example::new(16., gpui::yellow())), + ) + .child( + Example::new(12., gpui::green()) + .child(Example::new(48., gpui::blue())) + .child(Example::new(16., gpui::yellow())), + ), + ) + } +} + +#[derive(IntoElement)] +struct Example { + rem_size: Pixels, + border_color: Hsla, + children: Vec, +} + +impl Example { + pub fn new(rem_size: impl Into, border_color: Hsla) -> Self { + Self { + rem_size: rem_size.into(), + border_color, + children: Vec::new(), + } + } +} + +impl ParentElement for Example { + fn extend(&mut self, elements: impl IntoIterator) { + self.children.extend(elements); + } +} + +impl RenderOnce for Example { + fn render(self, _cx: &mut WindowContext) -> impl IntoElement { + WithRemSize::new(self.rem_size).child( + v_flex() + .gap_2() + .p_2() + .border_2() + .border_color(self.border_color) + .child(Label::new(format!("1rem = {}px", self.rem_size.0))) + .children(self.children), + ) + } +} diff --git a/crates/storybook/src/story_selector.rs b/crates/storybook/src/story_selector.rs index 9720522430..9d2c030847 100644 --- a/crates/storybook/src/story_selector.rs +++ b/crates/storybook/src/story_selector.rs @@ -40,6 +40,7 @@ pub enum ComponentStory { ToggleButton, ToolStrip, ViewportUnits, + WithRemSize, } impl ComponentStory { @@ -76,6 +77,7 @@ impl ComponentStory { Self::ToggleButton => cx.new_view(|_| ui::ToggleButtonStory).into(), Self::ToolStrip => cx.new_view(|_| ui::ToolStripStory).into(), Self::ViewportUnits => cx.new_view(|_| crate::stories::ViewportUnitsStory).into(), + Self::WithRemSize => cx.new_view(|_| crate::stories::WithRemSizeStory).into(), Self::Picker => PickerStory::new(cx).into(), } } diff --git a/crates/ui/src/ui.rs b/crates/ui/src/ui.rs index e8ee51818e..5ba501fd6a 100644 --- a/crates/ui/src/ui.rs +++ b/crates/ui/src/ui.rs @@ -13,12 +13,13 @@ mod styled_ext; mod styles; pub mod utils; mod visible_on_hover; +mod with_rem_size; pub use clickable::*; pub use components::*; pub use disableable::*; pub use fixed::*; pub use prelude::*; - pub use styled_ext::*; pub use styles::*; +pub use with_rem_size::*; diff --git a/crates/ui/src/with_rem_size.rs b/crates/ui/src/with_rem_size.rs new file mode 100644 index 0000000000..a63588bf9d --- /dev/null +++ b/crates/ui/src/with_rem_size.rs @@ -0,0 +1,75 @@ +use gpui::{ + div, AnyElement, Bounds, Div, DivFrameState, Element, ElementId, GlobalElementId, Hitbox, + IntoElement, LayoutId, ParentElement, Pixels, WindowContext, +}; + +/// An element that sets a particular rem size for its children. +pub struct WithRemSize { + div: Div, + rem_size: Pixels, +} + +impl WithRemSize { + pub fn new(rem_size: impl Into) -> Self { + Self { + div: div(), + rem_size: rem_size.into(), + } + } +} + +impl ParentElement for WithRemSize { + fn extend(&mut self, elements: impl IntoIterator) { + self.div.extend(elements) + } +} + +impl Element for WithRemSize { + type RequestLayoutState = DivFrameState; + type PrepaintState = Option; + + fn id(&self) -> Option { + self.div.id() + } + + fn request_layout( + &mut self, + id: Option<&GlobalElementId>, + cx: &mut WindowContext, + ) -> (LayoutId, Self::RequestLayoutState) { + cx.with_rem_size(Some(self.rem_size), |cx| self.div.request_layout(id, cx)) + } + + fn prepaint( + &mut self, + id: Option<&GlobalElementId>, + bounds: Bounds, + request_layout: &mut Self::RequestLayoutState, + cx: &mut WindowContext, + ) -> Self::PrepaintState { + cx.with_rem_size(Some(self.rem_size), |cx| { + self.div.prepaint(id, bounds, request_layout, cx) + }) + } + + fn paint( + &mut self, + id: Option<&GlobalElementId>, + bounds: Bounds, + request_layout: &mut Self::RequestLayoutState, + prepaint: &mut Self::PrepaintState, + cx: &mut WindowContext, + ) { + cx.with_rem_size(Some(self.rem_size), |cx| { + self.div.paint(id, bounds, request_layout, prepaint, cx) + }) + } +} + +impl IntoElement for WithRemSize { + type Element = Self; + + fn into_element(self) -> Self::Element { + self + } +}