Rework `List` to use `children` (#3369)

Marshall Bowers created

This PR reworks the `List` component to use `children` instead of
accepting a `Vec<ListItem>` in its constructor.

This is a step towards making the `List` component more open.

Release Notes:

- N/A

Change summary

crates/terminal_view2/src/terminal_view.rs |  13 
crates/ui2/src/components/context_menu.rs  |  58 +++---
crates/ui2/src/components/list.rs          | 113 +++---------
crates/ui2/src/static_data.rs              | 206 ++++++++++++-----------
crates/ui2/src/to_extract/collab_panel.rs  |  20 +
crates/ui2/src/to_extract/project_panel.rs |  20 +
crates/workspace2/src/dock.rs              |   8 
7 files changed, 205 insertions(+), 233 deletions(-)

Detailed changes

crates/terminal_view2/src/terminal_view.rs 🔗

@@ -300,11 +300,14 @@ impl TerminalView {
         cx: &mut ViewContext<Self>,
     ) {
         self.context_menu = Some(ContextMenu::build(cx, |menu, _| {
-            menu.action(ListEntry::new(Label::new("Clear")), Box::new(Clear))
-                .action(
-                    ListEntry::new(Label::new("Close")),
-                    Box::new(CloseActiveItem { save_intent: None }),
-                )
+            menu.action(
+                ListEntry::new("clear", Label::new("Clear")),
+                Box::new(Clear),
+            )
+            .action(
+                ListEntry::new("close", Label::new("Close")),
+                Box::new(CloseActiveItem { save_intent: None }),
+            )
         }));
         dbg!(&position);
         // todo!()

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

@@ -1,7 +1,7 @@
 use std::cell::RefCell;
 use std::rc::Rc;
 
-use crate::{prelude::*, v_stack, List, ListItem};
+use crate::{prelude::*, v_stack, List};
 use crate::{ListEntry, ListSeparator, ListSubHeader};
 use gpui::{
     overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, DispatchPhase, Div,
@@ -9,7 +9,7 @@ use gpui::{
     MouseDownEvent, Pixels, Point, Render, RenderOnce, View, VisualContext, WeakView,
 };
 
-pub enum ContextMenuItem<V> {
+pub enum ContextMenuItem<V: 'static> {
     Separator(ListSeparator),
     Header(ListSubHeader),
     Entry(
@@ -18,7 +18,7 @@ pub enum ContextMenuItem<V> {
     ),
 }
 
-pub struct ContextMenu<V> {
+pub struct ContextMenu<V: 'static> {
     items: Vec<ContextMenuItem<V>>,
     focus_handle: FocusHandle,
     handle: WeakView<V>,
@@ -105,25 +105,25 @@ impl<V: 'static> Render<Self> for ContextMenu<V> {
                 // .bg(cx.theme().colors().elevated_surface_background)
                 // .border()
                 // .border_color(cx.theme().colors().border)
-                .child(List::new(
-                    self.items
-                        .iter()
-                        .map(|item| match item {
-                            ContextMenuItem::Separator(separator) => {
-                                ListItem::Separator(separator.clone())
-                            }
-                            ContextMenuItem::Header(header) => ListItem::Header(header.clone()),
-                            ContextMenuItem::Entry(entry, callback) => {
-                                let callback = callback.clone();
-                                let handle = self.handle.clone();
-                                ListItem::Entry(entry.clone().on_click(move |this, cx| {
+                .child(
+                    List::new().children(self.items.iter().map(|item| match item {
+                        ContextMenuItem::Separator(separator) => {
+                            separator.clone().render_into_any()
+                        }
+                        ContextMenuItem::Header(header) => header.clone().render_into_any(),
+                        ContextMenuItem::Entry(entry, callback) => {
+                            let callback = callback.clone();
+                            let handle = self.handle.clone();
+                            entry
+                                .clone()
+                                .on_click(move |this, cx| {
                                     handle.update(cx, |view, cx| callback(view, cx)).ok();
                                     cx.emit(Manager::Dismiss);
-                                }))
-                            }
-                        })
-                        .collect(),
-                )),
+                                })
+                                .render_into_any()
+                        }
+                    })),
+                ),
         )
     }
 }
@@ -322,13 +322,17 @@ mod stories {
         ContextMenu::build(cx, |menu, _| {
             menu.header(header)
                 .separator()
-                .entry(ListEntry::new(Label::new("Print current time")), |v, cx| {
-                    println!("dispatching PrintCurrentTime action");
-                    cx.dispatch_action(PrintCurrentDate.boxed_clone())
-                })
-                .entry(ListEntry::new(Label::new("Print best food")), |v, cx| {
-                    cx.dispatch_action(PrintBestFood.boxed_clone())
-                })
+                .entry(
+                    ListEntry::new("Print current time", Label::new("Print current time")),
+                    |v, cx| {
+                        println!("dispatching PrintCurrentTime action");
+                        cx.dispatch_action(PrintCurrentDate.boxed_clone())
+                    },
+                )
+                .entry(
+                    ListEntry::new("Print best food", Label::new("Print best food")),
+                    |v, cx| cx.dispatch_action(PrintBestFood.boxed_clone()),
+                )
         })
     }
 

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

@@ -1,4 +1,5 @@
-use gpui::{div, Div, RenderOnce, Stateful, StatefulInteractiveElement};
+use gpui::{div, AnyElement, Div, RenderOnce, Stateful, StatefulInteractiveElement};
+use smallvec::SmallVec;
 use std::rc::Rc;
 
 use crate::settings::user_settings;
@@ -177,7 +178,7 @@ impl ListHeader {
     // }
 }
 
-#[derive(Clone)]
+#[derive(RenderOnce, Clone)]
 pub struct ListSubHeader {
     label: SharedString,
     left_icon: Option<Icon>,
@@ -197,8 +198,12 @@ impl ListSubHeader {
         self.left_icon = left_icon;
         self
     }
+}
+
+impl<V: 'static> Component<V> for ListSubHeader {
+    type Rendered = Div<V>;
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         h_stack().flex_1().w_full().relative().py_1().child(
             div()
                 .h_6()
@@ -232,55 +237,9 @@ pub enum ListEntrySize {
     Medium,
 }
 
-#[derive(Clone)]
-pub enum ListItem<V: 'static> {
-    Entry(ListEntry<V>),
-    Separator(ListSeparator),
-    Header(ListSubHeader),
-}
-
-impl<V: 'static> From<ListEntry<V>> for ListItem<V> {
-    fn from(entry: ListEntry<V>) -> Self {
-        Self::Entry(entry)
-    }
-}
-
-impl<V: 'static> From<ListSeparator> for ListItem<V> {
-    fn from(entry: ListSeparator) -> Self {
-        Self::Separator(entry)
-    }
-}
-
-impl<V: 'static> From<ListSubHeader> for ListItem<V> {
-    fn from(entry: ListSubHeader) -> Self {
-        Self::Header(entry)
-    }
-}
-
-impl<V: 'static> ListItem<V> {
-    pub fn new(label: Label) -> Self {
-        Self::Entry(ListEntry::new(label))
-    }
-
-    pub fn as_entry(&mut self) -> Option<&mut ListEntry<V>> {
-        if let Self::Entry(entry) = self {
-            Some(entry)
-        } else {
-            None
-        }
-    }
-
-    fn render(self, view: &mut V, ix: usize, cx: &mut ViewContext<V>) -> Div<V> {
-        match self {
-            ListItem::Entry(entry) => div().child(entry.render(ix, cx)),
-            ListItem::Separator(separator) => div().child(separator.render(view, cx)),
-            ListItem::Header(header) => div().child(header.render(view, cx)),
-        }
-    }
-}
-
-// #[derive(RenderOnce)]
-pub struct ListEntry<V> {
+#[derive(RenderOnce)]
+pub struct ListEntry<V: 'static> {
+    id: ElementId,
     disabled: bool,
     // TODO: Reintroduce this
     // disclosure_control_style: DisclosureControlVisibility,
@@ -297,6 +256,7 @@ pub struct ListEntry<V> {
 impl<V> Clone for ListEntry<V> {
     fn clone(&self) -> Self {
         Self {
+            id: self.id.clone(),
             disabled: self.disabled,
             indent_level: self.indent_level,
             label: self.label.clone(),
@@ -311,8 +271,9 @@ impl<V> Clone for ListEntry<V> {
 }
 
 impl<V: 'static> ListEntry<V> {
-    pub fn new(label: Label) -> Self {
+    pub fn new(id: impl Into<ElementId>, label: Label) -> Self {
         Self {
+            id: id.into(),
             disabled: false,
             indent_level: 0,
             label,
@@ -364,8 +325,12 @@ impl<V: 'static> ListEntry<V> {
         self.size = size;
         self
     }
+}
+
+impl<V: 'static> Component<V> for ListEntry<V> {
+    type Rendered = Stateful<V, Div<V>>;
 
-    fn render(self, ix: usize, cx: &mut ViewContext<V>) -> Stateful<V, Div<V>> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         let settings = user_settings(cx);
 
         let left_content = match self.left_slot.clone() {
@@ -386,7 +351,7 @@ impl<V: 'static> ListEntry<V> {
             ListEntrySize::Medium => div().h_7(),
         };
         div()
-            .id(ix)
+            .id(self.id)
             .relative()
             .hover(|mut style| {
                 style.background = Some(cx.theme().colors().editor_background.into());
@@ -454,25 +419,20 @@ impl<V: 'static> Component<V> for ListSeparator {
 
 #[derive(RenderOnce)]
 pub struct List<V: 'static> {
-    items: Vec<ListItem<V>>,
     /// Message to display when the list is empty
     /// Defaults to "No items"
     empty_message: SharedString,
     header: Option<ListHeader>,
     toggle: Toggle,
+    children: SmallVec<[AnyElement<V>; 2]>,
 }
 
 impl<V: 'static> Component<V> for List<V> {
     type Rendered = Div<V>;
 
     fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
-        let list_content = match (self.items.is_empty(), self.toggle) {
-            (false, _) => div().children(
-                self.items
-                    .into_iter()
-                    .enumerate()
-                    .map(|(ix, item)| item.render(view, ix, cx)),
-            ),
+        let list_content = match (self.children.is_empty(), self.toggle) {
+            (false, _) => div().children(self.children),
             (true, Toggle::Toggled(false)) => div(),
             (true, _) => {
                 div().child(Label::new(self.empty_message.clone()).color(TextColor::Muted))
@@ -488,12 +448,12 @@ impl<V: 'static> Component<V> for List<V> {
 }
 
 impl<V: 'static> List<V> {
-    pub fn new(items: Vec<ListItem<V>>) -> Self {
+    pub fn new() -> Self {
         Self {
-            items,
             empty_message: "No items".into(),
             header: None,
             toggle: Toggle::NotToggleable,
+            children: SmallVec::new(),
         }
     }
 
@@ -511,25 +471,10 @@ impl<V: 'static> List<V> {
         self.toggle = toggle;
         self
     }
+}
 
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
-        let list_content = match (self.items.is_empty(), self.toggle) {
-            (false, _) => div().children(
-                self.items
-                    .into_iter()
-                    .enumerate()
-                    .map(|(ix, item)| item.render(view, ix, cx)),
-            ),
-            (true, Toggle::Toggled(false)) => div(),
-            (true, _) => {
-                div().child(Label::new(self.empty_message.clone()).color(TextColor::Muted))
-            }
-        };
-
-        v_stack()
-            .w_full()
-            .py_1()
-            .children(self.header.map(|header| header))
-            .child(list_content)
+impl<V: 'static> ParentElement<V> for List<V> {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
+        &mut self.children
     }
 }

crates/ui2/src/static_data.rs 🔗

@@ -7,14 +7,13 @@ use gpui::{AppContext, ViewContext};
 use rand::Rng;
 use theme2::ActiveTheme;
 
-use crate::{binding, HighlightedText};
 use crate::{
-    Buffer, BufferRow, BufferRows, Button, EditorPane, FileSystemStatus, GitStatus,
-    HighlightedLine, Icon, KeyBinding, Label, ListEntry, ListEntrySize, Livestream, MicStatus,
-    Notification, PaletteItem, Player, PlayerCallStatus, PlayerWithCallStatus, PublicPlayer,
-    ScreenShareStatus, Symbol, Tab, TextColor, Toggle, VideoStatus,
+    binding, Buffer, BufferRow, BufferRows, Button, EditorPane, FileSystemStatus, GitStatus,
+    HighlightedLine, HighlightedText, Icon, KeyBinding, Label, ListEntry, ListEntrySize,
+    Livestream, MicStatus, Notification, NotificationAction, PaletteItem, Player, PlayerCallStatus,
+    PlayerWithCallStatus, PublicPlayer, ScreenShareStatus, Symbol, Tab, TextColor, Toggle,
+    VideoStatus,
 };
-use crate::{ListItem, NotificationAction};
 
 pub fn static_tabs_example() -> Vec<Tab> {
     vec![
@@ -478,225 +477,238 @@ pub fn static_new_notification_items_2<V: 'static>() -> Vec<Notification<V>> {
     ]
 }
 
-pub fn static_project_panel_project_items<V>() -> Vec<ListItem<V>> {
+pub fn static_project_panel_project_items<V>() -> Vec<ListEntry<V>> {
     vec![
-        ListEntry::new(Label::new("zed"))
+        ListEntry::new("zed", Label::new("zed"))
             .left_icon(Icon::FolderOpen.into())
             .indent_level(0)
             .toggle(Toggle::Toggled(true)),
-        ListEntry::new(Label::new(".cargo"))
+        ListEntry::new(".cargo", Label::new(".cargo"))
             .left_icon(Icon::Folder.into())
             .indent_level(1),
-        ListEntry::new(Label::new(".config"))
+        ListEntry::new(".config", Label::new(".config"))
             .left_icon(Icon::Folder.into())
             .indent_level(1),
-        ListEntry::new(Label::new(".git").color(TextColor::Hidden))
+        ListEntry::new(".git", Label::new(".git").color(TextColor::Hidden))
             .left_icon(Icon::Folder.into())
             .indent_level(1),
-        ListEntry::new(Label::new(".cargo"))
+        ListEntry::new(".cargo", Label::new(".cargo"))
             .left_icon(Icon::Folder.into())
             .indent_level(1),
-        ListEntry::new(Label::new(".idea").color(TextColor::Hidden))
+        ListEntry::new(".idea", Label::new(".idea").color(TextColor::Hidden))
             .left_icon(Icon::Folder.into())
             .indent_level(1),
-        ListEntry::new(Label::new("assets"))
+        ListEntry::new("assets", Label::new("assets"))
             .left_icon(Icon::Folder.into())
             .indent_level(1)
             .toggle(Toggle::Toggled(true)),
-        ListEntry::new(Label::new("cargo-target").color(TextColor::Hidden))
-            .left_icon(Icon::Folder.into())
-            .indent_level(1),
-        ListEntry::new(Label::new("crates"))
+        ListEntry::new(
+            "cargo-target",
+            Label::new("cargo-target").color(TextColor::Hidden),
+        )
+        .left_icon(Icon::Folder.into())
+        .indent_level(1),
+        ListEntry::new("crates", Label::new("crates"))
             .left_icon(Icon::FolderOpen.into())
             .indent_level(1)
             .toggle(Toggle::Toggled(true)),
-        ListEntry::new(Label::new("activity_indicator"))
+        ListEntry::new("activity_indicator", Label::new("activity_indicator"))
             .left_icon(Icon::Folder.into())
             .indent_level(2),
-        ListEntry::new(Label::new("ai"))
+        ListEntry::new("ai", Label::new("ai"))
             .left_icon(Icon::Folder.into())
             .indent_level(2),
-        ListEntry::new(Label::new("audio"))
+        ListEntry::new("audio", Label::new("audio"))
             .left_icon(Icon::Folder.into())
             .indent_level(2),
-        ListEntry::new(Label::new("auto_update"))
+        ListEntry::new("auto_update", Label::new("auto_update"))
             .left_icon(Icon::Folder.into())
             .indent_level(2),
-        ListEntry::new(Label::new("breadcrumbs"))
+        ListEntry::new("breadcrumbs", Label::new("breadcrumbs"))
             .left_icon(Icon::Folder.into())
             .indent_level(2),
-        ListEntry::new(Label::new("call"))
+        ListEntry::new("call", Label::new("call"))
             .left_icon(Icon::Folder.into())
             .indent_level(2),
-        ListEntry::new(Label::new("sqlez").color(TextColor::Modified))
+        ListEntry::new("sqlez", Label::new("sqlez").color(TextColor::Modified))
             .left_icon(Icon::Folder.into())
             .indent_level(2)
             .toggle(Toggle::Toggled(false)),
-        ListEntry::new(Label::new("gpui2"))
+        ListEntry::new("gpui2", Label::new("gpui2"))
             .left_icon(Icon::FolderOpen.into())
             .indent_level(2)
             .toggle(Toggle::Toggled(true)),
-        ListEntry::new(Label::new("src"))
+        ListEntry::new("src", Label::new("src"))
             .left_icon(Icon::FolderOpen.into())
             .indent_level(3)
             .toggle(Toggle::Toggled(true)),
-        ListEntry::new(Label::new("derive_element.rs"))
+        ListEntry::new("derive_element.rs", Label::new("derive_element.rs"))
             .left_icon(Icon::FileRust.into())
             .indent_level(4),
-        ListEntry::new(Label::new("storybook").color(TextColor::Modified))
-            .left_icon(Icon::FolderOpen.into())
-            .indent_level(1)
-            .toggle(Toggle::Toggled(true)),
-        ListEntry::new(Label::new("docs").color(TextColor::Default))
+        ListEntry::new(
+            "storybook",
+            Label::new("storybook").color(TextColor::Modified),
+        )
+        .left_icon(Icon::FolderOpen.into())
+        .indent_level(1)
+        .toggle(Toggle::Toggled(true)),
+        ListEntry::new("docs", Label::new("docs").color(TextColor::Default))
             .left_icon(Icon::Folder.into())
             .indent_level(2)
             .toggle(Toggle::Toggled(true)),
-        ListEntry::new(Label::new("src").color(TextColor::Modified))
+        ListEntry::new("src", Label::new("src").color(TextColor::Modified))
             .left_icon(Icon::FolderOpen.into())
             .indent_level(3)
             .toggle(Toggle::Toggled(true)),
-        ListEntry::new(Label::new("ui").color(TextColor::Modified))
+        ListEntry::new("ui", Label::new("ui").color(TextColor::Modified))
             .left_icon(Icon::FolderOpen.into())
             .indent_level(4)
             .toggle(Toggle::Toggled(true)),
-        ListEntry::new(Label::new("component").color(TextColor::Created))
-            .left_icon(Icon::FolderOpen.into())
-            .indent_level(5)
-            .toggle(Toggle::Toggled(true)),
-        ListEntry::new(Label::new("facepile.rs").color(TextColor::Default))
-            .left_icon(Icon::FileRust.into())
-            .indent_level(6),
-        ListEntry::new(Label::new("follow_group.rs").color(TextColor::Default))
+        ListEntry::new(
+            "component",
+            Label::new("component").color(TextColor::Created),
+        )
+        .left_icon(Icon::FolderOpen.into())
+        .indent_level(5)
+        .toggle(Toggle::Toggled(true)),
+        ListEntry::new(
+            "facepile.rs",
+            Label::new("facepile.rs").color(TextColor::Default),
+        )
+        .left_icon(Icon::FileRust.into())
+        .indent_level(6),
+        ListEntry::new(
+            "follow_group.rs",
+            Label::new("follow_group.rs").color(TextColor::Default),
+        )
+        .left_icon(Icon::FileRust.into())
+        .indent_level(6),
+        ListEntry::new(
+            "list_item.rs",
+            Label::new("list_item.rs").color(TextColor::Created),
+        )
+        .left_icon(Icon::FileRust.into())
+        .indent_level(6),
+        ListEntry::new("tab.rs", Label::new("tab.rs").color(TextColor::Default))
             .left_icon(Icon::FileRust.into())
             .indent_level(6),
-        ListEntry::new(Label::new("list_item.rs").color(TextColor::Created))
-            .left_icon(Icon::FileRust.into())
-            .indent_level(6),
-        ListEntry::new(Label::new("tab.rs").color(TextColor::Default))
-            .left_icon(Icon::FileRust.into())
-            .indent_level(6),
-        ListEntry::new(Label::new("target").color(TextColor::Hidden))
+        ListEntry::new("target", Label::new("target").color(TextColor::Hidden))
             .left_icon(Icon::Folder.into())
             .indent_level(1),
-        ListEntry::new(Label::new(".dockerignore"))
+        ListEntry::new(".dockerignore", Label::new(".dockerignore"))
             .left_icon(Icon::FileGeneric.into())
             .indent_level(1),
-        ListEntry::new(Label::new(".DS_Store").color(TextColor::Hidden))
-            .left_icon(Icon::FileGeneric.into())
-            .indent_level(1),
-        ListEntry::new(Label::new("Cargo.lock"))
+        ListEntry::new(
+            ".DS_Store",
+            Label::new(".DS_Store").color(TextColor::Hidden),
+        )
+        .left_icon(Icon::FileGeneric.into())
+        .indent_level(1),
+        ListEntry::new("Cargo.lock", Label::new("Cargo.lock"))
             .left_icon(Icon::FileLock.into())
             .indent_level(1),
-        ListEntry::new(Label::new("Cargo.toml"))
+        ListEntry::new("Cargo.toml", Label::new("Cargo.toml"))
             .left_icon(Icon::FileToml.into())
             .indent_level(1),
-        ListEntry::new(Label::new("Dockerfile"))
+        ListEntry::new("Dockerfile", Label::new("Dockerfile"))
             .left_icon(Icon::FileGeneric.into())
             .indent_level(1),
-        ListEntry::new(Label::new("Procfile"))
+        ListEntry::new("Procfile", Label::new("Procfile"))
             .left_icon(Icon::FileGeneric.into())
             .indent_level(1),
-        ListEntry::new(Label::new("README.md"))
+        ListEntry::new("README.md", Label::new("README.md"))
             .left_icon(Icon::FileDoc.into())
             .indent_level(1),
     ]
-    .into_iter()
-    .map(From::from)
-    .collect()
 }
 
-pub fn static_project_panel_single_items<V>() -> Vec<ListItem<V>> {
+pub fn static_project_panel_single_items<V>() -> Vec<ListEntry<V>> {
     vec![
-        ListEntry::new(Label::new("todo.md"))
+        ListEntry::new("todo.md", Label::new("todo.md"))
             .left_icon(Icon::FileDoc.into())
             .indent_level(0),
-        ListEntry::new(Label::new("README.md"))
+        ListEntry::new("README.md", Label::new("README.md"))
             .left_icon(Icon::FileDoc.into())
             .indent_level(0),
-        ListEntry::new(Label::new("config.json"))
+        ListEntry::new("config.json", Label::new("config.json"))
             .left_icon(Icon::FileGeneric.into())
             .indent_level(0),
     ]
-    .into_iter()
-    .map(From::from)
-    .collect()
 }
 
-pub fn static_collab_panel_current_call<V>() -> Vec<ListItem<V>> {
+pub fn static_collab_panel_current_call<V>() -> Vec<ListEntry<V>> {
     vec![
-        ListEntry::new(Label::new("as-cii")).left_avatar("http://github.com/as-cii.png?s=50"),
-        ListEntry::new(Label::new("nathansobo"))
+        ListEntry::new("as-cii", Label::new("as-cii"))
+            .left_avatar("http://github.com/as-cii.png?s=50"),
+        ListEntry::new("nathansobo", Label::new("nathansobo"))
             .left_avatar("http://github.com/nathansobo.png?s=50"),
-        ListEntry::new(Label::new("maxbrunsfeld"))
+        ListEntry::new("maxbrunsfeld", Label::new("maxbrunsfeld"))
             .left_avatar("http://github.com/maxbrunsfeld.png?s=50"),
     ]
-    .into_iter()
-    .map(From::from)
-    .collect()
 }
 
-pub fn static_collab_panel_channels<V>() -> Vec<ListItem<V>> {
+pub fn static_collab_panel_channels<V>() -> Vec<ListEntry<V>> {
     vec![
-        ListEntry::new(Label::new("zed"))
+        ListEntry::new("zed", Label::new("zed"))
             .left_icon(Icon::Hash.into())
             .size(ListEntrySize::Medium)
             .indent_level(0),
-        ListEntry::new(Label::new("community"))
+        ListEntry::new("community", Label::new("community"))
             .left_icon(Icon::Hash.into())
             .size(ListEntrySize::Medium)
             .indent_level(1),
-        ListEntry::new(Label::new("dashboards"))
-            .left_icon(Icon::Hash.into())
-            .size(ListEntrySize::Medium)
-            .indent_level(2),
-        ListEntry::new(Label::new("feedback"))
+        ListEntry::new("dashboards", Label::new("dashboards"))
             .left_icon(Icon::Hash.into())
             .size(ListEntrySize::Medium)
             .indent_level(2),
-        ListEntry::new(Label::new("teams-in-channels-alpha"))
+        ListEntry::new("feedback", Label::new("feedback"))
             .left_icon(Icon::Hash.into())
             .size(ListEntrySize::Medium)
             .indent_level(2),
-        ListEntry::new(Label::new("current-projects"))
+        ListEntry::new(
+            "teams-in-channels-alpha",
+            Label::new("teams-in-channels-alpha"),
+        )
+        .left_icon(Icon::Hash.into())
+        .size(ListEntrySize::Medium)
+        .indent_level(2),
+        ListEntry::new("current-projects", Label::new("current-projects"))
             .left_icon(Icon::Hash.into())
             .size(ListEntrySize::Medium)
             .indent_level(1),
-        ListEntry::new(Label::new("codegen"))
+        ListEntry::new("codegen", Label::new("codegen"))
             .left_icon(Icon::Hash.into())
             .size(ListEntrySize::Medium)
             .indent_level(2),
-        ListEntry::new(Label::new("gpui2"))
+        ListEntry::new("gpui2", Label::new("gpui2"))
             .left_icon(Icon::Hash.into())
             .size(ListEntrySize::Medium)
             .indent_level(2),
-        ListEntry::new(Label::new("livestreaming"))
+        ListEntry::new("livestreaming", Label::new("livestreaming"))
             .left_icon(Icon::Hash.into())
             .size(ListEntrySize::Medium)
             .indent_level(2),
-        ListEntry::new(Label::new("open-source"))
+        ListEntry::new("open-source", Label::new("open-source"))
             .left_icon(Icon::Hash.into())
             .size(ListEntrySize::Medium)
             .indent_level(2),
-        ListEntry::new(Label::new("replace"))
+        ListEntry::new("replace", Label::new("replace"))
             .left_icon(Icon::Hash.into())
             .size(ListEntrySize::Medium)
             .indent_level(2),
-        ListEntry::new(Label::new("semantic-index"))
+        ListEntry::new("semantic-index", Label::new("semantic-index"))
             .left_icon(Icon::Hash.into())
             .size(ListEntrySize::Medium)
             .indent_level(2),
-        ListEntry::new(Label::new("vim"))
+        ListEntry::new("vim", Label::new("vim"))
             .left_icon(Icon::Hash.into())
             .size(ListEntrySize::Medium)
             .indent_level(2),
-        ListEntry::new(Label::new("web-tech"))
+        ListEntry::new("web-tech", Label::new("web-tech"))
             .left_icon(Icon::Hash.into())
             .size(ListEntrySize::Medium)
             .indent_level(2),
     ]
-    .into_iter()
-    .map(From::from)
-    .collect()
 }
 
 pub fn example_editor_actions() -> Vec<PaletteItem> {

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

@@ -28,41 +28,45 @@ impl<V: 'static> Component<V> for CollabPanel {
                             .border_color(cx.theme().colors().border)
                             .border_b()
                             .child(
-                                List::new(static_collab_panel_current_call())
+                                List::new()
                                     .header(
                                         ListHeader::new("CRDB")
                                             .left_icon(Icon::Hash.into())
                                             .toggle(Toggle::Toggled(true)),
                                     )
-                                    .toggle(Toggle::Toggled(true)),
+                                    .toggle(Toggle::Toggled(true))
+                                    .children(static_collab_panel_current_call()),
                             ),
                     )
                     .child(
                         v_stack().id("channels").py_1().child(
-                            List::new(static_collab_panel_channels())
+                            List::new()
                                 .header(ListHeader::new("CHANNELS").toggle(Toggle::Toggled(true)))
                                 .empty_message("No channels yet. Add a channel to get started.")
-                                .toggle(Toggle::Toggled(true)),
+                                .toggle(Toggle::Toggled(true))
+                                .children(static_collab_panel_channels()),
                         ),
                     )
                     .child(
                         v_stack().id("contacts-online").py_1().child(
-                            List::new(static_collab_panel_current_call())
+                            List::new()
                                 .header(
                                     ListHeader::new("CONTACTS – ONLINE")
                                         .toggle(Toggle::Toggled(true)),
                                 )
-                                .toggle(Toggle::Toggled(true)),
+                                .toggle(Toggle::Toggled(true))
+                                .children(static_collab_panel_current_call()),
                         ),
                     )
                     .child(
                         v_stack().id("contacts-offline").py_1().child(
-                            List::new(static_collab_panel_current_call())
+                            List::new()
                                 .header(
                                     ListHeader::new("CONTACTS – OFFLINE")
                                         .toggle(Toggle::Toggled(false)),
                                 )
-                                .toggle(Toggle::Toggled(false)),
+                                .toggle(Toggle::Toggled(false))
+                                .children(static_collab_panel_current_call()),
                         ),
                     ),
             )

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

@@ -29,14 +29,16 @@ impl<V: 'static> Component<V> for ProjectPanel {
                     .flex_col()
                     .overflow_y_scroll()
                     .child(
-                        List::new(static_project_panel_single_items())
+                        List::new()
                             .header(ListHeader::new("FILES"))
-                            .empty_message("No files in directory"),
+                            .empty_message("No files in directory")
+                            .children(static_project_panel_single_items()),
                     )
                     .child(
-                        List::new(static_project_panel_project_items())
+                        List::new()
                             .header(ListHeader::new("PROJECT"))
-                            .empty_message("No folders in directory"),
+                            .empty_message("No folders in directory")
+                            .children(static_project_panel_project_items()),
                     ),
             )
             .child(
@@ -67,14 +69,16 @@ impl ProjectPanel {
                     .flex_col()
                     .overflow_y_scroll()
                     .child(
-                        List::new(static_project_panel_single_items())
+                        List::new()
                             .header(ListHeader::new("FILES"))
-                            .empty_message("No files in directory"),
+                            .empty_message("No files in directory")
+                            .children(static_project_panel_single_items()),
                     )
                     .child(
-                        List::new(static_project_panel_project_items())
+                        List::new()
                             .header(ListHeader::new("PROJECT"))
-                            .empty_message("No folders in directory"),
+                            .empty_message("No folders in directory")
+                            .children(static_project_panel_project_items()),
                     ),
             )
             .child(

crates/workspace2/src/dock.rs 🔗

@@ -719,10 +719,10 @@ impl Render<Self> for PanelButtons {
                                     {
                                         let panel = panel.clone();
                                         menu = menu.entry(
-                                            ListEntry::new(Label::new(format!(
-                                                "Dock {}",
-                                                position.to_label()
-                                            ))),
+                                            ListEntry::new(
+                                                SharedString::from(format!("dock-{position:?}")),
+                                                Label::new(format!("Dock {}", position.to_label())),
+                                            ),
                                             move |_, cx| {
                                                 panel.set_position(position, cx);
                                             },