UI refinements + Popover (#3377)

Nate Butler created

[[PR Description]]

- Update default ui_font_size
- Update toolbar style
- Start on ui::Popover

Release Notes:

- N/A

Change summary

assets/settings/default.json               |  2 
assets/settings/initial_user_settings.json |  3 
crates/ui2/src/components.rs               |  2 
crates/ui2/src/components/list.rs          |  3 
crates/ui2/src/components/popover.rs       | 89 ++++++++++++++++++++++++
crates/ui2/src/prelude.rs                  | 10 ++
crates/workspace2/src/toolbar.rs           |  3 
7 files changed, 108 insertions(+), 4 deletions(-)

Detailed changes

assets/settings/default.json 🔗

@@ -43,7 +43,7 @@
     "calt": false
   },
   // The default font size for text in the UI
-  "ui_font_size": 14,
+  "ui_font_size": 16,
   // The factor to grow the active pane by. Defaults to 1.0
   // which gives the same size as all other panes.
   "active_pane_magnification": 1.0,

assets/settings/initial_user_settings.json 🔗

@@ -7,5 +7,6 @@
 // custom settings, run the `open default settings` command
 // from the command palette or from `Zed` application menu.
 {
-  "buffer_font_size": 15
+  "ui_font_size": 16,
+  "buffer_font_size": 16
 }

crates/ui2/src/components.rs 🔗

@@ -10,6 +10,7 @@ mod input;
 mod keybinding;
 mod label;
 mod list;
+mod popover;
 mod slot;
 mod stack;
 mod toggle;
@@ -30,6 +31,7 @@ pub use input::*;
 pub use keybinding::*;
 pub use label::*;
 pub use list::*;
+pub use popover::*;
 pub use slot::*;
 pub use stack::*;
 pub use toggle::*;

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

@@ -364,12 +364,13 @@ impl Component for ListItem {
                     }
                 }
             })
-            .bg(cx.theme().colors().surface_background)
             // TODO: Add focus state
             // .when(self.state == InteractionState::Focused, |this| {
             //     this.border()
             //         .border_color(cx.theme().colors().border_focused)
             // })
+            .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
+            .active(|style| style.bg(cx.theme().colors().ghost_element_active))
             .child(
                 sized_item
                     .when(self.variant == ListItemVariant::Inset, |this| this.px_2())

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

@@ -0,0 +1,89 @@
+use gpui::{
+    AnyElement, Component, Div, Element, ElementId, ParentElement, RenderOnce, Styled,
+    WindowContext,
+};
+use smallvec::SmallVec;
+
+use crate::{v_stack, StyledExt};
+
+/// A popover is used to display a menu or show some options.
+///
+/// Clicking the element that launches the popover should not change the current view,
+/// and the popover should be statically positioned relative to that element (not the
+/// user's mouse.)
+///
+/// Example: A "new" menu with options like "new file", "new folder", etc,
+/// Linear's "Display" menu, a profile menu that appers when you click your avatar.
+///
+/// Related elements:
+///
+/// `ContextMenu`:
+///
+/// Used to display a popover menu that only contains a list of items. Context menus are always
+/// launched by secondary clicking on an element. The menu is positioned relative to the user's cursor.
+///
+/// Example: Right clicking a file in the file tree to get a list of actions, right clicking
+/// a tab to in the tab bar to get a list of actions.
+///
+/// `Dropdown`:
+///
+/// Used to display a list of options when the user clicks an element. The menu is
+/// positioned relative the element that was clicked, and clicking an item in the
+/// dropdown should change the value of the element that was clicked.
+///
+/// Example: A theme select control. Displays "One Dark", clicking it opens a list of themes.
+/// When one is selected, the theme select control displays the selected theme.
+#[derive(RenderOnce)]
+pub struct Popover {
+    children: SmallVec<[AnyElement; 2]>,
+    aside: Option<AnyElement>,
+}
+
+impl Component for Popover {
+    type Rendered = Div;
+
+    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
+        v_stack()
+            .relative()
+            .elevation_2(cx)
+            .p_1()
+            .children(self.children)
+            .when_some(self.aside, |this, aside| {
+                // TODO: This will statically position the aside to the top right of the popover.
+                // We should update this to use gpui2::overlay avoid collisions with the window edges.
+                this.child(
+                    v_stack()
+                        .top_0()
+                        .left_full()
+                        .ml_1()
+                        .absolute()
+                        .elevation_2(cx)
+                        .p_1()
+                        .child(aside),
+                )
+            })
+    }
+}
+
+impl Popover {
+    pub fn new() -> Self {
+        Self {
+            children: SmallVec::new(),
+            aside: None,
+        }
+    }
+
+    pub fn aside(mut self, aside: impl RenderOnce) -> Self
+    where
+        Self: Sized,
+    {
+        self.aside = Some(aside.render_once().into_any());
+        self
+    }
+}
+
+impl ParentElement for Popover {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
+        &mut self.children
+    }
+}

crates/ui2/src/prelude.rs 🔗

@@ -24,12 +24,22 @@ pub enum OverflowStyle {
 
 #[derive(Default, PartialEq, Copy, Clone, EnumIter, strum::Display)]
 pub enum InteractionState {
+    /// An element that is enabled and not hovered, active, focused, or disabled.
+    ///
+    /// This is often referred to as the "default" state.
     #[default]
     Enabled,
+    /// An element that is hovered.
     Hovered,
+    /// An element has an active mouse down or touch start event on it.
     Active,
+    /// An element that is focused using the keyboard.
     Focused,
+    /// An element that is disabled.
     Disabled,
+    /// A toggleable element that is selected, like the active button in a
+    /// button toggle group.
+    Selected,
 }
 
 impl InteractionState {

crates/workspace2/src/toolbar.rs 🔗

@@ -83,7 +83,8 @@ impl Render for Toolbar {
         //dbg!(&self.items.len());
         v_stack()
             .border_b()
-            .border_color(cx.theme().colors().border)
+            .border_color(cx.theme().colors().border_variant)
+            .bg(cx.theme().colors().toolbar_background)
             .child(
                 h_stack()
                     .justify_between()