Extend `Styled` for Zed-specific methods (#3276)

Nate Butler created

[[PR Description]]

- Adds `StyledExt` for Zed-specific methods like `ui_text`, `v_flex`,
etc.
- Updates components previously using `text_*` methods.

Release Notes:

- N/A

Change summary

crates/ui2/src/components/avatar.rs       | 17 -----
crates/ui2/src/components/button.rs       | 14 ++--
crates/ui2/src/components/details.rs      |  2 
crates/ui2/src/components/icon_button.rs  |  1 
crates/ui2/src/components/input.rs        |  2 
crates/ui2/src/components/keybinding.rs   |  2 
crates/ui2/src/components/label.rs        |  6 +
crates/ui2/src/components/stack.rs        | 20 +-----
crates/ui2/src/lib.rs                     |  2 
crates/ui2/src/prelude.rs                 | 29 +++++++++
crates/ui2/src/styled_ext.rs              | 74 +++++++++++++++++++++++++
crates/ui2/src/to_extract/breadcrumb.rs   |  2 
crates/ui2/src/to_extract/collab_panel.rs |  2 
crates/workspace2/src/workspace2.rs       | 13 +++-
14 files changed, 135 insertions(+), 51 deletions(-)

Detailed changes

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

@@ -61,23 +61,6 @@ mod stories {
                 .child(Avatar::new(
                     "https://avatars.githubusercontent.com/u/326587?v=4",
                 ))
-                // .child(Avatar::new(
-                //     "https://avatars.githubusercontent.com/u/326587?v=4",
-                // ))
-                // .child(Avatar::new(
-                //     "https://avatars.githubusercontent.com/u/482957?v=4",
-                // ))
-                // .child(Avatar::new(
-                //     "https://avatars.githubusercontent.com/u/1714999?v=4",
-                // ))
-                // .child(Avatar::new(
-                //     "https://avatars.githubusercontent.com/u/1486634?v=4",
-                // ))
-                .child(Story::label(cx, "Rounded rectangle"))
-            // .child(
-            //     Avatar::new("https://avatars.githubusercontent.com/u/1714999?v=4")
-            //         .shape(Shape::RoundedRectangle),
-            // )
         }
     }
 }

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

@@ -1,9 +1,11 @@
 use std::sync::Arc;
 
-use gpui::{div, rems, DefiniteLength, Hsla, MouseButton, WindowContext};
+use gpui::{div, DefiniteLength, Hsla, MouseButton, WindowContext};
 
-use crate::{h_stack, Icon, IconColor, IconElement, Label, LabelColor, LineHeightStyle};
-use crate::{prelude::*, IconButton};
+use crate::{
+    h_stack, prelude::*, Icon, IconButton, IconColor, IconElement, Label, LabelColor,
+    LineHeightStyle,
+};
 
 /// Provides the flexibility to use either a standard
 /// button or an icon button in a given context.
@@ -167,10 +169,10 @@ impl<V: 'static> Button<V> {
         let icon_color = self.icon_color();
 
         let mut button = h_stack()
-            .relative()
             .id(SharedString::from(format!("{}", self.label)))
+            .relative()
             .p_1()
-            .text_size(rems(1.))
+            .text_ui()
             .rounded_md()
             .bg(self.variant.bg_color(cx))
             .hover(|style| style.bg(self.variant.bg_color_hover(cx)))
@@ -217,7 +219,7 @@ impl<V: 'static> ButtonGroup<V> {
     }
 
     fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        let mut el = h_stack().text_size(rems(1.));
+        let mut el = h_stack().text_ui();
 
         for button in self.buttons {
             el = el.child(button.render(_view, cx));

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

@@ -31,7 +31,7 @@ impl<V: 'static> Details<V> {
         v_stack()
             .p_1()
             .gap_0p5()
-            .text_xs()
+            .text_ui_sm()
             .text_color(cx.theme().colors().text)
             .size_full()
             .child(self.text)

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

@@ -88,6 +88,7 @@ impl<V: 'static> IconButton<V> {
             .id(self.id.clone())
             .justify_center()
             .rounded_md()
+            // todo!("Where do these numbers come from?")
             .py(rems(0.21875))
             .px(rems(0.375))
             .bg(bg_color)

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

@@ -94,7 +94,7 @@ impl Input {
             .active(|style| style.bg(input_active_bg))
             .flex()
             .items_center()
-            .child(div().flex().items_center().text_sm().map(|this| {
+            .child(div().flex().items_center().text_ui_sm().map(|this| {
                 if self.value.is_empty() {
                     this.child(placeholder_label)
                 } else {

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

@@ -64,7 +64,7 @@ impl Key {
             .px_2()
             .py_0()
             .rounded_md()
-            .text_sm()
+            .text_ui_sm()
             .text_color(cx.theme().colors().text)
             .bg(cx.theme().colors().element_background)
             .child(self.key.clone())

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

@@ -1,7 +1,9 @@
-use gpui::{relative, rems, Hsla, WindowContext};
+use gpui::{relative, Hsla, WindowContext};
 use smallvec::SmallVec;
 
 use crate::prelude::*;
+use crate::styled_ext::StyledExt;
+
 #[derive(Default, PartialEq, Copy, Clone)]
 pub enum LabelColor {
     #[default]
@@ -85,7 +87,7 @@ impl Label {
                         .bg(LabelColor::Hidden.hsla(cx)),
                 )
             })
-            .text_size(rems(1.))
+            .text_ui()
             .when(self.line_height_style == LineHeightStyle::UILabel, |this| {
                 this.line_height(relative(1.))
             })

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

@@ -1,31 +1,17 @@
 use gpui::{div, Div};
 
-use crate::prelude::*;
-
-pub trait Stack: Styled + Sized {
-    /// Horizontally stacks elements.
-    fn h_stack(self) -> Self {
-        self.flex().flex_row().items_center()
-    }
-
-    /// Vertically stacks elements.
-    fn v_stack(self) -> Self {
-        self.flex().flex_col()
-    }
-}
-
-impl<V: 'static> Stack for Div<V> {}
+use crate::StyledExt;
 
 /// Horizontally stacks elements.
 ///
 /// Sets `flex()`, `flex_row()`, `items_center()`
 pub fn h_stack<V: 'static>() -> Div<V> {
-    div().h_stack()
+    div().h_flex()
 }
 
 /// Vertically stacks elements.
 ///
 /// Sets `flex()`, `flex_col()`
 pub fn v_stack<V: 'static>() -> Div<V> {
-    div().v_stack()
+    div().v_flex()
 }

crates/ui2/src/lib.rs 🔗

@@ -19,12 +19,14 @@ mod elevation;
 pub mod prelude;
 pub mod settings;
 mod static_data;
+mod styled_ext;
 mod to_extract;
 pub mod utils;
 
 pub use components::*;
 pub use prelude::*;
 pub use static_data::*;
+pub use styled_ext::*;
 pub use to_extract::*;
 
 // This needs to be fully qualified with `crate::` otherwise we get a panic

crates/ui2/src/prelude.rs 🔗

@@ -1,3 +1,5 @@
+use gpui::rems;
+use gpui::Rems;
 pub use gpui::{
     div, Component, Element, ElementId, ParentElement, SharedString, StatefulInteractive,
     StatelessInteractive, Styled, ViewContext, WindowContext,
@@ -5,11 +7,38 @@ pub use gpui::{
 
 pub use crate::elevation::*;
 pub use crate::ButtonVariant;
+pub use crate::StyledExt;
 pub use theme2::ActiveTheme;
 
 use gpui::Hsla;
 use strum::EnumIter;
 
+#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
+pub enum UITextSize {
+    /// The default size for UI text.
+    ///
+    /// `0.825rem` or `14px` at the default scale of `1rem` = `16px`.
+    ///
+    /// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
+    #[default]
+    Default,
+    /// The small size for UI text.
+    ///
+    /// `0.75rem` or `12px` at the default scale of `1rem` = `16px`.
+    ///
+    /// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
+    Small,
+}
+
+impl UITextSize {
+    pub fn rems(self) -> Rems {
+        match self {
+            Self::Default => rems(0.875),
+            Self::Small => rems(0.75),
+        }
+    }
+}
+
 #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
 pub enum FileSystemStatus {
     #[default]

crates/ui2/src/styled_ext.rs 🔗

@@ -0,0 +1,74 @@
+use gpui::{Div, ElementFocus, ElementInteractivity, Styled};
+
+use crate::UITextSize;
+
+/// Extends [`Styled`](gpui::Styled) with Zed specific styling methods.
+pub trait StyledExt: Styled {
+    /// Horizontally stacks elements.
+    ///
+    /// Sets `flex()`, `flex_row()`, `items_center()`
+    fn h_flex(self) -> Self
+    where
+        Self: Sized,
+    {
+        self.flex().flex_row().items_center()
+    }
+
+    /// Vertically stacks elements.
+    ///
+    /// Sets `flex()`, `flex_col()`
+    fn v_flex(self) -> Self
+    where
+        Self: Sized,
+    {
+        self.flex().flex_col()
+    }
+
+    fn text_ui_size(self, size: UITextSize) -> Self
+    where
+        Self: Sized,
+    {
+        let size = size.rems();
+
+        self.text_size(size)
+    }
+
+    /// The default size for UI text.
+    ///
+    /// `0.825rem` or `14px` at the default scale of `1rem` = `16px`.
+    ///
+    /// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
+    ///
+    /// Use [`text_ui_sm`] for regular-sized text.
+    fn text_ui(self) -> Self
+    where
+        Self: Sized,
+    {
+        let size = UITextSize::default().rems();
+
+        self.text_size(size)
+    }
+
+    /// The small size for UI text.
+    ///
+    /// `0.75rem` or `12px` at the default scale of `1rem` = `16px`.
+    ///
+    /// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
+    ///
+    /// Use [`text_ui`] for regular-sized text.
+    fn text_ui_sm(self) -> Self
+    where
+        Self: Sized,
+    {
+        let size = UITextSize::Small.rems();
+
+        self.text_size(size)
+    }
+}
+
+impl<V, I, F> StyledExt for Div<V, I, F>
+where
+    I: ElementInteractivity<V>,
+    F: ElementFocus<V>,
+{
+}

crates/ui2/src/to_extract/breadcrumb.rs 🔗

@@ -30,7 +30,7 @@ impl Breadcrumb {
         h_stack()
             .id("breadcrumb")
             .px_1()
-            .text_sm()
+            .text_ui_sm()
             .text_color(cx.theme().colors().text_muted)
             .rounded_md()
             .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))

crates/workspace2/src/workspace2.rs 🔗

@@ -36,7 +36,7 @@ use futures::{
     Future, FutureExt, StreamExt,
 };
 use gpui::{
-    div, point, size, AnyModel, AnyView, AnyWeakView, AppContext, AsyncAppContext,
+    div, point, rems, size, AnyModel, AnyView, AnyWeakView, AppContext, AsyncAppContext,
     AsyncWindowContext, Bounds, Component, Div, Entity, EntityId, EventEmitter, FocusHandle,
     GlobalPixels, Model, ModelContext, ParentElement, Point, Render, Size, StatefulInteractive,
     Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds,
@@ -69,6 +69,7 @@ use std::{
 };
 use theme2::ActiveTheme;
 pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
+use ui::{h_stack, Label};
 use util::ResultExt;
 use uuid::Uuid;
 use workspace_settings::{AutosaveSetting, WorkspaceSettings};
@@ -2620,19 +2621,23 @@ impl Workspace {
     //     }
 
     fn render_titlebar(&self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
-        div()
+        h_stack()
+            .id("titlebar")
+            .justify_between()
+            .w_full()
+            .h(rems(1.75))
             .bg(cx.theme().colors().title_bar_background)
             .when(
                 !matches!(cx.window_bounds(), WindowBounds::Fullscreen),
                 |s| s.pl_20(),
             )
-            .id("titlebar")
             .on_click(|_, event, cx| {
                 if event.up.click_count == 2 {
                     cx.zoom_window();
                 }
             })
-            .child("Collab title bar Item") // self.titlebar_item
+            .child(h_stack().child(Label::new("Left side titlebar item"))) // self.titlebar_item
+            .child(h_stack().child(Label::new("Right side titlebar item")))
     }
 
     fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {