diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index c49011b86b..16de60e735 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/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 { + disclosure: ToggleIconButtonStyle, + spacing: f32, + content: S, + } + + struct Disclosable { + disclosed: bool, + action: Box, + content: C, + style: S, + } + + impl Disclosable<(), ()> { + fn new(disclosed: bool, content: C, action: Box) -> Disclosable { + Disclosable { + disclosed, + content, + action, + style: (), + } + } + } + + impl StyleableComponent for Disclosable { + type Style = DisclosureStyle; + + type Output = Disclosable; + + fn with_style(self, style: Self::Style) -> Self::Output { + Disclosable { + disclosed: self.disclosed, + action: self.action, + content: self.content, + style, + } + } + } + + impl GeneralComponent for Disclosable> { + fn render( + self, + v: &mut V, + cx: &mut gpui::ViewContext, + ) -> gpui::AnyElement { + 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) -> Disclosable + where + Self: Sized; + } + + impl DisclosureExt for C { + fn disclosable(self, disclosed: bool, action: Box) -> Disclosable { + Disclosable::new(disclosed, self, action) + } + } +} diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index 03caae8dd9..f7697d6fc1 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -229,6 +229,13 @@ pub trait Element: 'static { { MouseEventHandler::for_child::(self.into_any(), region_id) } + + fn component(self) -> ElementAdapter + where + Self: Sized, + { + ElementAdapter::new(self.into_any()) + } } pub trait RenderElement { diff --git a/crates/gpui/src/elements/component.rs b/crates/gpui/src/elements/component.rs index e2770c0148..ee4702a6fa 100644 --- a/crates/gpui/src/elements/component.rs +++ b/crates/gpui/src/elements/component.rs @@ -50,6 +50,13 @@ pub trait Component { { ComponentAdapter::new(self) } + + fn styleable(self) -> StylableComponentAdapter + where + Self: Sized, + { + StylableComponentAdapter::new(self) + } } impl Component for C { diff --git a/crates/search/src/search.rs b/crates/search/src/search.rs index 8d8c02c8d7..2dc45e3973 100644 --- a/crates/search/src/search.rs +++ b/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 { - 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() } } diff --git a/crates/theme/src/components.rs b/crates/theme/src/components.rs index fce7ad825c..1e395405cb 100644 --- a/crates/theme/src/components.rs +++ b/crates/theme/src/components.rs @@ -81,8 +81,7 @@ pub mod action_button { pub struct ActionButton { action: Box, - tooltip: Cow<'static, str>, - tooltip_style: TooltipStyle, + tooltip: Option<(Cow<'static, str>, TooltipStyle)>, tag: TypeTag, contents: C, style: Interactive, @@ -99,27 +98,27 @@ pub mod action_button { } impl ActionButton<(), ()> { - pub fn new_dynamic( - action: Box, - tooltip: impl Into>, - tooltip_style: TooltipStyle, - ) -> Self { + pub fn new_dynamic(action: Box) -> Self { Self { contents: (), tag: action.type_tag(), style: Interactive::new_blank(), - tooltip: tooltip.into(), - tooltip_style, + tooltip: None, action, } } - pub fn new( - action: A, + pub fn new(action: A) -> Self { + Self::new_dynamic(Box::new(action)) + } + + pub fn with_tooltip( + mut self, tooltip: impl Into>, 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(self, contents: C) -> ActionButton { @@ -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 GeneralComponent for ActionButton> { fn render(self, v: &mut V, cx: &mut gpui::ViewContext) -> gpui::AnyElement { - 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 } } }