From 4ba9259890fb3f430fbff5c08a1e52a086ab1641 Mon Sep 17 00:00:00 2001 From: Anthony Date: Mon, 18 Aug 2025 04:03:03 -0400 Subject: [PATCH] Start work to add container queries to gpui --- crates/gpui/examples/grid_layout.rs | 11 ++-- crates/gpui/src/elements/div.rs | 78 ++++++++++++++++++++++++++++- crates/gpui/src/style.rs | 4 ++ crates/gpui/src/styled.rs | 23 +++++++++ 4 files changed, 112 insertions(+), 4 deletions(-) diff --git a/crates/gpui/examples/grid_layout.rs b/crates/gpui/examples/grid_layout.rs index f285497578..63db5c59e9 100644 --- a/crates/gpui/examples/grid_layout.rs +++ b/crates/gpui/examples/grid_layout.rs @@ -21,6 +21,7 @@ impl Render for HolyGrailExample { div() .gap_1() + .id("Random") .grid() .bg(rgb(0x505050)) .size(px(500.0)) @@ -37,7 +38,9 @@ impl Render for HolyGrailExample { ) .child( block(gpui::red()) - .col_span(1) + .id("Table of contents") + .row_span(1) + .container_3xs(|style| style.col_span(1).row_span_auto()) .h_56() .child("Table of contents"), ) @@ -49,8 +52,10 @@ impl Render for HolyGrailExample { ) .child( block(gpui::blue()) - .col_span(1) - .row_span(3) + .id("Ads") + .col_span(3) + .row_span(1) + .container_3xs(|style| style.col_span(1).row_span(3)) .child("AD :(") .text_color(gpui::white()), ) diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index 09afbff929..c581dc9f10 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -648,6 +648,61 @@ pub trait InteractiveElement: Sized { self } + /// Apply the given style when container width is is less than or equal to 256.0 Pixels + fn container_3xs(mut self, f: impl Fn(StyleRefinement) -> StyleRefinement + 'static) -> Self { + self.container_query_with(256.0, f) + } + + /// Apply the given style when container width is is less than or equal to 288.0 Pixels + fn container_2xs(mut self, f: impl Fn(StyleRefinement) -> StyleRefinement + 'static) -> Self { + self.container_query_with(288.0, f) + } + + /// Apply the given style when container width is is less than or equal to 320.0 Pixels + fn container_xs(mut self, f: impl Fn(StyleRefinement) -> StyleRefinement + 'static) -> Self { + self.container_query_with(320.0, f) + } + + /// Apply the given style when container width is is less than or equal to 384.0 Pixels + fn container_sm(mut self, f: impl Fn(StyleRefinement) -> StyleRefinement + 'static) -> Self { + self.container_query_with(384.0, f) + } + + /// Apply the given style when container width is is less than or equal to 448.0 Pixels + fn container_md(mut self, f: impl Fn(StyleRefinement) -> StyleRefinement + 'static) -> Self { + self.container_query_with(448.0, f) + } + + /// Apply the given style when container width is is less than or equal to 512.0 Pixels + fn container_lg(mut self, f: impl Fn(StyleRefinement) -> StyleRefinement + 'static) -> Self { + self.container_query_with(512.0, f) + } + + /// Apply the given style when container width is is less than or equal to 576.0 Pixels + fn container_xl(mut self, f: impl Fn(StyleRefinement) -> StyleRefinement + 'static) -> Self { + self.container_query_with(576.0, f) + } + + /// Apply the given style when width is less than or equal to the current value + fn container_query_with( + mut self, + width: impl Into, + f: impl Fn(StyleRefinement) -> StyleRefinement + 'static, + ) -> Self { + let width = width.into(); + + self.interactivity() + .container_queries + .push(Box::new(move |layout_width, style| { + if layout_width >= width { + f(style) + } else { + style + } + })); + self + } + /// Apply the given style to this element when the mouse hovers over a group member fn group_hover( mut self, @@ -1464,6 +1519,7 @@ pub struct Interactivity { pub(crate) click_listeners: Vec, pub(crate) drag_listener: Option<(Arc, DragListener)>, pub(crate) hover_listener: Option>, + pub(crate) container_queries: Vec StyleRefinement>>, pub(crate) tooltip_builder: Option, pub(crate) window_control: Option, pub(crate) hitbox_behavior: HitboxBehavior, @@ -1556,7 +1612,22 @@ impl Interactivity { } } - let style = self.compute_style_internal(None, element_state.as_mut(), window, cx); + let mut style = + self.compute_style_internal(None, element_state.as_mut(), window, cx); + + let mut style_refinement = StyleRefinement::default(); + + if let Some(element_state) = element_state.as_ref() + && let Some(bounds) = element_state.cached_bounds + { + let current_width = bounds.size.width; + + for query in self.container_queries.iter() { + style_refinement = query(current_width, style_refinement); + } + + style.refine(&style_refinement); + } let layout_id = f(style, window, cx); (layout_id, element_state) }, @@ -1738,6 +1809,10 @@ impl Interactivity { let mut element_state = element_state.map(|element_state| element_state.unwrap_or_default()); + if let Some(element_state) = element_state.as_mut() { + element_state.cached_bounds = Some(bounds); + } + let style = self.compute_style_internal(hitbox, element_state.as_mut(), window, cx); #[cfg(any(feature = "test-support", test))] @@ -2517,6 +2592,7 @@ pub struct InteractiveElementState { pub(crate) pending_mouse_down: Option>>>, pub(crate) scroll_offset: Option>>>, pub(crate) active_tooltip: Option>>>, + pub(crate) cached_bounds: Option>, } /// Whether or not the element or a group that contains it is clicked by the mouse. diff --git a/crates/gpui/src/style.rs b/crates/gpui/src/style.rs index 09985722ef..869ecc0468 100644 --- a/crates/gpui/src/style.rs +++ b/crates/gpui/src/style.rs @@ -271,6 +271,9 @@ pub struct Style { /// The grid location of this element pub grid_location: Option, + /// The container name (used for container queries) + pub container_name: Option, + /// Whether to draw a red debugging outline around this element #[cfg(debug_assertions)] pub debug: bool, @@ -778,6 +781,7 @@ impl Default for Style { grid_rows: None, grid_cols: None, grid_location: None, + container_name: None, #[cfg(debug_assertions)] debug: false, diff --git a/crates/gpui/src/styled.rs b/crates/gpui/src/styled.rs index c714cac14f..96db82f458 100644 --- a/crates/gpui/src/styled.rs +++ b/crates/gpui/src/styled.rs @@ -701,6 +701,13 @@ pub trait Styled: Sized { self } + /// Sets the column span of this element to auto. + fn col_span_auto(mut self) -> Self { + let grid_location = self.style().grid_location_mut(); + grid_location.column = GridPlacement::Auto..GridPlacement::Auto; + self + } + /// Sets the row start of this element. fn row_start(mut self, start: i16) -> Self { let grid_location = self.style().grid_location_mut(); @@ -722,6 +729,13 @@ pub trait Styled: Sized { self } + /// Sets the row span to auto + fn row_span_auto(mut self) -> Self { + let grid_location = self.style().grid_location_mut(); + grid_location.row = GridPlacement::Auto..GridPlacement::Auto; + self + } + /// Sets the row end of this element to "auto" fn row_end_auto(mut self) -> Self { let grid_location = self.style().grid_location_mut(); @@ -743,6 +757,15 @@ pub trait Styled: Sized { self } + /// Sets the container name of this element. + fn container_name(mut self, name: SharedString) -> Self { + let container_name = &mut self.style().container_name; + if container_name.is_none() { + *container_name = Some(name); + } // todo! Add a debug assert here so there can only be one container name per element + self + } + /// Draws a debug border around this element. #[cfg(debug_assertions)] fn debug(mut self) -> Self {