Togglable channels, the greatest since sliced bread

Conrad Irwin created

Change summary

crates/collab_ui2/src/collab_panel.rs    | 31 ++++++++++++++-----------
crates/ui2/src/components/disclosure.rs  | 25 +++++++++++++++-----
crates/ui2/src/components/icon_button.rs | 15 ++++++++++-
crates/ui2/src/components/list.rs        | 16 ++++++++++--
4 files changed, 61 insertions(+), 26 deletions(-)

Detailed changes

crates/collab_ui2/src/collab_panel.rs 🔗

@@ -2233,20 +2233,20 @@ impl CollabPanel {
     //         self.toggle_channel_collapsed(action.location, cx);
     //     }
 
-    //     fn toggle_channel_collapsed<'a>(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
-    //         match self.collapsed_channels.binary_search(&channel_id) {
-    //             Ok(ix) => {
-    //                 self.collapsed_channels.remove(ix);
-    //             }
-    //             Err(ix) => {
-    //                 self.collapsed_channels.insert(ix, channel_id);
-    //             }
-    //         };
-    //         self.serialize(cx);
-    //         self.update_entries(true, cx);
-    //         cx.notify();
-    //         cx.focus_self();
-    //     }
+    fn toggle_channel_collapsed<'a>(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
+        match self.collapsed_channels.binary_search(&channel_id) {
+            Ok(ix) => {
+                self.collapsed_channels.remove(ix);
+            }
+            Err(ix) => {
+                self.collapsed_channels.insert(ix, channel_id);
+            }
+        };
+        // self.serialize(cx); todo!()
+        self.update_entries(true, cx);
+        cx.notify();
+        cx.focus_self();
+    }
 
     fn is_channel_collapsed(&self, channel_id: ChannelId) -> bool {
         self.collapsed_channels.binary_search(&channel_id).is_ok()
@@ -3129,6 +3129,9 @@ impl CollabPanel {
                 } else {
                     Toggle::NotToggleable
                 })
+                .on_toggle(
+                    cx.listener(move |this, _, cx| this.toggle_channel_collapsed(channel_id, cx)),
+                )
                 .on_click(cx.listener(move |this, _, cx| {
                     if this.drag_target_channel == ChannelDragTarget::None {
                         if is_active {

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

@@ -1,19 +1,30 @@
-use gpui::{div, Element, ParentElement};
+use std::rc::Rc;
 
-use crate::{Color, Icon, IconElement, IconSize, Toggle};
+use gpui::{div, Element, IntoElement, MouseDownEvent, ParentElement, WindowContext};
 
-pub fn disclosure_control(toggle: Toggle) -> impl Element {
+use crate::{Color, Icon, IconButton, IconSize, Toggle};
+
+pub fn disclosure_control(
+    toggle: Toggle,
+    on_toggle: Option<Rc<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
+) -> impl Element {
     match (toggle.is_toggleable(), toggle.is_toggled()) {
         (false, _) => div(),
         (_, true) => div().child(
-            IconElement::new(Icon::ChevronDown)
+            IconButton::new("toggle", Icon::ChevronDown)
                 .color(Color::Muted)
-                .size(IconSize::Small),
+                .size(IconSize::Small)
+                .when_some(on_toggle, move |el, on_toggle| {
+                    el.on_click(move |e, cx| on_toggle(e, cx))
+                }),
         ),
         (_, false) => div().child(
-            IconElement::new(Icon::ChevronRight)
+            IconButton::new("toggle", Icon::ChevronRight)
                 .color(Color::Muted)
-                .size(IconSize::Small),
+                .size(IconSize::Small)
+                .when_some(on_toggle, move |el, on_toggle| {
+                    el.on_click(move |e, cx| on_toggle(e, cx))
+                }),
         ),
     }
 }

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

@@ -1,4 +1,4 @@
-use crate::{h_stack, prelude::*, Icon, IconElement};
+use crate::{h_stack, prelude::*, Icon, IconElement, IconSize};
 use gpui::{prelude::*, Action, AnyView, Div, MouseButton, MouseDownEvent, Stateful};
 
 #[derive(IntoElement)]
@@ -6,6 +6,7 @@ pub struct IconButton {
     id: ElementId,
     icon: Icon,
     color: Color,
+    size: IconSize,
     variant: ButtonVariant,
     state: InteractionState,
     selected: bool,
@@ -50,7 +51,11 @@ impl RenderOnce for IconButton {
             // place we use an icon button.
             // .hover(|style| style.bg(bg_hover_color))
             .active(|style| style.bg(bg_active_color))
-            .child(IconElement::new(self.icon).color(icon_color));
+            .child(
+                IconElement::new(self.icon)
+                    .size(self.size)
+                    .color(icon_color),
+            );
 
         if let Some(click_handler) = self.on_mouse_down {
             button = button.on_mouse_down(MouseButton::Left, move |event, cx| {
@@ -76,6 +81,7 @@ impl IconButton {
             id: id.into(),
             icon,
             color: Color::default(),
+            size: Default::default(),
             variant: ButtonVariant::default(),
             state: InteractionState::default(),
             selected: false,
@@ -94,6 +100,11 @@ impl IconButton {
         self
     }
 
+    pub fn size(mut self, size: IconSize) -> Self {
+        self.size = size;
+        self
+    }
+
     pub fn variant(mut self, variant: ButtonVariant) -> Self {
         self.variant = variant;
         self

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

@@ -63,7 +63,7 @@ impl RenderOnce for ListHeader {
     type Rendered = Div;
 
     fn render(self, cx: &mut WindowContext) -> Self::Rendered {
-        let disclosure_control = disclosure_control(self.toggle);
+        let disclosure_control = disclosure_control(self.toggle, None);
 
         let meta = match self.meta {
             Some(ListHeaderMeta::Tools(icons)) => div().child(
@@ -177,6 +177,7 @@ pub struct ListItem {
     toggle: Toggle,
     inset: bool,
     on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
+    on_toggle: Option<Rc<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
     on_secondary_mouse_down: Option<Rc<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
     children: SmallVec<[AnyElement; 2]>,
 }
@@ -193,6 +194,7 @@ impl ListItem {
             inset: false,
             on_click: None,
             on_secondary_mouse_down: None,
+            on_toggle: None,
             children: SmallVec::new(),
         }
     }
@@ -230,6 +232,14 @@ impl ListItem {
         self
     }
 
+    pub fn on_toggle(
+        mut self,
+        on_toggle: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
+    ) -> Self {
+        self.on_toggle = Some(Rc::new(on_toggle));
+        self
+    }
+
     pub fn selected(mut self, selected: bool) -> Self {
         self.selected = selected;
         self
@@ -283,7 +293,7 @@ impl RenderOnce for ListItem {
                 this.bg(cx.theme().colors().ghost_element_selected)
             })
             .when_some(self.on_click.clone(), |this, on_click| {
-                this.on_click(move |event, cx| {
+                this.cursor_pointer().on_click(move |event, cx| {
                     // HACK: GPUI currently fires `on_click` with any mouse button,
                     // but we only care about the left button.
                     if event.down.button == MouseButton::Left {
@@ -304,7 +314,7 @@ impl RenderOnce for ListItem {
                     .gap_1()
                     .items_center()
                     .relative()
-                    .child(disclosure_control(self.toggle))
+                    .child(disclosure_control(self.toggle, self.on_toggle))
                     .children(left_content)
                     .children(self.children)
                     // HACK: We need to attach the `on_click` handler to the child element in order to have the click