Checkpoint

Antonio Scandurra created

Change summary

crates/storybook2/src/stories/kitchen_sink.rs    |  3 
crates/ui2/src/components/assistant_panel.rs     | 13 +-
crates/ui2/src/components/buffer.rs              | 10 -
crates/ui2/src/components/chat_panel.rs          | 23 +++--
crates/ui2/src/components/collab_panel.rs        | 18 ++-
crates/ui2/src/components/command_palette.rs     | 18 ++-
crates/ui2/src/components/editor_pane.rs         |  2 
crates/ui2/src/components/language_selector.rs   | 18 ++-
crates/ui2/src/components/notifications_panel.rs | 12 ++
crates/ui2/src/components/palette.rs             | 15 ++-
crates/ui2/src/components/panel.rs               | 12 +-
crates/ui2/src/components/panes.rs               | 11 +-
crates/ui2/src/components/project_panel.rs       | 15 ++-
crates/ui2/src/components/recent_projects.rs     | 12 +-
crates/ui2/src/components/tab_bar.rs             | 80 +++++++++--------
crates/ui2/src/components/terminal.rs            |  2 
crates/ui2/src/components/theme_selector.rs      | 16 ++-
crates/ui2/src/components/workspace.rs           | 45 ++++-----
crates/ui2/src/prelude.rs                        |  2 
crates/ui2/src/static_data.rs                    |  8 
20 files changed, 183 insertions(+), 152 deletions(-)

Detailed changes

crates/storybook2/src/stories/kitchen_sink.rs πŸ”—

@@ -25,7 +25,8 @@ impl KitchenSinkStory {
             .collect::<Vec<_>>();
 
         Story::container(cx)
-            .overflow_y_scroll(ScrollState::default())
+            .id("kitchen-sink")
+            .overflow_y_scroll()
             .child(Story::title(cx, "Kitchen Sink"))
             .child(Story::label(cx, "Elements"))
             .child(div().flex().flex_col().children(element_stories))

crates/ui2/src/components/assistant_panel.rs πŸ”—

@@ -7,16 +7,16 @@ use crate::{Icon, IconButton, Label, Panel, PanelSide};
 
 #[derive(Element)]
 pub struct AssistantPanel<S: 'static + Send + Sync + Clone> {
+    id: ElementId,
     state_type: PhantomData<S>,
-    scroll_state: ScrollState,
     current_side: PanelSide,
 }
 
 impl<S: 'static + Send + Sync + Clone> AssistantPanel<S> {
-    pub fn new() -> Self {
+    pub fn new(id: impl Into<ElementId>) -> Self {
         Self {
+            id: id.into(),
             state_type: PhantomData,
-            scroll_state: ScrollState::default(),
             current_side: PanelSide::default(),
         }
     }
@@ -29,7 +29,7 @@ impl<S: 'static + Send + Sync + Clone> AssistantPanel<S> {
     fn render(&mut self, view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
         let color = ThemeColor::new(cx);
 
-        Panel::new(cx)
+        Panel::new(self.id.clone(), cx)
             .children(vec![div()
                 .flex()
                 .flex_col()
@@ -63,11 +63,12 @@ impl<S: 'static + Send + Sync + Clone> AssistantPanel<S> {
                 // Chat Body
                 .child(
                     div()
+                        .id("chat-body")
                         .w_full()
                         .flex()
                         .flex_col()
                         .gap_3()
-                        .overflow_y_scroll(self.scroll_state.clone())
+                        .overflow_y_scroll()
                         .child(Label::new("Is this thing on?")),
                 )
                 .into_any()])
@@ -105,7 +106,7 @@ mod stories {
             Story::container(cx)
                 .child(Story::title_for::<_, AssistantPanel<S>>(cx))
                 .child(Story::label(cx, "Default"))
-                .child(AssistantPanel::new())
+                .child(AssistantPanel::new("assistant-panel"))
         }
     }
 }

crates/ui2/src/components/buffer.rs πŸ”—

@@ -111,8 +111,8 @@ impl BufferRow {
 
 #[derive(Element, Clone)]
 pub struct Buffer<S: 'static + Send + Sync + Clone> {
+    id: ElementId,
     state_type: PhantomData<S>,
-    scroll_state: ScrollState,
     rows: Option<BufferRows>,
     readonly: bool,
     language: Option<String>,
@@ -121,10 +121,10 @@ pub struct Buffer<S: 'static + Send + Sync + Clone> {
 }
 
 impl<S: 'static + Send + Sync + Clone> Buffer<S> {
-    pub fn new() -> Self {
+    pub fn new(id: impl Into<ElementId>) -> Self {
         Self {
+            id: id.into(),
             state_type: PhantomData,
-            scroll_state: ScrollState::default(),
             rows: Some(BufferRows::default()),
             readonly: false,
             language: None,
@@ -133,10 +133,6 @@ impl<S: 'static + Send + Sync + Clone> Buffer<S> {
         }
     }
 
-    pub fn bind_scroll_state(&mut self, scroll_state: ScrollState) {
-        self.scroll_state = scroll_state;
-    }
-
     pub fn set_title<T: Into<Option<String>>>(mut self, title: T) -> Self {
         self.title = title.into();
         self

crates/ui2/src/components/chat_panel.rs πŸ”—

@@ -7,14 +7,14 @@ use crate::{Icon, IconButton, Input, Label, LabelColor};
 
 #[derive(Element)]
 pub struct ChatPanel<S: 'static + Send + Sync + Clone> {
-    scroll_state: ScrollState,
+    element_id: ElementId,
     messages: Vec<ChatMessage<S>>,
 }
 
 impl<S: 'static + Send + Sync + Clone> ChatPanel<S> {
-    pub fn new(scroll_state: ScrollState) -> Self {
+    pub fn new(element_id: impl Into<ElementId>) -> Self {
         Self {
-            scroll_state,
+            element_id: element_id.into(),
             messages: Vec::new(),
         }
     }
@@ -26,6 +26,7 @@ impl<S: 'static + Send + Sync + Clone> ChatPanel<S> {
 
     fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
         div()
+            .id(self.element_id.clone())
             .flex()
             .flex_col()
             .justify_between()
@@ -55,11 +56,12 @@ impl<S: 'static + Send + Sync + Clone> ChatPanel<S> {
                     // Chat Body
                     .child(
                         div()
+                            .id("chat-body")
                             .w_full()
                             .flex()
                             .flex_col()
                             .gap_3()
-                            .overflow_y_scroll(self.scroll_state.clone())
+                            .overflow_y_scroll()
                             .children(self.messages.clone()),
                     )
                     // Composer
@@ -135,10 +137,13 @@ mod stories {
             Story::container(cx)
                 .child(Story::title_for::<_, ChatPanel<S>>(cx))
                 .child(Story::label(cx, "Default"))
-                .child(Panel::new(cx).child(ChatPanel::new(ScrollState::default())))
-                .child(Story::label(cx, "With Mesages"))
                 .child(
-                    Panel::new(cx).child(ChatPanel::new(ScrollState::default()).messages(vec![
+                    Panel::new("chat-panel-1-outer", cx)
+                        .child(ChatPanel::new("chat-panel-1-inner")),
+                )
+                .child(Story::label(cx, "With Mesages"))
+                .child(Panel::new("chat-panel-2-outer", cx).child(
+                    ChatPanel::new("chat-panel-2-inner").messages(vec![
                         ChatMessage::new(
                             "osiewicz".to_string(),
                             "is this thing on?".to_string(),
@@ -153,8 +158,8 @@ mod stories {
                                 .unwrap()
                                 .naive_local(),
                         ),
-                    ])),
-                )
+                    ]),
+                ))
         }
     }
 }

crates/ui2/src/components/collab_panel.rs πŸ”—

@@ -8,15 +8,15 @@ use std::marker::PhantomData;
 
 #[derive(Element)]
 pub struct CollabPanel<S: 'static + Send + Sync + Clone> {
+    id: ElementId,
     state_type: PhantomData<S>,
-    scroll_state: ScrollState,
 }
 
 impl<S: 'static + Send + Sync + Clone> CollabPanel<S> {
-    pub fn new(scroll_state: ScrollState) -> Self {
+    pub fn new(id: impl Into<ElementId>) -> Self {
         Self {
+            id: id.into(),
             state_type: PhantomData,
-            scroll_state,
         }
     }
 
@@ -25,12 +25,14 @@ impl<S: 'static + Send + Sync + Clone> CollabPanel<S> {
         let color = ThemeColor::new(cx);
 
         v_stack()
+            .id(self.id.clone())
             .h_full()
             .bg(color.surface)
             .child(
                 v_stack()
+                    .id("crdb")
                     .w_full()
-                    .overflow_y_scroll(self.scroll_state.clone())
+                    .overflow_y_scroll()
                     .child(
                         div().pb_1().border_color(color.border).border_b().child(
                             List::new(static_collab_panel_current_call())
@@ -43,7 +45,7 @@ impl<S: 'static + Send + Sync + Clone> CollabPanel<S> {
                         ),
                     )
                     .child(
-                        v_stack().py_1().child(
+                        v_stack().id("channels").py_1().child(
                             List::new(static_collab_panel_channels())
                                 .header(
                                     ListHeader::new("CHANNELS").set_toggle(ToggleState::Toggled),
@@ -53,7 +55,7 @@ impl<S: 'static + Send + Sync + Clone> CollabPanel<S> {
                         ),
                     )
                     .child(
-                        v_stack().py_1().child(
+                        v_stack().id("contacts-online").py_1().child(
                             List::new(static_collab_panel_current_call())
                                 .header(
                                     ListHeader::new("CONTACTS – ONLINE")
@@ -63,7 +65,7 @@ impl<S: 'static + Send + Sync + Clone> CollabPanel<S> {
                         ),
                     )
                     .child(
-                        v_stack().py_1().child(
+                        v_stack().id("contacts-offline").py_1().child(
                             List::new(static_collab_panel_current_call())
                                 .header(
                                     ListHeader::new("CONTACTS – OFFLINE")
@@ -182,7 +184,7 @@ mod stories {
             Story::container(cx)
                 .child(Story::title_for::<_, CollabPanel<S>>(cx))
                 .child(Story::label(cx, "Default"))
-                .child(CollabPanel::new(ScrollState::default()))
+                .child(CollabPanel::new("collab-panel"))
         }
     }
 }

crates/ui2/src/components/command_palette.rs πŸ”—

@@ -5,21 +5,21 @@ use crate::{example_editor_actions, OrderMethod, Palette};
 
 #[derive(Element)]
 pub struct CommandPalette<S: 'static + Send + Sync + Clone> {
+    id: ElementId,
     state_type: PhantomData<S>,
-    scroll_state: ScrollState,
 }
 
 impl<S: 'static + Send + Sync + Clone> CommandPalette<S> {
-    pub fn new(scroll_state: ScrollState) -> Self {
+    pub fn new(id: impl Into<ElementId>) -> Self {
         Self {
+            id: id.into(),
             state_type: PhantomData,
-            scroll_state,
         }
     }
 
     fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
-        div().child(
-            Palette::new(self.scroll_state.clone())
+        div().id(self.id.clone()).child(
+            Palette::new("palette")
                 .items(example_editor_actions())
                 .placeholder("Execute a command...")
                 .empty_string("No items found.")
@@ -49,11 +49,15 @@ mod stories {
             }
         }
 
-        fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
+        fn render(
+            &mut self,
+            _view: &mut S,
+            cx: &mut ViewContext<S>,
+        ) -> impl Element<ViewState = S> {
             Story::container(cx)
                 .child(Story::title_for::<_, CommandPalette<S>>(cx))
                 .child(Story::label(cx, "Default"))
-                .child(CommandPalette::new(ScrollState::default()))
+                .child(CommandPalette::new("command-palette"))
         }
     }
 }

crates/ui2/src/components/editor_pane.rs πŸ”—

@@ -56,7 +56,7 @@ impl EditorPane {
             .w_full()
             .h_full()
             .flex_1()
-            .child(TabBar::new(self.tabs.clone()).can_navigate((false, true)))
+            .child(TabBar::new("editor-pane-tabs", self.tabs.clone()).can_navigate((false, true)))
             .child(
                 Toolbar::new()
                     .left_item(Breadcrumb::new(self.path.clone(), self.symbols.clone()))

crates/ui2/src/components/language_selector.rs πŸ”—

@@ -5,21 +5,21 @@ use crate::{OrderMethod, Palette, PaletteItem};
 
 #[derive(Element)]
 pub struct LanguageSelector<S: 'static + Send + Sync + Clone> {
+    id: ElementId,
     state_type: PhantomData<S>,
-    scroll_state: ScrollState,
 }
 
 impl<S: 'static + Send + Sync + Clone> LanguageSelector<S> {
-    pub fn new() -> Self {
+    pub fn new(id: impl Into<ElementId>) -> Self {
         Self {
+            id: id.into(),
             state_type: PhantomData,
-            scroll_state: ScrollState::default(),
         }
     }
 
     fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
-        div().child(
-            Palette::new(self.scroll_state.clone())
+        div().id(self.id.clone()).child(
+            Palette::new("palette")
                 .items(vec![
                     PaletteItem::new("C"),
                     PaletteItem::new("C++"),
@@ -60,11 +60,15 @@ mod stories {
             }
         }
 
-        fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
+        fn render(
+            &mut self,
+            _view: &mut S,
+            cx: &mut ViewContext<S>,
+        ) -> impl Element<ViewState = S> {
             Story::container(cx)
                 .child(Story::title_for::<_, LanguageSelector<S>>(cx))
                 .child(Story::label(cx, "Default"))
-                .child(LanguageSelector::new())
+                .child(LanguageSelector::new("language-selector"))
         }
     }
 }

crates/ui2/src/components/notifications_panel.rs πŸ”—

@@ -5,12 +5,14 @@ use crate::{List, ListHeader};
 
 #[derive(Element)]
 pub struct NotificationsPanel<S: 'static + Send + Sync + Clone> {
+    id: ElementId,
     state_type: PhantomData<S>,
 }
 
 impl<S: 'static + Send + Sync + Clone> NotificationsPanel<S> {
-    pub fn new() -> Self {
+    pub fn new(id: impl Into<ElementId>) -> Self {
         Self {
+            id: id.into(),
             state_type: PhantomData,
         }
     }
@@ -19,6 +21,7 @@ impl<S: 'static + Send + Sync + Clone> NotificationsPanel<S> {
         let color = ThemeColor::new(cx);
 
         div()
+            .id(self.id.clone())
             .flex()
             .flex_col()
             .w_full()
@@ -26,10 +29,11 @@ impl<S: 'static + Send + Sync + Clone> NotificationsPanel<S> {
             .bg(color.surface)
             .child(
                 div()
+                    .id("header")
                     .w_full()
                     .flex()
                     .flex_col()
-                    .overflow_y_scroll(ScrollState::default())
+                    .overflow_y_scroll()
                     .child(
                         List::new(static_new_notification_items())
                             .header(ListHeader::new("NEW").set_toggle(ToggleState::Toggled))
@@ -74,7 +78,9 @@ mod stories {
             Story::container(cx)
                 .child(Story::title_for::<_, NotificationsPanel<S>>(cx))
                 .child(Story::label(cx, "Default"))
-                .child(Panel::new(cx).child(NotificationsPanel::new()))
+                .child(
+                    Panel::new("panel", cx).child(NotificationsPanel::new("notifications_panel")),
+                )
         }
     }
 }

crates/ui2/src/components/palette.rs πŸ”—

@@ -5,8 +5,8 @@ use crate::{h_stack, v_stack, Keybinding, Label, LabelColor};
 
 #[derive(Element)]
 pub struct Palette<S: 'static + Send + Sync + Clone> {
+    id: ElementId,
     state_type: PhantomData<S>,
-    scroll_state: ScrollState,
     input_placeholder: SharedString,
     empty_string: SharedString,
     items: Vec<PaletteItem<S>>,
@@ -14,10 +14,10 @@ pub struct Palette<S: 'static + Send + Sync + Clone> {
 }
 
 impl<S: 'static + Send + Sync + Clone> Palette<S> {
-    pub fn new(scroll_state: ScrollState) -> Self {
+    pub fn new(id: impl Into<ElementId>) -> Self {
         Self {
+            id: id.into(),
             state_type: PhantomData,
-            scroll_state,
             input_placeholder: "Find something...".into(),
             empty_string: "No items found.".into(),
             items: vec![],
@@ -50,6 +50,7 @@ impl<S: 'static + Send + Sync + Clone> Palette<S> {
         let color = ThemeColor::new(cx);
 
         v_stack()
+            .id(self.id.clone())
             .w_96()
             .rounded_lg()
             .bg(color.elevated_surface)
@@ -64,11 +65,12 @@ impl<S: 'static + Send + Sync + Clone> Palette<S> {
                     .child(div().h_px().w_full().bg(color.filled_element))
                     .child(
                         v_stack()
+                            .id("items")
                             .py_0p5()
                             .px_1()
                             .grow()
                             .max_h_96()
-                            .overflow_y_scroll(self.scroll_state.clone())
+                            .overflow_y_scroll()
                             .children(
                                 vec![if self.items.is_empty() {
                                     Some(
@@ -150,6 +152,7 @@ impl<S: 'static + Send + Sync + Clone> PaletteItem<S> {
     }
 }
 
+use gpui3::ElementId;
 #[cfg(feature = "stories")]
 pub use stories::*;
 
@@ -179,10 +182,10 @@ mod stories {
             Story::container(cx)
                 .child(Story::title_for::<_, Palette<S>>(cx))
                 .child(Story::label(cx, "Default"))
-                .child(Palette::new(ScrollState::default()))
+                .child(Palette::new("palette-1"))
                 .child(Story::label(cx, "With Items"))
                 .child(
-                    Palette::new(ScrollState::default())
+                    Palette::new("palette-2")
                         .placeholder("Execute a command...")
                         .items(vec![
                             PaletteItem::new("theme selector: toggle").keybinding(

crates/ui2/src/components/panel.rs πŸ”—

@@ -42,8 +42,8 @@ use std::collections::HashSet;
 
 #[derive(Element)]
 pub struct Panel<S: 'static + Send + Sync> {
+    id: ElementId,
     state_type: PhantomData<S>,
-    scroll_state: ScrollState,
     current_side: PanelSide,
     /// Defaults to PanelAllowedSides::LeftAndRight
     allowed_sides: PanelAllowedSides,
@@ -53,12 +53,12 @@ pub struct Panel<S: 'static + Send + Sync> {
 }
 
 impl<S: 'static + Send + Sync> Panel<S> {
-    pub fn new(cx: &mut WindowContext) -> Self {
+    pub fn new(id: impl Into<ElementId>, cx: &mut WindowContext) -> Self {
         let settings = user_settings(cx);
 
         Self {
+            id: id.into(),
             state_type: PhantomData,
-            scroll_state: ScrollState::default(),
             current_side: PanelSide::default(),
             allowed_sides: PanelAllowedSides::default(),
             initial_width: *settings.default_panel_size,
@@ -102,6 +102,7 @@ impl<S: 'static + Send + Sync> Panel<S> {
         let current_size = self.width.unwrap_or(self.initial_width);
 
         v_stack()
+            .id(self.id.clone())
             .flex_initial()
             .when(
                 self.current_side == PanelSide::Left || self.current_side == PanelSide::Right,
@@ -156,9 +157,10 @@ mod stories {
                 .child(Story::title_for::<_, Panel<S>>(cx))
                 .child(Story::label(cx, "Default"))
                 .child(
-                    Panel::new(cx).child(
+                    Panel::new("panel", cx).child(
                         div()
-                            .overflow_y_scroll(ScrollState::default())
+                            .id("panel-contents")
+                            .overflow_y_scroll()
                             .children((0..100).map(|ix| Label::new(format!("Item {}", ix + 1)))),
                     ),
                 )

crates/ui2/src/components/panes.rs πŸ”—

@@ -1,6 +1,6 @@
 use std::marker::PhantomData;
 
-use gpui3::{hsla, AnyElement, Hsla, Length, Size};
+use gpui3::{hsla, AnyElement, ElementId, Hsla, Length, Size};
 use smallvec::SmallVec;
 
 use crate::prelude::*;
@@ -14,21 +14,21 @@ pub enum SplitDirection {
 
 #[derive(Element)]
 pub struct Pane<S: 'static + Send + Sync> {
+    id: ElementId,
     state_type: PhantomData<S>,
-    scroll_state: ScrollState,
     size: Size<Length>,
     fill: Hsla,
     children: SmallVec<[AnyElement<S>; 2]>,
 }
 
 impl<S: 'static + Send + Sync> Pane<S> {
-    pub fn new(scroll_state: ScrollState, size: Size<Length>) -> Self {
+    pub fn new(id: impl Into<ElementId>, size: Size<Length>) -> Self {
         // Fill is only here for debugging purposes, remove before release
         let system_color = SystemColor::new();
 
         Self {
+            id: id.into(),
             state_type: PhantomData,
-            scroll_state,
             size,
             fill: hsla(0.3, 0.3, 0.3, 1.),
             // fill: system_color.transparent,
@@ -45,12 +45,13 @@ impl<S: 'static + Send + Sync> Pane<S> {
         let color = ThemeColor::new(cx);
 
         div()
+            .id(self.id.clone())
             .flex()
             .flex_initial()
             .bg(self.fill)
             .w(self.size.width)
             .h(self.size.height)
-            .overflow_y_scroll(self.scroll_state.clone())
+            .overflow_y_scroll()
             .children(self.children.drain(..))
     }
 }

crates/ui2/src/components/project_panel.rs πŸ”—

@@ -7,15 +7,15 @@ use crate::{
 
 #[derive(Element)]
 pub struct ProjectPanel<S: 'static + Send + Sync + Clone> {
+    id: ElementId,
     state_type: PhantomData<S>,
-    scroll_state: ScrollState,
 }
 
 impl<S: 'static + Send + Sync + Clone> ProjectPanel<S> {
-    pub fn new(scroll_state: ScrollState) -> Self {
+    pub fn new(id: impl Into<ElementId>) -> Self {
         Self {
+            id: id.into(),
             state_type: PhantomData,
-            scroll_state,
         }
     }
 
@@ -24,6 +24,7 @@ impl<S: 'static + Send + Sync + Clone> ProjectPanel<S> {
         let color = ThemeColor::new(cx);
 
         div()
+            .id(self.id.clone())
             .flex()
             .flex_col()
             .w_full()
@@ -31,10 +32,11 @@ impl<S: 'static + Send + Sync + Clone> ProjectPanel<S> {
             .bg(color.surface)
             .child(
                 div()
+                    .id("project-panel-contents")
                     .w_full()
                     .flex()
                     .flex_col()
-                    .overflow_y_scroll(ScrollState::default())
+                    .overflow_y_scroll()
                     .child(
                         List::new(static_project_panel_single_items())
                             .header(ListHeader::new("FILES").set_toggle(ToggleState::Toggled))
@@ -56,6 +58,7 @@ impl<S: 'static + Send + Sync + Clone> ProjectPanel<S> {
     }
 }
 
+use gpui3::ElementId;
 #[cfg(feature = "stories")]
 pub use stories::*;
 
@@ -86,8 +89,8 @@ mod stories {
                 .child(Story::title_for::<_, ProjectPanel<S>>(cx))
                 .child(Story::label(cx, "Default"))
                 .child(
-                    Panel::new(cx)
-                        .child(ProjectPanel::new(ScrollState::default())),
+                    Panel::new("project-panel-outer", cx)
+                        .child(ProjectPanel::new("project-panel-inner")),
                 )
         }
     }

crates/ui2/src/components/recent_projects.rs πŸ”—

@@ -5,21 +5,21 @@ use crate::{OrderMethod, Palette, PaletteItem};
 
 #[derive(Element)]
 pub struct RecentProjects<S: 'static + Send + Sync + Clone> {
+    id: ElementId,
     state_type: PhantomData<S>,
-    scroll_state: ScrollState,
 }
 
 impl<S: 'static + Send + Sync + Clone> RecentProjects<S> {
-    pub fn new() -> Self {
+    pub fn new(id: impl Into<ElementId>) -> Self {
         Self {
+            id: id.into(),
             state_type: PhantomData,
-            scroll_state: ScrollState::default(),
         }
     }
 
     fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
-        div().child(
-            Palette::new(self.scroll_state.clone())
+        div().id(self.id.clone()).child(
+            Palette::new("palette")
                 .items(vec![
                     PaletteItem::new("zed").sublabel(SharedString::from("~/projects/zed")),
                     PaletteItem::new("saga").sublabel(SharedString::from("~/projects/saga")),
@@ -64,7 +64,7 @@ mod stories {
             Story::container(cx)
                 .child(Story::title_for::<_, RecentProjects<S>>(cx))
                 .child(Story::label(cx, "Default"))
-                .child(RecentProjects::new())
+                .child(RecentProjects::new("recent-projects"))
         }
     }
 }

crates/ui2/src/components/tab_bar.rs πŸ”—

@@ -5,27 +5,23 @@ use crate::{Icon, IconButton, Tab};
 
 #[derive(Element)]
 pub struct TabBar<S: 'static + Send + Sync + Clone> {
+    id: ElementId,
     state_type: PhantomData<S>,
-    scroll_state: ScrollState,
     /// Backwards, Forwards
     can_navigate: (bool, bool),
     tabs: Vec<Tab<S>>,
 }
 
 impl<S: 'static + Send + Sync + Clone> TabBar<S> {
-    pub fn new(tabs: Vec<Tab<S>>) -> Self {
+    pub fn new(id: impl Into<ElementId>, tabs: Vec<Tab<S>>) -> Self {
         Self {
+            id: id.into(),
             state_type: PhantomData,
-            scroll_state: ScrollState::default(),
             can_navigate: (false, false),
             tabs,
         }
     }
 
-    pub fn bind_scroll_state(&mut self, scroll_state: ScrollState) {
-        self.scroll_state = scroll_state;
-    }
-
     pub fn can_navigate(mut self, can_navigate: (bool, bool)) -> Self {
         self.can_navigate = can_navigate;
         self
@@ -37,6 +33,7 @@ impl<S: 'static + Send + Sync + Clone> TabBar<S> {
         let (can_navigate_back, can_navigate_forward) = self.can_navigate;
 
         div()
+            .id(self.id.clone())
             .w_full()
             .flex()
             .bg(color.tab_bar)
@@ -67,8 +64,9 @@ impl<S: 'static + Send + Sync + Clone> TabBar<S> {
             .child(
                 div().w_0().flex_1().h_full().child(
                     div()
+                        .id("tabs")
                         .flex()
-                        .overflow_x_scroll(self.scroll_state.clone())
+                        .overflow_x_scroll()
                         .children(self.tabs.clone()),
                 ),
             )
@@ -92,6 +90,7 @@ impl<S: 'static + Send + Sync + Clone> TabBar<S> {
     }
 }
 
+use gpui3::ElementId;
 #[cfg(feature = "stories")]
 pub use stories::*;
 
@@ -121,37 +120,40 @@ mod stories {
             Story::container(cx)
                 .child(Story::title_for::<_, TabBar<S>>(cx))
                 .child(Story::label(cx, "Default"))
-                .child(TabBar::new(vec![
-                    Tab::new(1)
-                        .title("Cargo.toml".to_string())
-                        .current(false)
-                        .git_status(GitStatus::Modified),
-                    Tab::new(2)
-                        .title("Channels Panel".to_string())
-                        .current(false),
-                    Tab::new(3)
-                        .title("channels_panel.rs".to_string())
-                        .current(true)
-                        .git_status(GitStatus::Modified),
-                    Tab::new(4)
-                        .title("workspace.rs".to_string())
-                        .current(false)
-                        .git_status(GitStatus::Modified),
-                    Tab::new(5)
-                        .title("icon_button.rs".to_string())
-                        .current(false),
-                    Tab::new(6)
-                        .title("storybook.rs".to_string())
-                        .current(false)
-                        .git_status(GitStatus::Created),
-                    Tab::new(7).title("theme.rs".to_string()).current(false),
-                    Tab::new(8)
-                        .title("theme_registry.rs".to_string())
-                        .current(false),
-                    Tab::new(9)
-                        .title("styleable_helpers.rs".to_string())
-                        .current(false),
-                ]))
+                .child(TabBar::new(
+                    "tab-bar",
+                    vec![
+                        Tab::new(1)
+                            .title("Cargo.toml".to_string())
+                            .current(false)
+                            .git_status(GitStatus::Modified),
+                        Tab::new(2)
+                            .title("Channels Panel".to_string())
+                            .current(false),
+                        Tab::new(3)
+                            .title("channels_panel.rs".to_string())
+                            .current(true)
+                            .git_status(GitStatus::Modified),
+                        Tab::new(4)
+                            .title("workspace.rs".to_string())
+                            .current(false)
+                            .git_status(GitStatus::Modified),
+                        Tab::new(5)
+                            .title("icon_button.rs".to_string())
+                            .current(false),
+                        Tab::new(6)
+                            .title("storybook.rs".to_string())
+                            .current(false)
+                            .git_status(GitStatus::Created),
+                        Tab::new(7).title("theme.rs".to_string()).current(false),
+                        Tab::new(8)
+                            .title("theme_registry.rs".to_string())
+                            .current(false),
+                        Tab::new(9)
+                            .title("styleable_helpers.rs".to_string())
+                            .current(false),
+                    ],
+                ))
         }
     }
 }

crates/ui2/src/components/terminal.rs πŸ”—

@@ -73,7 +73,7 @@ impl<S: 'static + Send + Sync + Clone> Terminal<S> {
             // Terminal Pane.
             .child(
                 Pane::new(
-                    ScrollState::default(),
+                    "terminal",
                     Size {
                         width: relative(1.).into(),
                         height: rems(36.).into(),

crates/ui2/src/components/theme_selector.rs πŸ”—

@@ -5,21 +5,21 @@ use crate::{OrderMethod, Palette, PaletteItem};
 
 #[derive(Element)]
 pub struct ThemeSelector<S: 'static + Send + Sync + Clone> {
+    id: ElementId,
     state_type: PhantomData<S>,
-    scroll_state: ScrollState,
 }
 
 impl<S: 'static + Send + Sync + Clone> ThemeSelector<S> {
-    pub fn new() -> Self {
+    pub fn new(id: impl Into<ElementId>) -> Self {
         Self {
+            id: id.into(),
             state_type: PhantomData,
-            scroll_state: ScrollState::default(),
         }
     }
 
     fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
         div().child(
-            Palette::new(self.scroll_state.clone())
+            Palette::new(self.id.clone())
                 .items(vec![
                     PaletteItem::new("One Dark"),
                     PaletteItem::new("RosΓ© Pine"),
@@ -61,11 +61,15 @@ mod stories {
             }
         }
 
-        fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
+        fn render(
+            &mut self,
+            _view: &mut S,
+            cx: &mut ViewContext<S>,
+        ) -> impl Element<ViewState = S> {
             Story::container(cx)
                 .child(Story::title_for::<_, ThemeSelector<S>>(cx))
                 .child(Story::label(cx, "Default"))
-                .child(ThemeSelector::new())
+                .child(ThemeSelector::new("theme-selector"))
         }
     }
 }

crates/ui2/src/components/workspace.rs πŸ”—

@@ -3,7 +3,7 @@ use std::sync::Arc;
 use chrono::DateTime;
 use gpui3::{px, relative, rems, view, Context, Size, View};
 
-use crate::{prelude::*, NotificationToast, NotificationsPanel};
+use crate::{prelude::*, NotificationsPanel};
 use crate::{
     static_livestream, theme, user_settings_mut, v_stack, AssistantPanel, Button, ChatMessage,
     ChatPanel, CollabPanel, EditorPane, FakeSettings, Label, LanguageSelector, Pane, PaneGroup,
@@ -40,10 +40,6 @@ pub struct Workspace {
     show_terminal: bool,
     show_debug: bool,
     show_language_selector: bool,
-    left_panel_scroll_state: ScrollState,
-    right_panel_scroll_state: ScrollState,
-    tab_bar_scroll_state: ScrollState,
-    bottom_panel_scroll_state: ScrollState,
     debug: Gpui2UiDebug,
 }
 
@@ -60,10 +56,6 @@ impl Workspace {
             show_language_selector: false,
             show_debug: false,
             show_notifications_panel: true,
-            left_panel_scroll_state: ScrollState::default(),
-            right_panel_scroll_state: ScrollState::default(),
-            tab_bar_scroll_state: ScrollState::default(),
-            bottom_panel_scroll_state: ScrollState::default(),
             debug: Gpui2UiDebug::default(),
         }
     }
@@ -201,7 +193,7 @@ impl Workspace {
 
         let root_group = PaneGroup::new_panes(
             vec![Pane::new(
-                ScrollState::default(),
+                "pane-0",
                 Size {
                     width: relative(1.).into(),
                     height: relative(1.).into(),
@@ -235,16 +227,16 @@ impl Workspace {
                     .border_color(theme.lowest.base.default.border)
                     .children(
                         Some(
-                            Panel::new(cx)
+                            Panel::new("project-panel-outer", cx)
                                 .side(PanelSide::Left)
-                                .child(ProjectPanel::new(ScrollState::default())),
+                                .child(ProjectPanel::new("project-panel-inner")),
                         )
                         .filter(|_| self.is_project_panel_open()),
                     )
                     .children(
                         Some(
-                            Panel::new(cx)
-                                .child(CollabPanel::new(ScrollState::default()))
+                            Panel::new("collab-panel-outer", cx)
+                                .child(CollabPanel::new("collab-panel-inner"))
                                 .side(PanelSide::Left),
                         )
                         .filter(|_| self.is_collab_panel_open()),
@@ -259,7 +251,7 @@ impl Workspace {
                             .child(div().flex().flex_1().child(root_group))
                             .children(
                                 Some(
-                                    Panel::new(cx)
+                                    Panel::new("terminal-panel", cx)
                                         .child(Terminal::new())
                                         .allowed_sides(PanelAllowedSides::BottomOnly)
                                         .side(PanelSide::Bottom),
@@ -268,8 +260,10 @@ impl Workspace {
                             ),
                     )
                     .children(
-                        Some(Panel::new(cx).side(PanelSide::Right).child(
-                            ChatPanel::new(ScrollState::default()).messages(vec![
+                        Some(
+                            Panel::new("chat-panel-outer", cx)
+                                .side(PanelSide::Right)
+                                .child(ChatPanel::new("chat-panel-inner").messages(vec![
                                     ChatMessage::new(
                                         "osiewicz".to_string(),
                                         "is this thing on?".to_string(),
@@ -284,21 +278,24 @@ impl Workspace {
                                             .unwrap()
                                             .naive_local(),
                                     ),
-                                ]),
-                        ))
+                                ])),
+                        )
                         .filter(|_| self.is_chat_panel_open()),
                     )
                     .children(
                         Some(
-                            Panel::new(cx)
+                            Panel::new("notifications-panel-outer", cx)
                                 .side(PanelSide::Right)
-                                .child(NotificationsPanel::new()),
+                                .child(NotificationsPanel::new("notifications-panel-inner")),
                         )
                         .filter(|_| self.is_notifications_panel_open()),
                     )
                     .children(
-                        Some(Panel::new(cx).child(AssistantPanel::new()))
-                            .filter(|_| self.is_assistant_panel_open()),
+                        Some(
+                            Panel::new("assistant-panel-outer", cx)
+                                .child(AssistantPanel::new("assistant-panel-inner")),
+                        )
+                        .filter(|_| self.is_assistant_panel_open()),
                     ),
             )
             .child(StatusBar::new())
@@ -312,7 +309,7 @@ impl Workspace {
                         .top(px(50.))
                         .left(px(640.))
                         .z_index(8)
-                        .child(LanguageSelector::new()),
+                        .child(LanguageSelector::new("language-selector")),
                 )
                 .filter(|_| self.is_language_selector_open()),
             )

crates/ui2/src/prelude.rs πŸ”—

@@ -1,5 +1,5 @@
 pub use gpui3::{
-    div, Element, IntoAnyElement, ParentElement, ScrollState, SharedString, StatefulInteractive,
+    div, Element, ElementId, IntoAnyElement, ParentElement, SharedString, StatefulInteractive,
     StatelessInteractive, Styled, ViewContext, WindowContext,
 };
 

crates/ui2/src/static_data.rs πŸ”—

@@ -638,7 +638,7 @@ pub fn empty_editor_example(cx: &mut WindowContext) -> EditorPane {
 }
 
 pub fn empty_buffer_example<S: 'static + Send + Sync + Clone>() -> Buffer<S> {
-    Buffer::new().set_rows(Some(BufferRows::default()))
+    Buffer::new("empty-buffer").set_rows(Some(BufferRows::default()))
 }
 
 pub fn hello_world_rust_editor_example(cx: &mut WindowContext) -> EditorPane {
@@ -665,7 +665,7 @@ pub fn hello_world_rust_editor_example(cx: &mut WindowContext) -> EditorPane {
 pub fn hello_world_rust_buffer_example<S: 'static + Send + Sync + Clone>(
     color: &ThemeColor,
 ) -> Buffer<S> {
-    Buffer::new()
+    Buffer::new("hello-world-rust-buffer")
         .set_title("hello_world.rs".to_string())
         .set_path("src/hello_world.rs".to_string())
         .set_language("rust".to_string())
@@ -806,7 +806,7 @@ pub fn hello_world_rust_editor_with_status_example(cx: &mut WindowContext) -> Ed
 pub fn hello_world_rust_buffer_with_status_example<S: 'static + Send + Sync + Clone>(
     color: &ThemeColor,
 ) -> Buffer<S> {
-    Buffer::new()
+    Buffer::new("hello-world-rust-buffer-with-status")
         .set_title("hello_world.rs".to_string())
         .set_path("src/hello_world.rs".to_string())
         .set_language("rust".to_string())
@@ -952,7 +952,7 @@ pub fn hello_world_rust_with_status_buffer_rows(color: &ThemeColor) -> Vec<Buffe
 }
 
 pub fn terminal_buffer<S: 'static + Send + Sync + Clone>(color: &ThemeColor) -> Buffer<S> {
-    Buffer::new()
+    Buffer::new("terminal")
         .set_title("zed β€” fish".to_string())
         .set_rows(Some(BufferRows {
             show_line_numbers: false,