WIP: Add disclosable channels

Mikayla created

Change summary

crates/collab_ui/src/collab_panel.rs  | 91 ++++++++++++++++++++++++++++
crates/gpui/src/elements.rs           |  7 ++
crates/gpui/src/elements/component.rs |  7 ++
crates/search/src/search.rs           | 21 +++---
crates/theme/src/components.rs        | 46 +++++++-------
5 files changed, 136 insertions(+), 36 deletions(-)

Detailed changes

crates/collab_ui/src/collab_panel.rs 🔗

@@ -8,6 +8,7 @@ use client::{
     proto::PeerId, Channel, ChannelEvent, ChannelId, ChannelStore, Client, Contact, User, UserStore,
 };
 
+use components::DisclosureExt;
 use context_menu::{ContextMenu, ContextMenuItem};
 use db::kvp::KEY_VALUE_STORE;
 use editor::{Cancel, Editor};
@@ -16,7 +17,7 @@ use fuzzy::{match_strings, StringMatchCandidate};
 use gpui::{
     actions,
     elements::{
-        Canvas, ChildView, Empty, Flex, Image, Label, List, ListOffset, ListState,
+        Canvas, ChildView, Component, Empty, Flex, Image, Label, List, ListOffset, ListState,
         MouseEventHandler, Orientation, OverlayPositionMode, Padding, ParentElement, Stack, Svg,
     },
     geometry::{
@@ -1615,6 +1616,10 @@ impl CollabPanel {
             this.deploy_channel_context_menu(Some(e.position), channel_id, cx);
         })
         .with_cursor_style(CursorStyle::PointingHand)
+        .component()
+        .styleable()
+        .disclosable()
+        .into_element()
         .into_any()
     }
 
@@ -2522,3 +2527,87 @@ fn render_icon_button(style: &IconButton, svg_path: &'static str) -> impl Elemen
         .contained()
         .with_style(style.container)
 }
+
+mod components {
+
+    use gpui::{
+        elements::{Empty, Flex, GeneralComponent, ParentElement, StyleableComponent},
+        Action, Element,
+    };
+    use theme::components::{
+        action_button::ActionButton, svg::Svg, ComponentExt, ToggleIconButtonStyle,
+    };
+
+    #[derive(Clone)]
+    struct DisclosureStyle<S> {
+        disclosure: ToggleIconButtonStyle,
+        spacing: f32,
+        content: S,
+    }
+
+    struct Disclosable<C, S> {
+        disclosed: bool,
+        action: Box<dyn Action>,
+        content: C,
+        style: S,
+    }
+
+    impl Disclosable<(), ()> {
+        fn new<C>(disclosed: bool, content: C, action: Box<dyn Action>) -> Disclosable<C, ()> {
+            Disclosable {
+                disclosed,
+                content,
+                action,
+                style: (),
+            }
+        }
+    }
+
+    impl<C: StyleableComponent> StyleableComponent for Disclosable<C, ()> {
+        type Style = DisclosureStyle<C::Style>;
+
+        type Output = Disclosable<C, Self::Style>;
+
+        fn with_style(self, style: Self::Style) -> Self::Output {
+            Disclosable {
+                disclosed: self.disclosed,
+                action: self.action,
+                content: self.content,
+                style,
+            }
+        }
+    }
+
+    impl<C: StyleableComponent> GeneralComponent for Disclosable<C, DisclosureStyle<C::Style>> {
+        fn render<V: gpui::View>(
+            self,
+            v: &mut V,
+            cx: &mut gpui::ViewContext<V>,
+        ) -> gpui::AnyElement<V> {
+            Flex::row()
+                .with_child(
+                    ActionButton::new_dynamic(self.action)
+                        .with_contents(Svg::new("path"))
+                        .toggleable(self.disclosed)
+                        .with_style(self.style.disclosure)
+                        .element(),
+                )
+                .with_child(Empty::new().constrained().with_width(self.style.spacing))
+                .with_child(self.content.with_style(self.style.content).render(v, cx))
+                .align_children_center()
+                .into_any()
+        }
+    }
+
+    pub trait DisclosureExt {
+        fn disclosable(self, disclosed: bool, action: Box<dyn Action>) -> Disclosable<Self, ()>
+        where
+            Self: Sized;
+    }
+
+    impl<C: StyleableComponent> DisclosureExt for C {
+        fn disclosable(self, disclosed: bool, action: Box<dyn Action>) -> Disclosable<Self, ()> {
+            Disclosable::new(disclosed, self, action)
+        }
+    }
+}

crates/gpui/src/elements.rs 🔗

@@ -229,6 +229,13 @@ pub trait Element<V: View>: 'static {
     {
         MouseEventHandler::for_child::<Tag>(self.into_any(), region_id)
     }
+
+    fn component(self) -> ElementAdapter<V>
+    where
+        Self: Sized,
+    {
+        ElementAdapter::new(self.into_any())
+    }
 }
 
 pub trait RenderElement {

crates/gpui/src/elements/component.rs 🔗

@@ -50,6 +50,13 @@ pub trait Component<V: View> {
     {
         ComponentAdapter::new(self)
     }
+
+    fn styleable(self) -> StylableComponentAdapter<Self, V>
+    where
+        Self: Sized,
+    {
+        StylableComponentAdapter::new(self)
+    }
 }
 
 impl<V: View, C: GeneralComponent> Component<V> for C {

crates/search/src/search.rs 🔗

@@ -8,7 +8,9 @@ use gpui::{
 pub use mode::SearchMode;
 use project::search::SearchQuery;
 pub use project_search::{ProjectSearchBar, ProjectSearchView};
-use theme::components::{action_button::ActionButton, ComponentExt, ToggleIconButtonStyle};
+use theme::components::{
+    action_button::ActionButton, svg::Svg, ComponentExt, ToggleIconButtonStyle,
+};
 
 pub mod buffer_search;
 mod history;
@@ -89,15 +91,12 @@ impl SearchOptions {
         tooltip_style: TooltipStyle,
         button_style: ToggleIconButtonStyle,
     ) -> AnyElement<V> {
-        ActionButton::new_dynamic(
-            self.to_toggle_action(),
-            format!("Toggle {}", self.label()),
-            tooltip_style,
-        )
-        .with_contents(theme::components::svg::Svg::new(self.icon()))
-        .toggleable(active)
-        .with_style(button_style)
-        .element()
-        .into_any()
+        ActionButton::new_dynamic(self.to_toggle_action())
+            .with_tooltip(format!("Toggle {}", self.label()), tooltip_style)
+            .with_contents(Svg::new(self.icon()))
+            .toggleable(active)
+            .with_style(button_style)
+            .element()
+            .into_any()
     }
 }

crates/theme/src/components.rs 🔗

@@ -81,8 +81,7 @@ pub mod action_button {
 
     pub struct ActionButton<C, S> {
         action: Box<dyn Action>,
-        tooltip: Cow<'static, str>,
-        tooltip_style: TooltipStyle,
+        tooltip: Option<(Cow<'static, str>, TooltipStyle)>,
         tag: TypeTag,
         contents: C,
         style: Interactive<S>,
@@ -99,27 +98,27 @@ pub mod action_button {
     }
 
     impl ActionButton<(), ()> {
-        pub fn new_dynamic(
-            action: Box<dyn Action>,
-            tooltip: impl Into<Cow<'static, str>>,
-            tooltip_style: TooltipStyle,
-        ) -> Self {
+        pub fn new_dynamic(action: Box<dyn Action>) -> Self {
             Self {
                 contents: (),
                 tag: action.type_tag(),
                 style: Interactive::new_blank(),
-                tooltip: tooltip.into(),
-                tooltip_style,
+                tooltip: None,
                 action,
             }
         }
 
-        pub fn new<A: Action + Clone>(
-            action: A,
+        pub fn new<A: Action + Clone>(action: A) -> Self {
+            Self::new_dynamic(Box::new(action))
+        }
+
+        pub fn with_tooltip(
+            mut self,
             tooltip: impl Into<Cow<'static, str>>,
             tooltip_style: TooltipStyle,
         ) -> Self {
-            Self::new_dynamic(Box::new(action), tooltip, tooltip_style)
+            self.tooltip = Some((tooltip.into(), tooltip_style));
+            self
         }
 
         pub fn with_contents<C: StyleableComponent>(self, contents: C) -> ActionButton<C, ()> {
@@ -128,7 +127,6 @@ pub mod action_button {
                 tag: self.tag,
                 style: self.style,
                 tooltip: self.tooltip,
-                tooltip_style: self.tooltip_style,
                 contents,
             }
         }
@@ -144,7 +142,7 @@ pub mod action_button {
                 tag: self.tag,
                 contents: self.contents,
                 tooltip: self.tooltip,
-                tooltip_style: self.tooltip_style,
+
                 style,
             }
         }
@@ -152,7 +150,7 @@ pub mod action_button {
 
     impl<C: StyleableComponent> GeneralComponent for ActionButton<C, ButtonStyle<C::Style>> {
         fn render<V: View>(self, v: &mut V, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
-            MouseEventHandler::new_dynamic(self.tag, 0, cx, |state, cx| {
+            let mut button = MouseEventHandler::new_dynamic(self.tag, 0, cx, |state, cx| {
                 let style = self.style.style_for(state);
                 let mut contents = self
                     .contents
@@ -180,15 +178,15 @@ pub mod action_button {
                 }
             })
             .with_cursor_style(CursorStyle::PointingHand)
-            .with_dynamic_tooltip(
-                self.tag,
-                0,
-                self.tooltip,
-                Some(self.action),
-                self.tooltip_style,
-                cx,
-            )
-            .into_any()
+            .into_any();
+
+            if let Some((tooltip, style)) = self.tooltip {
+                button = button
+                    .with_dynamic_tooltip(self.tag, 0, tooltip, Some(self.action), style, cx)
+                    .into_any()
+            }
+
+            button
         }
     }
 }