Factor out `LabelLike` to share common label styles (#3510)

Marshall Bowers created

This PR factors out a new `LabelLike` component to share common styles
between the `Label` and `HighlightedLabel` components.

Release Notes:

- N/A

Change summary

crates/copilot2/src/sign_in.rs                       |   2 
crates/diagnostics2/src/diagnostics.rs               |   2 
crates/editor2/src/items.rs                          |   2 
crates/go_to_line2/src/go_to_line.rs                 |   2 
crates/ui2/src/components/label.rs                   | 188 -------------
crates/ui2/src/components/label/highlighted_label.rs |  86 ++++++
crates/ui2/src/components/label/label.rs             |  48 +++
crates/ui2/src/components/label/label_like.rs        | 102 +++++++
crates/ui2/src/prelude.rs                            |   2 
9 files changed, 247 insertions(+), 187 deletions(-)

Detailed changes

crates/copilot2/src/sign_in.rs 🔗

@@ -5,7 +5,7 @@ use gpui::{
     WindowBounds, WindowHandle, WindowKind, WindowOptions,
 };
 use theme::ActiveTheme;
-use ui::{h_stack, v_stack, Button, Clickable, Color, Icon, IconElement, Label};
+use ui::{prelude::*, Button, Icon, IconElement, Label};
 
 const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot";
 

crates/diagnostics2/src/diagnostics.rs 🔗

@@ -36,7 +36,7 @@ use std::{
 };
 use theme::ActiveTheme;
 pub use toolbar_controls::ToolbarControls;
-use ui::{h_stack, Color, HighlightedLabel, Icon, IconElement, Label};
+use ui::{h_stack, prelude::*, HighlightedLabel, Icon, IconElement, Label};
 use util::TryFutureExt;
 use workspace::{
     item::{BreadcrumbText, Item, ItemEvent, ItemHandle},

crates/editor2/src/items.rs 🔗

@@ -32,7 +32,7 @@ use std::{
 };
 use text::Selection;
 use theme::{ActiveTheme, Theme};
-use ui::{h_stack, Color, Label};
+use ui::{h_stack, prelude::*, Label};
 use util::{paths::PathExt, paths::FILE_ROW_COLUMN_DELIMITER, ResultExt, TryFutureExt};
 use workspace::{
     item::{BreadcrumbText, FollowEvent, FollowableItemHandle},

crates/go_to_line2/src/go_to_line.rs 🔗

@@ -6,7 +6,7 @@ use gpui::{
 };
 use text::{Bias, Point};
 use theme::ActiveTheme;
-use ui::{h_stack, v_stack, Color, Label, StyledExt};
+use ui::{h_stack, prelude::*, v_stack, Label};
 use util::paths::FILE_ROW_COLUMN_DELIMITER;
 
 actions!(Toggle);

crates/ui2/src/components/label.rs 🔗

@@ -1,183 +1,7 @@
-use std::ops::Range;
+mod highlighted_label;
+mod label;
+mod label_like;
 
-use crate::prelude::*;
-use crate::styled_ext::StyledExt;
-use gpui::{relative, Div, HighlightStyle, IntoElement, StyledText, WindowContext};
-
-#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
-pub enum LabelSize {
-    #[default]
-    Default,
-    Small,
-}
-
-#[derive(Default, PartialEq, Copy, Clone)]
-pub enum LineHeightStyle {
-    #[default]
-    TextLabel,
-    /// Sets the line height to 1
-    UILabel,
-}
-
-#[derive(IntoElement, Clone)]
-pub struct Label {
-    label: SharedString,
-    size: LabelSize,
-    line_height_style: LineHeightStyle,
-    color: Color,
-    strikethrough: bool,
-}
-
-impl RenderOnce for Label {
-    type Rendered = Div;
-
-    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
-        div()
-            .when(self.strikethrough, |this| {
-                this.relative().child(
-                    div()
-                        .absolute()
-                        .top_1_2()
-                        .w_full()
-                        .h_px()
-                        .bg(Color::Hidden.color(cx)),
-                )
-            })
-            .map(|this| match self.size {
-                LabelSize::Default => this.text_ui(),
-                LabelSize::Small => this.text_ui_sm(),
-            })
-            .when(self.line_height_style == LineHeightStyle::UILabel, |this| {
-                this.line_height(relative(1.))
-            })
-            .text_color(self.color.color(cx))
-            .child(self.label.clone())
-    }
-}
-
-impl Label {
-    pub fn new(label: impl Into<SharedString>) -> Self {
-        Self {
-            label: label.into(),
-            size: LabelSize::Default,
-            line_height_style: LineHeightStyle::default(),
-            color: Color::Default,
-            strikethrough: false,
-        }
-    }
-
-    pub fn size(mut self, size: LabelSize) -> Self {
-        self.size = size;
-        self
-    }
-
-    pub fn color(mut self, color: Color) -> Self {
-        self.color = color;
-        self
-    }
-
-    pub fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {
-        self.line_height_style = line_height_style;
-        self
-    }
-
-    pub fn set_strikethrough(mut self, strikethrough: bool) -> Self {
-        self.strikethrough = strikethrough;
-        self
-    }
-}
-
-#[derive(IntoElement)]
-pub struct HighlightedLabel {
-    label: SharedString,
-    size: LabelSize,
-    color: Color,
-    highlight_indices: Vec<usize>,
-    strikethrough: bool,
-}
-
-impl RenderOnce for HighlightedLabel {
-    type Rendered = Div;
-
-    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
-        let highlight_color = cx.theme().colors().text_accent;
-
-        let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
-        let mut highlights: Vec<(Range<usize>, HighlightStyle)> = Vec::new();
-
-        while let Some(start_ix) = highlight_indices.next() {
-            let mut end_ix = start_ix;
-
-            loop {
-                end_ix = end_ix + self.label[end_ix..].chars().next().unwrap().len_utf8();
-                if let Some(&next_ix) = highlight_indices.peek() {
-                    if next_ix == end_ix {
-                        end_ix = next_ix;
-                        highlight_indices.next();
-                        continue;
-                    }
-                }
-                break;
-            }
-
-            highlights.push((
-                start_ix..end_ix,
-                HighlightStyle {
-                    color: Some(highlight_color),
-                    ..Default::default()
-                },
-            ));
-        }
-
-        let mut text_style = cx.text_style().clone();
-        text_style.color = self.color.color(cx);
-
-        div()
-            .flex()
-            .when(self.strikethrough, |this| {
-                this.relative().child(
-                    div()
-                        .absolute()
-                        .top_px()
-                        .my_auto()
-                        .w_full()
-                        .h_px()
-                        .bg(Color::Hidden.color(cx)),
-                )
-            })
-            .map(|this| match self.size {
-                LabelSize::Default => this.text_ui(),
-                LabelSize::Small => this.text_ui_sm(),
-            })
-            .child(StyledText::new(self.label).with_highlights(&text_style, highlights))
-    }
-}
-
-impl HighlightedLabel {
-    /// shows a label with the given characters highlighted.
-    /// characters are identified by utf8 byte position.
-    pub fn new(label: impl Into<SharedString>, highlight_indices: Vec<usize>) -> Self {
-        Self {
-            label: label.into(),
-            size: LabelSize::Default,
-            color: Color::Default,
-            highlight_indices,
-            strikethrough: false,
-        }
-    }
-
-    pub fn size(mut self, size: LabelSize) -> Self {
-        self.size = size;
-        self
-    }
-
-    pub fn color(mut self, color: Color) -> Self {
-        self.color = color;
-        self
-    }
-
-    pub fn set_strikethrough(mut self, strikethrough: bool) -> Self {
-        self.strikethrough = strikethrough;
-        self
-    }
-}
+pub use highlighted_label::*;
+pub use label::*;
+pub use label_like::*;

crates/ui2/src/components/label/highlighted_label.rs 🔗

@@ -0,0 +1,86 @@
+use std::ops::Range;
+
+use gpui::{HighlightStyle, StyledText};
+
+use crate::{prelude::*, LabelCommon, LabelLike, LabelSize, LineHeightStyle};
+
+#[derive(IntoElement)]
+pub struct HighlightedLabel {
+    base: LabelLike,
+    label: SharedString,
+    highlight_indices: Vec<usize>,
+}
+
+impl HighlightedLabel {
+    /// Constructs a label with the given characters highlighted.
+    /// Characters are identified by UTF-8 byte position.
+    pub fn new(label: impl Into<SharedString>, highlight_indices: Vec<usize>) -> Self {
+        Self {
+            base: LabelLike::new(),
+            label: label.into(),
+            highlight_indices,
+        }
+    }
+}
+
+impl LabelCommon for HighlightedLabel {
+    fn size(mut self, size: LabelSize) -> Self {
+        self.base = self.base.size(size);
+        self
+    }
+
+    fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {
+        self.base = self.base.line_height_style(line_height_style);
+        self
+    }
+
+    fn color(mut self, color: Color) -> Self {
+        self.base = self.base.color(color);
+        self
+    }
+
+    fn strikethrough(mut self, strikethrough: bool) -> Self {
+        self.base = self.base.strikethrough(strikethrough);
+        self
+    }
+}
+
+impl RenderOnce for HighlightedLabel {
+    type Rendered = LabelLike;
+
+    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
+        let highlight_color = cx.theme().colors().text_accent;
+
+        let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
+        let mut highlights: Vec<(Range<usize>, HighlightStyle)> = Vec::new();
+
+        while let Some(start_ix) = highlight_indices.next() {
+            let mut end_ix = start_ix;
+
+            loop {
+                end_ix = end_ix + self.label[end_ix..].chars().next().unwrap().len_utf8();
+                if let Some(&next_ix) = highlight_indices.peek() {
+                    if next_ix == end_ix {
+                        end_ix = next_ix;
+                        highlight_indices.next();
+                        continue;
+                    }
+                }
+                break;
+            }
+
+            highlights.push((
+                start_ix..end_ix,
+                HighlightStyle {
+                    color: Some(highlight_color),
+                    ..Default::default()
+                },
+            ));
+        }
+
+        let mut text_style = cx.text_style().clone();
+        text_style.color = self.base.color.color(cx);
+
+        LabelLike::new().child(StyledText::new(self.label).with_highlights(&text_style, highlights))
+    }
+}

crates/ui2/src/components/label/label.rs 🔗

@@ -0,0 +1,48 @@
+use gpui::WindowContext;
+
+use crate::{prelude::*, LabelCommon, LabelLike, LabelSize, LineHeightStyle};
+
+#[derive(IntoElement)]
+pub struct Label {
+    base: LabelLike,
+    label: SharedString,
+}
+
+impl Label {
+    pub fn new(label: impl Into<SharedString>) -> Self {
+        Self {
+            base: LabelLike::new(),
+            label: label.into(),
+        }
+    }
+}
+
+impl LabelCommon for Label {
+    fn size(mut self, size: LabelSize) -> Self {
+        self.base = self.base.size(size);
+        self
+    }
+
+    fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {
+        self.base = self.base.line_height_style(line_height_style);
+        self
+    }
+
+    fn color(mut self, color: Color) -> Self {
+        self.base = self.base.color(color);
+        self
+    }
+
+    fn strikethrough(mut self, strikethrough: bool) -> Self {
+        self.base = self.base.strikethrough(strikethrough);
+        self
+    }
+}
+
+impl RenderOnce for Label {
+    type Rendered = LabelLike;
+
+    fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
+        self.base.child(self.label)
+    }
+}

crates/ui2/src/components/label/label_like.rs 🔗

@@ -0,0 +1,102 @@
+use gpui::{relative, AnyElement, Div, Styled};
+use smallvec::SmallVec;
+
+use crate::prelude::*;
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
+pub enum LabelSize {
+    #[default]
+    Default,
+    Small,
+}
+
+#[derive(Default, PartialEq, Copy, Clone)]
+pub enum LineHeightStyle {
+    #[default]
+    TextLabel,
+    /// Sets the line height to 1
+    UILabel,
+}
+
+pub trait LabelCommon {
+    fn size(self, size: LabelSize) -> Self;
+    fn line_height_style(self, line_height_style: LineHeightStyle) -> Self;
+    fn color(self, color: Color) -> Self;
+    fn strikethrough(self, strikethrough: bool) -> Self;
+}
+
+#[derive(IntoElement)]
+pub struct LabelLike {
+    size: LabelSize,
+    line_height_style: LineHeightStyle,
+    pub(crate) color: Color,
+    strikethrough: bool,
+    children: SmallVec<[AnyElement; 2]>,
+}
+
+impl LabelLike {
+    pub fn new() -> Self {
+        Self {
+            size: LabelSize::Default,
+            line_height_style: LineHeightStyle::default(),
+            color: Color::Default,
+            strikethrough: false,
+            children: SmallVec::new(),
+        }
+    }
+}
+
+impl LabelCommon for LabelLike {
+    fn size(mut self, size: LabelSize) -> Self {
+        self.size = size;
+        self
+    }
+
+    fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {
+        self.line_height_style = line_height_style;
+        self
+    }
+
+    fn color(mut self, color: Color) -> Self {
+        self.color = color;
+        self
+    }
+
+    fn strikethrough(mut self, strikethrough: bool) -> Self {
+        self.strikethrough = strikethrough;
+        self
+    }
+}
+
+impl ParentElement for LabelLike {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
+        &mut self.children
+    }
+}
+
+impl RenderOnce for LabelLike {
+    type Rendered = Div;
+
+    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
+        div()
+            .when(self.strikethrough, |this| {
+                this.relative().child(
+                    div()
+                        .absolute()
+                        .top_1_2()
+                        .w_full()
+                        .h_px()
+                        .bg(Color::Hidden.color(cx)),
+                )
+            })
+            .map(|this| match self.size {
+                LabelSize::Default => this.text_ui(),
+                LabelSize::Small => this.text_ui_sm(),
+            })
+            .when(self.line_height_style == LineHeightStyle::UILabel, |this| {
+                this.line_height(relative(1.))
+            })
+            .text_color(self.color.color(cx))
+            .children(self.children)
+    }
+}

crates/ui2/src/prelude.rs 🔗

@@ -9,5 +9,5 @@ pub use crate::disableable::*;
 pub use crate::fixed::*;
 pub use crate::selectable::*;
 pub use crate::{h_stack, v_stack};
-pub use crate::{ButtonCommon, Color, StyledExt};
+pub use crate::{ButtonCommon, Color, LabelCommon, StyledExt};
 pub use theme::ActiveTheme;