Extract {Container,Label}Style structs from those elements

Max Brunsfeld and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

gpui/src/elements/container.rs | 118 ++++++++++++++++-------------
gpui/src/elements/label.rs     | 141 +++++++++++++++++------------------
zed/src/file_finder.rs         |  22 ++---
zed/src/theme_selector.rs      |  18 ++--
4 files changed, 155 insertions(+), 144 deletions(-)

Detailed changes

gpui/src/elements/container.rs 🔗

@@ -10,53 +10,58 @@ use crate::{
     SizeConstraint,
 };
 
-pub struct Container {
+#[derive(Clone, Debug, Default)]
+pub struct ContainerStyle {
     margin: Margin,
     padding: Padding,
     background_color: Option<ColorU>,
     border: Border,
     corner_radius: f32,
     shadow: Option<Shadow>,
+}
+
+pub struct Container {
     child: ElementBox,
+    style: ContainerStyle,
 }
 
 impl Container {
     pub fn new(child: ElementBox) -> Self {
         Self {
-            margin: Margin::default(),
-            padding: Padding::default(),
-            background_color: None,
-            border: Border::default(),
-            corner_radius: 0.0,
-            shadow: None,
             child,
+            style: Default::default(),
         }
     }
 
+    pub fn with_style(mut self, style: &ContainerStyle) -> Self {
+        self.style = style.clone();
+        self
+    }
+
     pub fn with_margin_top(mut self, margin: f32) -> Self {
-        self.margin.top = margin;
+        self.style.margin.top = margin;
         self
     }
 
     pub fn with_margin_left(mut self, margin: f32) -> Self {
-        self.margin.left = margin;
+        self.style.margin.left = margin;
         self
     }
 
     pub fn with_horizontal_padding(mut self, padding: f32) -> Self {
-        self.padding.left = padding;
-        self.padding.right = padding;
+        self.style.padding.left = padding;
+        self.style.padding.right = padding;
         self
     }
 
     pub fn with_vertical_padding(mut self, padding: f32) -> Self {
-        self.padding.top = padding;
-        self.padding.bottom = padding;
+        self.style.padding.top = padding;
+        self.style.padding.bottom = padding;
         self
     }
 
     pub fn with_uniform_padding(mut self, padding: f32) -> Self {
-        self.padding = Padding {
+        self.style.padding = Padding {
             top: padding,
             left: padding,
             bottom: padding,
@@ -66,32 +71,32 @@ impl Container {
     }
 
     pub fn with_padding_right(mut self, padding: f32) -> Self {
-        self.padding.right = padding;
+        self.style.padding.right = padding;
         self
     }
 
     pub fn with_padding_bottom(mut self, padding: f32) -> Self {
-        self.padding.bottom = padding;
+        self.style.padding.bottom = padding;
         self
     }
 
     pub fn with_background_color(mut self, color: impl Into<ColorU>) -> Self {
-        self.background_color = Some(color.into());
+        self.style.background_color = Some(color.into());
         self
     }
 
     pub fn with_border(mut self, border: Border) -> Self {
-        self.border = border;
+        self.style.border = border;
         self
     }
 
     pub fn with_corner_radius(mut self, radius: f32) -> Self {
-        self.corner_radius = radius;
+        self.style.corner_radius = radius;
         self
     }
 
     pub fn with_shadow(mut self, offset: Vector2F, blur: f32, color: impl Into<ColorU>) -> Self {
-        self.shadow = Some(Shadow {
+        self.style.shadow = Some(Shadow {
             offset,
             blur,
             color: color.into(),
@@ -101,33 +106,33 @@ impl Container {
 
     fn margin_size(&self) -> Vector2F {
         vec2f(
-            self.margin.left + self.margin.right,
-            self.margin.top + self.margin.bottom,
+            self.style.margin.left + self.style.margin.right,
+            self.style.margin.top + self.style.margin.bottom,
         )
     }
 
     fn padding_size(&self) -> Vector2F {
         vec2f(
-            self.padding.left + self.padding.right,
-            self.padding.top + self.padding.bottom,
+            self.style.padding.left + self.style.padding.right,
+            self.style.padding.top + self.style.padding.bottom,
         )
     }
 
     fn border_size(&self) -> Vector2F {
         let mut x = 0.0;
-        if self.border.left {
-            x += self.border.width;
+        if self.style.border.left {
+            x += self.style.border.width;
         }
-        if self.border.right {
-            x += self.border.width;
+        if self.style.border.right {
+            x += self.style.border.width;
         }
 
         let mut y = 0.0;
-        if self.border.top {
-            y += self.border.width;
+        if self.style.border.top {
+            y += self.style.border.width;
         }
-        if self.border.bottom {
-            y += self.border.width;
+        if self.style.border.bottom {
+            y += self.style.border.width;
         }
 
         vec2f(x, y)
@@ -168,28 +173,31 @@ impl Element for Container {
         cx: &mut PaintContext,
     ) -> Self::PaintState {
         let quad_bounds = RectF::from_points(
-            bounds.origin() + vec2f(self.margin.left, self.margin.top),
-            bounds.lower_right() - vec2f(self.margin.right, self.margin.bottom),
+            bounds.origin() + vec2f(self.style.margin.left, self.style.margin.top),
+            bounds.lower_right() - vec2f(self.style.margin.right, self.style.margin.bottom),
         );
 
-        if let Some(shadow) = self.shadow.as_ref() {
+        if let Some(shadow) = self.style.shadow.as_ref() {
             cx.scene.push_shadow(scene::Shadow {
                 bounds: quad_bounds + shadow.offset,
-                corner_radius: self.corner_radius,
+                corner_radius: self.style.corner_radius,
                 sigma: shadow.blur,
                 color: shadow.color,
             });
         }
         cx.scene.push_quad(Quad {
             bounds: quad_bounds,
-            background: self.background_color,
-            border: self.border,
-            corner_radius: self.corner_radius,
+            background: self.style.background_color,
+            border: self.style.border,
+            corner_radius: self.style.corner_radius,
         });
 
         let child_origin = quad_bounds.origin()
-            + vec2f(self.padding.left, self.padding.top)
-            + vec2f(self.border.left_width(), self.border.top_width());
+            + vec2f(self.style.padding.left, self.style.padding.top)
+            + vec2f(
+                self.style.border.left_width(),
+                self.style.border.top_width(),
+            );
         self.child.paint(child_origin, cx);
     }
 
@@ -214,20 +222,26 @@ impl Element for Container {
         json!({
             "type": "Container",
             "bounds": bounds.to_json(),
-            "details": {
-                "margin": self.margin.to_json(),
-                "padding": self.padding.to_json(),
-                "background_color": self.background_color.to_json(),
-                "border": self.border.to_json(),
-                "corner_radius": self.corner_radius,
-                "shadow": self.shadow.to_json(),
-            },
+            "details": self.style.to_json(),
             "child": self.child.debug(cx),
         })
     }
 }
 
-#[derive(Default)]
+impl ToJson for ContainerStyle {
+    fn to_json(&self) -> serde_json::Value {
+        json!({
+            "margin": self.margin.to_json(),
+            "padding": self.padding.to_json(),
+            "background_color": self.background_color.to_json(),
+            "border": self.border.to_json(),
+            "corner_radius": self.corner_radius,
+            "shadow": self.shadow.to_json(),
+        })
+    }
+}
+
+#[derive(Clone, Debug, Default)]
 pub struct Margin {
     top: f32,
     left: f32,
@@ -254,7 +268,7 @@ impl ToJson for Margin {
     }
 }
 
-#[derive(Default)]
+#[derive(Clone, Debug, Default)]
 pub struct Padding {
     top: f32,
     left: f32,
@@ -281,7 +295,7 @@ impl ToJson for Padding {
     }
 }
 
-#[derive(Default)]
+#[derive(Clone, Debug, Default)]
 pub struct Shadow {
     offset: Vector2F,
     blur: f32,

gpui/src/elements/label.rs 🔗

@@ -18,16 +18,17 @@ use crate::{
 pub struct Label {
     text: String,
     family_id: FamilyId,
-    font_properties: Properties,
     font_size: f32,
-    default_color: ColorU,
-    highlights: Option<Highlights>,
+    style: LabelStyle,
+    highlight_indices: Vec<usize>,
 }
 
-pub struct Highlights {
-    color: ColorU,
-    indices: Vec<usize>,
-    font_properties: Properties,
+#[derive(Clone, Debug, Default)]
+pub struct LabelStyle {
+    pub default_color: ColorU,
+    pub highlight_color: ColorU,
+    pub font_properties: Properties,
+    pub highlight_font_properties: Properties,
 }
 
 impl Label {
@@ -35,29 +36,24 @@ impl Label {
         Self {
             text,
             family_id,
-            font_properties: Properties::new(),
             font_size,
-            default_color: ColorU::black(),
-            highlights: None,
+            highlight_indices: Default::default(),
+            style: Default::default(),
         }
     }
 
+    pub fn with_style(mut self, style: &LabelStyle) -> Self {
+        self.style = style.clone();
+        self
+    }
+
     pub fn with_default_color(mut self, color: ColorU) -> Self {
-        self.default_color = color;
+        self.style.default_color = color;
         self
     }
 
-    pub fn with_highlights(
-        mut self,
-        color: ColorU,
-        font_properties: Properties,
-        indices: Vec<usize>,
-    ) -> Self {
-        self.highlights = Some(Highlights {
-            color,
-            font_properties,
-            indices,
-        });
+    pub fn with_highlights(mut self, indices: Vec<usize>) -> Self {
+        self.highlight_indices = indices;
         self
     }
 
@@ -66,46 +62,45 @@ impl Label {
         font_cache: &FontCache,
         font_id: FontId,
     ) -> SmallVec<[(usize, FontId, ColorU); 8]> {
-        if let Some(highlights) = self.highlights.as_ref() {
-            let highlight_font_id = font_cache
-                .select_font(self.family_id, &highlights.font_properties)
-                .unwrap_or(font_id);
-
-            let mut highlight_indices = highlights.indices.iter().copied().peekable();
-            let mut runs = SmallVec::new();
-
-            for (char_ix, c) in self.text.char_indices() {
-                let mut font_id = font_id;
-                let mut color = self.default_color;
-                if let Some(highlight_ix) = highlight_indices.peek() {
-                    if char_ix == *highlight_ix {
-                        font_id = highlight_font_id;
-                        color = highlights.color;
-                        highlight_indices.next();
-                    }
-                }
+        if self.highlight_indices.is_empty() {
+            return smallvec![(self.text.len(), font_id, self.style.default_color)];
+        }
 
-                let push_new_run =
-                    if let Some((last_len, last_font_id, last_color)) = runs.last_mut() {
-                        if font_id == *last_font_id && color == *last_color {
-                            *last_len += c.len_utf8();
-                            false
-                        } else {
-                            true
-                        }
-                    } else {
-                        true
-                    };
-
-                if push_new_run {
-                    runs.push((c.len_utf8(), font_id, color));
+        let highlight_font_id = font_cache
+            .select_font(self.family_id, &self.style.highlight_font_properties)
+            .unwrap_or(font_id);
+
+        let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
+        let mut runs = SmallVec::new();
+
+        for (char_ix, c) in self.text.char_indices() {
+            let mut font_id = font_id;
+            let mut color = self.style.default_color;
+            if let Some(highlight_ix) = highlight_indices.peek() {
+                if char_ix == *highlight_ix {
+                    font_id = highlight_font_id;
+                    color = self.style.highlight_color;
+                    highlight_indices.next();
                 }
             }
 
-            runs
-        } else {
-            smallvec![(self.text.len(), font_id, self.default_color)]
+            let push_new_run = if let Some((last_len, last_font_id, last_color)) = runs.last_mut() {
+                if font_id == *last_font_id && color == *last_color {
+                    *last_len += c.len_utf8();
+                    false
+                } else {
+                    true
+                }
+            } else {
+                true
+            };
+
+            if push_new_run {
+                runs.push((c.len_utf8(), font_id, color));
+            }
         }
+
+        runs
     }
 }
 
@@ -120,7 +115,7 @@ impl Element for Label {
     ) -> (Vector2F, Self::LayoutState) {
         let font_id = cx
             .font_cache
-            .select_font(self.family_id, &self.font_properties)
+            .select_font(self.family_id, &self.style.font_properties)
             .unwrap();
         let runs = self.compute_runs(&cx.font_cache, font_id);
         let line =
@@ -172,21 +167,22 @@ impl Element for Label {
         json!({
             "type": "Label",
             "bounds": bounds.to_json(),
+            "text": &self.text,
+            "highlight_indices": self.highlight_indices,
             "font_family": cx.font_cache.family_name(self.family_id).unwrap(),
             "font_size": self.font_size,
-            "font_properties": self.font_properties.to_json(),
-            "text": &self.text,
-            "highlights": self.highlights.to_json(),
+            "style": self.style.to_json(),
         })
     }
 }
 
-impl ToJson for Highlights {
+impl ToJson for LabelStyle {
     fn to_json(&self) -> Value {
         json!({
-            "color": self.color.to_json(),
-            "indices": self.indices,
-            "font_properties": self.font_properties.to_json(),
+            "default_color": self.default_color.to_json(),
+            "default_font_properties": self.font_properties.to_json(),
+            "highlight_color": self.highlight_color.to_json(),
+            "highlight_font_properties": self.highlight_font_properties.to_json(),
         })
     }
 }
@@ -211,17 +207,20 @@ mod tests {
         let black = ColorU::black();
         let red = ColorU::new(255, 0, 0, 255);
 
-        let label = Label::new(".αβγδε.ⓐⓑⓒⓓⓔ.abcde.".to_string(), menlo, 12.0).with_highlights(
-            red,
-            *Properties::new().weight(Weight::BOLD),
-            vec![
+        let label = Label::new(".αβγδε.ⓐⓑⓒⓓⓔ.abcde.".to_string(), menlo, 12.0)
+            .with_style(&LabelStyle {
+                default_color: black,
+                highlight_color: red,
+                highlight_font_properties: *Properties::new().weight(Weight::BOLD),
+                ..Default::default()
+            })
+            .with_highlights(vec![
                 ".α".len(),
                 ".αβ".len(),
                 ".αβγδ".len(),
                 ".αβγδε.ⓐ".len(),
                 ".αβγδε.ⓐⓑ".len(),
-            ],
-        );
+            ]);
 
         let runs = label.compute_runs(cx.font_cache().as_ref(), menlo_regular);
         assert_eq!(

zed/src/file_finder.rs 🔗

@@ -154,6 +154,12 @@ impl FileFinder {
             |(file_name, file_name_positions, full_path, full_path_positions)| {
                 let bold = *Properties::new().weight(Weight::BOLD);
                 let selected_index = self.selected_index();
+                let label_style = LabelStyle {
+                    default_color: theme.modal_match_text.0,
+                    highlight_color: theme.modal_match_text_highlight.0,
+                    highlight_font_properties: bold,
+                    ..Default::default()
+                };
                 let mut container = Container::new(
                     Flex::row()
                         .with_child(
@@ -178,12 +184,8 @@ impl FileFinder {
                                             settings.ui_font_family,
                                             settings.ui_font_size,
                                         )
-                                        .with_default_color(theme.modal_match_text.0)
-                                        .with_highlights(
-                                            theme.modal_match_text_highlight.0,
-                                            bold,
-                                            file_name_positions,
-                                        )
+                                        .with_style(&label_style)
+                                        .with_highlights(file_name_positions)
                                         .boxed(),
                                     )
                                     .with_child(
@@ -192,12 +194,8 @@ impl FileFinder {
                                             settings.ui_font_family,
                                             settings.ui_font_size,
                                         )
-                                        .with_default_color(theme.modal_match_text.0)
-                                        .with_highlights(
-                                            theme.modal_match_text_highlight.0,
-                                            bold,
-                                            full_path_positions,
-                                        )
+                                        .with_style(&label_style)
+                                        .with_highlights(full_path_positions)
                                         .boxed(),
                                     )
                                     .boxed(),

zed/src/theme_selector.rs 🔗

@@ -11,8 +11,8 @@ use futures::lock::Mutex;
 use gpui::{
     color::ColorF,
     elements::{
-        Align, ChildView, ConstrainedBox, Container, Expanded, Flex, Label, ParentElement,
-        UniformList, UniformListState,
+        Align, ChildView, ConstrainedBox, Container, Expanded, Flex, Label, LabelStyle,
+        ParentElement, UniformList, UniformListState,
     },
     fonts::{Properties, Weight},
     geometry::vector::vec2f,
@@ -233,7 +233,6 @@ impl ThemeSelector {
     fn render_match(&self, theme_match: &StringMatch, index: usize) -> ElementBox {
         let settings = self.settings.borrow();
         let theme = &settings.theme.ui;
-        let bold = *Properties::new().weight(Weight::BOLD);
 
         let mut container = Container::new(
             Label::new(
@@ -241,12 +240,13 @@ impl ThemeSelector {
                 settings.ui_font_family,
                 settings.ui_font_size,
             )
-            .with_default_color(theme.modal_match_text.0)
-            .with_highlights(
-                theme.modal_match_text_highlight.0,
-                bold,
-                theme_match.positions.clone(),
-            )
+            .with_style(&LabelStyle {
+                default_color: theme.modal_match_text.0,
+                highlight_color: theme.modal_match_text_highlight.0,
+                highlight_font_properties: *Properties::new().weight(Weight::BOLD),
+                ..Default::default()
+            })
+            .with_highlights(theme_match.positions.clone())
             .boxed(),
         )
         .with_uniform_padding(6.0)