diff --git a/crates/client/src/channel_store.rs b/crates/client/src/channel_store.rs index 03d334a9de..6352ac791e 100644 --- a/crates/client/src/channel_store.rs +++ b/crates/client/src/channel_store.rs @@ -114,6 +114,16 @@ impl ChannelStore { } } + pub fn has_children(&self, channel_id: ChannelId) -> bool { + self.channel_paths.iter().any(|path| { + if let Some(ix) = path.iter().position(|id| *id == channel_id) { + path.len() > ix + 1 + } else { + false + } + }) + } + pub fn channel_count(&self) -> usize { self.channel_paths.len() } diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index f35c07d5aa..41c87094d2 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -16,9 +16,9 @@ use fuzzy::{match_strings, StringMatchCandidate}; use gpui::{ actions, elements::{ - Canvas, ChildView, Empty, Flex, GeneralComponent, GeneralStyleableComponent, Image, Label, - List, ListOffset, ListState, MouseEventHandler, Orientation, OverlayPositionMode, Padding, - ParentElement, Stack, Svg, + Canvas, ChildView, Component, Empty, Flex, Image, Label, List, ListOffset, ListState, + MouseEventHandler, Orientation, OverlayPositionMode, Padding, ParentElement, Stack, + StyleableComponent, Svg, }, geometry::{ rect::RectF, @@ -36,7 +36,7 @@ use serde_derive::{Deserialize, Serialize}; use settings::SettingsStore; use staff_mode::StaffMode; use std::{borrow::Cow, mem, sync::Arc}; -use theme::IconButton; +use theme::{components::ComponentExt, IconButton}; use util::{iife, ResultExt, TryFutureExt}; use workspace::{ dock::{DockPosition, Panel}, @@ -44,10 +44,7 @@ use workspace::{ Workspace, }; -use crate::{ - collab_panel::components::{DisclosureExt, DisclosureStyle}, - face_pile::FacePile, -}; +use crate::face_pile::FacePile; use channel_modal::ChannelModal; use self::contact_finder::ContactFinder; @@ -57,6 +54,11 @@ struct RemoveChannel { channel_id: u64, } +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +struct ToggleCollapsed { + channel_id: u64, +} + #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] struct NewChannel { channel_id: u64, @@ -86,7 +88,8 @@ impl_actions!( NewChannel, InviteMembers, ManageMembers, - RenameChannel + RenameChannel, + ToggleCollapsed ] ); @@ -109,6 +112,7 @@ pub fn init(_client: Arc, cx: &mut AppContext) { cx.add_action(CollabPanel::manage_members); cx.add_action(CollabPanel::rename_selected_channel); cx.add_action(CollabPanel::rename_channel); + cx.add_action(CollabPanel::toggle_channel_collapsed); } #[derive(Debug)] @@ -151,6 +155,7 @@ pub struct CollabPanel { list_state: ListState, subscriptions: Vec, collapsed_sections: Vec
, + collapsed_channels: Vec, workspace: WeakViewHandle, context_menu_on_selected: bool, } @@ -402,6 +407,7 @@ impl CollabPanel { subscriptions: Vec::default(), match_candidates: Vec::default(), collapsed_sections: vec![Section::Offline], + collapsed_channels: Vec::default(), workspace: workspace.weak_handle(), client: workspace.app_state().client.clone(), context_menu_on_selected: true, @@ -661,10 +667,24 @@ impl CollabPanel { self.entries.push(ListEntry::ChannelEditor { depth: 0 }); } } + let mut collapse_depth = None; for mat in matches { let (depth, channel) = channel_store.channel_at_index(mat.candidate_id).unwrap(); + if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) { + collapse_depth = Some(depth); + } else if let Some(collapsed_depth) = collapse_depth { + if depth > collapsed_depth { + continue; + } + if self.is_channel_collapsed(channel.id) { + collapse_depth = Some(depth); + } else { + collapse_depth = None; + } + } + match &self.channel_editing_state { Some(ChannelEditingState::Create { parent_id, .. }) if *parent_id == Some(channel.id) => @@ -1483,6 +1503,11 @@ impl CollabPanel { cx: &AppContext, ) -> AnyElement { Flex::row() + .with_child( + Empty::new() + .constrained() + .with_width(theme.collab_panel.disclosure.button_space()), + ) .with_child( Svg::new("icons/hash.svg") .with_color(theme.collab_panel.channel_hash.color) @@ -1541,6 +1566,10 @@ impl CollabPanel { cx: &mut ViewContext, ) -> AnyElement { let channel_id = channel.id; + let has_children = self.channel_store.read(cx).has_children(channel_id); + let disclosed = + has_children.then(|| !self.collapsed_channels.binary_search(&channel_id).is_ok()); + let is_active = iife!({ let call_channel = ActiveCall::global(cx) .read(cx) @@ -1554,7 +1583,7 @@ impl CollabPanel { const FACEPILE_LIMIT: usize = 3; MouseEventHandler::new::(channel.id as usize, cx, |state, cx| { - Flex::row() + Flex::::row() .with_child( Svg::new("icons/hash.svg") .with_color(theme.channel_hash.color) @@ -1603,6 +1632,14 @@ impl CollabPanel { } }) .align_children_center() + .styleable_component() + .disclosable( + disclosed, + Box::new(ToggleCollapsed { channel_id }), + channel_id as usize, + ) + .with_style(theme.disclosure.clone()) + .element() .constrained() .with_height(theme.row_height) .contained() @@ -1619,17 +1656,6 @@ impl CollabPanel { this.deploy_channel_context_menu(Some(e.position), channel_id, cx); }) .with_cursor_style(CursorStyle::PointingHand) - .dynamic_component() - .stylable() - .disclosable(true, Box::new(RemoveChannel { channel_id: 0 })) - .with_style({ - fn style() -> DisclosureStyle<()> { - todo!() - } - - style() - }) - .element() .into_any() } @@ -2024,6 +2050,24 @@ impl CollabPanel { self.update_entries(false, cx); } + fn toggle_channel_collapsed(&mut self, action: &ToggleCollapsed, cx: &mut ViewContext) { + let channel_id = action.channel_id; + 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.update_entries(false, cx); + cx.notify(); + } + + fn is_channel_collapsed(&self, channel: ChannelId) -> bool { + self.collapsed_channels.binary_search(&channel).is_ok() + } + fn leave_call(cx: &mut ViewContext) { ActiveCall::global(cx) .update(cx, |call, cx| call.hang_up(cx)) @@ -2537,87 +2581,3 @@ 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, GeneralStyleableComponent, ParentElement}, - Action, Element, - }; - use theme::components::{ - action_button::ActionButton, svg::Svg, ComponentExt, ToggleIconButtonStyle, - }; - - #[derive(Clone)] - pub struct DisclosureStyle { - disclosure: ToggleIconButtonStyle, - spacing: f32, - content: S, - } - - pub 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 GeneralStyleableComponent 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/examples/components.rs b/crates/gpui/examples/components.rs index aeeab8101d..5aa648bd65 100644 --- a/crates/gpui/examples/components.rs +++ b/crates/gpui/examples/components.rs @@ -2,7 +2,7 @@ use button_component::Button; use gpui::{ color::Color, - elements::{Component, ContainerStyle, Flex, Label, ParentElement}, + elements::{ContainerStyle, Flex, Label, ParentElement, StatefulComponent}, fonts::{self, TextStyle}, platform::WindowOptions, AnyElement, App, Element, Entity, View, ViewContext, @@ -72,7 +72,7 @@ impl View for TestView { TextStyle::for_color(Color::blue()), ) .with_style(ButtonStyle::fill(Color::yellow())) - .c_element(), + .stateful_element(), ) .with_child( ToggleableButton::new(self.is_doubling, move |_, v: &mut Self, cx| { @@ -84,7 +84,7 @@ impl View for TestView { inactive: ButtonStyle::fill(Color::red()), active: ButtonStyle::fill(Color::green()), }) - .c_element(), + .stateful_element(), ) .expanded() .contained() @@ -114,7 +114,7 @@ mod theme { // Component creation: mod toggleable_button { use gpui::{ - elements::{Component, ContainerStyle, LabelStyle}, + elements::{ContainerStyle, LabelStyle, StatefulComponent}, scene::MouseClick, EventContext, View, }; @@ -156,7 +156,7 @@ mod toggleable_button { } } - impl Component for ToggleableButton { + impl StatefulComponent for ToggleableButton { fn render(self, v: &mut V, cx: &mut gpui::ViewContext) -> gpui::AnyElement { let button = if let Some(style) = self.style { self.button.with_style(*style.style_for(self.active)) @@ -171,7 +171,7 @@ mod toggleable_button { mod button_component { use gpui::{ - elements::{Component, ContainerStyle, Label, LabelStyle, MouseEventHandler}, + elements::{ContainerStyle, Label, LabelStyle, MouseEventHandler, StatefulComponent}, platform::MouseButton, scene::MouseClick, AnyElement, Element, EventContext, TypeTag, View, ViewContext, @@ -212,7 +212,7 @@ mod button_component { } } - impl Component for Button { + impl StatefulComponent for Button { fn render(self, _: &mut V, cx: &mut ViewContext) -> AnyElement { let click_handler = self.click_handler; diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index c0484ef939..4fcf3815f5 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -230,19 +230,26 @@ pub trait Element: 'static { MouseEventHandler::for_child::(self.into_any(), region_id) } - fn component(self) -> ElementAdapter + fn stateful_component(self) -> ElementAdapter where Self: Sized, { ElementAdapter::new(self.into_any()) } - fn dynamic_component(self) -> DynamicElementAdapter + fn component(self) -> DynamicElementAdapter where Self: Sized, { DynamicElementAdapter::new(self.into_any()) } + + fn styleable_component(self) -> StylableAdapter + where + Self: Sized, + { + DynamicElementAdapter::new(self.into_any()).stylable() + } } pub trait RenderElement { diff --git a/crates/gpui/src/elements/component.rs b/crates/gpui/src/elements/component.rs index f5a4180d7f..703d6eca07 100644 --- a/crates/gpui/src/elements/component.rs +++ b/crates/gpui/src/elements/component.rs @@ -9,35 +9,35 @@ use crate::{ use super::Empty; -pub trait GeneralComponent { +pub trait Component { fn render(self, v: &mut V, cx: &mut ViewContext) -> AnyElement; - fn element(self) -> ComponentAdapter + fn element(self) -> StatefulAdapter where Self: Sized, { - ComponentAdapter::new(self) + StatefulAdapter::new(self) } - fn stylable(self) -> GeneralStylableComponentAdapter + fn stylable(self) -> StylableAdapter where Self: Sized, { - GeneralStylableComponentAdapter::new(self) + StylableAdapter::new(self) } } -pub struct GeneralStylableComponentAdapter { +pub struct StylableAdapter { component: C, } -impl GeneralStylableComponentAdapter { +impl StylableAdapter { pub fn new(component: C) -> Self { Self { component } } } -impl GeneralStyleableComponent for GeneralStylableComponentAdapter { +impl StyleableComponent for StylableAdapter { type Style = (); type Output = C; @@ -47,20 +47,20 @@ impl GeneralStyleableComponent for GeneralStylableComponent } } -pub trait GeneralStyleableComponent { +pub trait StyleableComponent { type Style: Clone; - type Output: GeneralComponent; + type Output: Component; fn with_style(self, style: Self::Style) -> Self::Output; } -impl GeneralComponent for () { +impl Component for () { fn render(self, _: &mut V, _: &mut ViewContext) -> AnyElement { Empty::new().into_any() } } -impl GeneralStyleableComponent for () { +impl StyleableComponent for () { type Style = (); type Output = (); @@ -69,54 +69,54 @@ impl GeneralStyleableComponent for () { } } -pub trait StyleableComponent { +pub trait StatefulStyleableComponent { type Style: Clone; - type Output: Component; + type Output: StatefulComponent; - fn c_with_style(self, style: Self::Style) -> Self::Output; + fn stateful_with_style(self, style: Self::Style) -> Self::Output; } -impl StyleableComponent for C { +impl StatefulStyleableComponent for C { type Style = C::Style; type Output = C::Output; - fn c_with_style(self, style: Self::Style) -> Self::Output { + fn stateful_with_style(self, style: Self::Style) -> Self::Output { self.with_style(style) } } -pub trait Component { +pub trait StatefulComponent { fn render(self, v: &mut V, cx: &mut ViewContext) -> AnyElement; - fn c_element(self) -> ComponentAdapter + fn stateful_element(self) -> StatefulAdapter where Self: Sized, { - ComponentAdapter::new(self) + StatefulAdapter::new(self) } - fn c_styleable(self) -> StylableComponentAdapter + fn stateful_styleable(self) -> StatefulStylableAdapter where Self: Sized, { - StylableComponentAdapter::new(self) + StatefulStylableAdapter::new(self) } } -impl Component for C { +impl StatefulComponent for C { fn render(self, v: &mut V, cx: &mut ViewContext) -> AnyElement { self.render(v, cx) } } // StylableComponent -> Component -pub struct StylableComponentAdapter, V: View> { +pub struct StatefulStylableAdapter, V: View> { component: C, phantom: std::marker::PhantomData, } -impl, V: View> StylableComponentAdapter { +impl, V: View> StatefulStylableAdapter { pub fn new(component: C) -> Self { Self { component, @@ -125,12 +125,14 @@ impl, V: View> StylableComponentAdapter { } } -impl, V: View> StyleableComponent for StylableComponentAdapter { +impl, V: View> StatefulStyleableComponent + for StatefulStylableAdapter +{ type Style = (); type Output = C; - fn c_with_style(self, _: Self::Style) -> Self::Output { + fn stateful_with_style(self, _: Self::Style) -> Self::Output { self.component } } @@ -149,7 +151,7 @@ impl DynamicElementAdapter { } } -impl GeneralComponent for DynamicElementAdapter { +impl Component for DynamicElementAdapter { fn render(self, _: &mut V, _: &mut ViewContext) -> AnyElement { let element = self .element @@ -174,20 +176,20 @@ impl ElementAdapter { } } -impl Component for ElementAdapter { +impl StatefulComponent for ElementAdapter { fn render(self, _: &mut V, _: &mut ViewContext) -> AnyElement { self.element } } // Component -> Element -pub struct ComponentAdapter { +pub struct StatefulAdapter { component: Option, element: Option>, phantom: PhantomData, } -impl ComponentAdapter { +impl StatefulAdapter { pub fn new(e: E) -> Self { Self { component: Some(e), @@ -197,7 +199,7 @@ impl ComponentAdapter { } } -impl + 'static> Element for ComponentAdapter { +impl + 'static> Element for StatefulAdapter { type LayoutState = (); type PaintState = (); diff --git a/crates/gpui/src/elements/container.rs b/crates/gpui/src/elements/container.rs index bb1366b4e7..68d3e57610 100644 --- a/crates/gpui/src/elements/container.rs +++ b/crates/gpui/src/elements/container.rs @@ -45,6 +45,14 @@ impl ContainerStyle { ..Default::default() } } + + pub fn additional_length(&self) -> f32 { + self.padding.left + + self.padding.right + + self.border.width * 2. + + self.margin.left + + self.margin.right + } } pub struct Container { diff --git a/crates/search/src/search.rs b/crates/search/src/search.rs index b3167efe50..2dc45e3973 100644 --- a/crates/search/src/search.rs +++ b/crates/search/src/search.rs @@ -2,7 +2,7 @@ use bitflags::bitflags; pub use buffer_search::BufferSearchBar; use gpui::{ actions, - elements::{Component, GeneralStyleableComponent, TooltipStyle}, + elements::{Component, StyleableComponent, TooltipStyle}, Action, AnyElement, AppContext, Element, View, }; pub use mode::SearchMode; @@ -96,7 +96,7 @@ impl SearchOptions { .with_contents(Svg::new(self.icon())) .toggleable(active) .with_style(button_style) - .c_element() + .element() .into_any() } } diff --git a/crates/theme/src/components.rs b/crates/theme/src/components.rs index 3679b1fa4f..6f9242199f 100644 --- a/crates/theme/src/components.rs +++ b/crates/theme/src/components.rs @@ -1,23 +1,147 @@ -use gpui::elements::GeneralStyleableComponent; +use gpui::{elements::StyleableComponent, Action}; use crate::{Interactive, Toggleable}; -use self::{action_button::ButtonStyle, svg::SvgStyle, toggle::Toggle}; +use self::{action_button::ButtonStyle, disclosure::Disclosable, svg::SvgStyle, toggle::Toggle}; pub type ToggleIconButtonStyle = Toggleable>>; -pub trait ComponentExt { +pub trait ComponentExt { fn toggleable(self, active: bool) -> Toggle; + fn disclosable( + self, + disclosed: Option, + action: Box, + id: usize, + ) -> Disclosable; } -impl ComponentExt for C { +impl ComponentExt for C { fn toggleable(self, active: bool) -> Toggle { Toggle::new(self, active) } + + /// Some(True) => disclosed => content is visible + /// Some(false) => closed => content is hidden + /// None => No disclosure button, but reserve spacing + fn disclosable( + self, + disclosed: Option, + action: Box, + id: usize, + ) -> Disclosable { + Disclosable::new(disclosed, self, action, id) + } +} + +pub mod disclosure { + + use gpui::{ + elements::{Component, Empty, Flex, ParentElement, StyleableComponent}, + Action, Element, + }; + use schemars::JsonSchema; + use serde_derive::Deserialize; + + use super::{action_button::ActionButton, svg::Svg, ComponentExt, ToggleIconButtonStyle}; + + #[derive(Clone, Default, Deserialize, JsonSchema)] + pub struct DisclosureStyle { + pub button: ToggleIconButtonStyle, + pub spacing: f32, + #[serde(flatten)] + content: S, + } + + impl DisclosureStyle { + pub fn button_space(&self) -> f32 { + self.spacing + self.button.button_width.unwrap() + } + } + + pub struct Disclosable { + disclosed: Option, + action: Box, + id: usize, + content: C, + style: S, + } + + impl Disclosable<(), ()> { + pub fn new( + disclosed: Option, + content: C, + action: Box, + id: usize, + ) -> Disclosable { + Disclosable { + disclosed, + content, + action, + id, + 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, + id: self.id, + style, + } + } + } + + impl Component for Disclosable> { + fn render( + self, + v: &mut V, + cx: &mut gpui::ViewContext, + ) -> gpui::AnyElement { + Flex::row() + .with_child(if let Some(disclosed) = self.disclosed { + ActionButton::new_dynamic(self.action) + .with_id(self.id) + .with_contents(Svg::new(if disclosed { + "icons/file_icons/chevron_down.svg" + } else { + "icons/file_icons/chevron_right.svg" + })) + .toggleable(disclosed) + .with_style(self.style.button) + .element() + .into_any() + } else { + Empty::new() + .into_any() + .constrained() + // TODO: Why is this optional at all? + .with_width(self.style.button.button_width.unwrap()) + .into_any() + }) + .with_child(Empty::new().constrained().with_width(self.style.spacing)) + .with_child( + self.content + .with_style(self.style.content) + .render(v, cx) + .flex(1., true), + ) + .align_children_center() + .into_any() + } + } } pub mod toggle { - use gpui::elements::{GeneralComponent, GeneralStyleableComponent}; + use gpui::elements::{Component, StyleableComponent}; use crate::Toggleable; @@ -27,7 +151,7 @@ pub mod toggle { component: C, } - impl Toggle { + impl Toggle { pub fn new(component: C, active: bool) -> Self { Toggle { active, @@ -37,7 +161,7 @@ pub mod toggle { } } - impl GeneralStyleableComponent for Toggle { + impl StyleableComponent for Toggle { type Style = Toggleable; type Output = Toggle; @@ -51,7 +175,7 @@ pub mod toggle { } } - impl GeneralComponent for Toggle> { + impl Component for Toggle> { fn render( self, v: &mut V, @@ -69,8 +193,7 @@ pub mod action_button { use gpui::{ elements::{ - ContainerStyle, GeneralComponent, GeneralStyleableComponent, MouseEventHandler, - TooltipStyle, + Component, ContainerStyle, MouseEventHandler, StyleableComponent, TooltipStyle, }, platform::{CursorStyle, MouseButton}, Action, Element, TypeTag, View, @@ -80,24 +203,28 @@ pub mod action_button { use crate::Interactive; + #[derive(Clone, Deserialize, Default, JsonSchema)] + pub struct ButtonStyle { + #[serde(flatten)] + pub container: ContainerStyle, + // TODO: These are incorrect for the intended usage of the buttons. + // The size should be constant, but putting them here duplicates them + // across the states the buttons can be in + pub button_width: Option, + pub button_height: Option, + #[serde(flatten)] + contents: C, + } + pub struct ActionButton { action: Box, tooltip: Option<(Cow<'static, str>, TooltipStyle)>, tag: TypeTag, + id: usize, contents: C, style: Interactive, } - #[derive(Clone, Deserialize, Default, JsonSchema)] - pub struct ButtonStyle { - #[serde(flatten)] - container: ContainerStyle, - button_width: Option, - button_height: Option, - #[serde(flatten)] - contents: C, - } - impl ActionButton<(), ()> { pub fn new_dynamic(action: Box) -> Self { Self { @@ -105,6 +232,7 @@ pub mod action_button { tag: action.type_tag(), style: Interactive::new_blank(), tooltip: None, + id: 0, action, } } @@ -122,21 +250,24 @@ pub mod action_button { self } - pub fn with_contents( - self, - contents: C, - ) -> ActionButton { + pub fn with_id(mut self, id: usize) -> Self { + self.id = id; + self + } + + pub fn with_contents(self, contents: C) -> ActionButton { ActionButton { action: self.action, tag: self.tag, style: self.style, tooltip: self.tooltip, + id: self.id, contents, } } } - impl GeneralStyleableComponent for ActionButton { + impl StyleableComponent for ActionButton { type Style = Interactive>; type Output = ActionButton>; @@ -146,15 +277,15 @@ pub mod action_button { tag: self.tag, contents: self.contents, tooltip: self.tooltip, - + id: self.id, style, } } } - impl GeneralComponent for ActionButton> { + impl Component for ActionButton> { fn render(self, v: &mut V, cx: &mut gpui::ViewContext) -> gpui::AnyElement { - let mut button = MouseEventHandler::new_dynamic(self.tag, 0, cx, |state, cx| { + let mut button = MouseEventHandler::new_dynamic(self.tag, self.id, cx, |state, cx| { let style = self.style.style_for(state); let mut contents = self .contents @@ -177,8 +308,13 @@ pub mod action_button { .on_click(MouseButton::Left, { let action = self.action.boxed_clone(); move |_, _, cx| { - cx.window() - .dispatch_action(cx.view_id(), action.as_ref(), cx); + let window = cx.window(); + let view = cx.view_id(); + let action = action.boxed_clone(); + cx.spawn(|_, mut cx| async move { + window.dispatch_action(view, action.as_ref(), &mut cx) + }) + .detach(); } }) .with_cursor_style(CursorStyle::PointingHand) @@ -199,7 +335,7 @@ pub mod svg { use std::borrow::Cow; use gpui::{ - elements::{GeneralComponent, GeneralStyleableComponent}, + elements::{Component, Empty, StyleableComponent}, Element, }; use schemars::JsonSchema; @@ -222,6 +358,7 @@ pub mod svg { pub enum IconSize { IconSize { icon_size: f32 }, Dimensions { width: f32, height: f32 }, + IconDimensions { icon_width: f32, icon_height: f32 }, } #[derive(Deserialize)] @@ -245,6 +382,14 @@ pub mod svg { icon_height: height, color, }, + IconSize::IconDimensions { + icon_width, + icon_height, + } => SvgStyle { + icon_width, + icon_height, + color, + }, }; Ok(result) @@ -252,20 +397,27 @@ pub mod svg { } pub struct Svg { - path: Cow<'static, str>, + path: Option>, style: S, } impl Svg<()> { pub fn new(path: impl Into>) -> Self { Self { - path: path.into(), + path: Some(path.into()), + style: (), + } + } + + pub fn optional(path: Option>>) -> Self { + Self { + path: path.map(Into::into), style: (), } } } - impl GeneralStyleableComponent for Svg<()> { + impl StyleableComponent for Svg<()> { type Style = SvgStyle; type Output = Svg; @@ -278,18 +430,23 @@ pub mod svg { } } - impl GeneralComponent for Svg { + impl Component for Svg { fn render( self, _: &mut V, _: &mut gpui::ViewContext, ) -> gpui::AnyElement { - gpui::elements::Svg::new(self.path) - .with_color(self.style.color) - .constrained() - .with_width(self.style.icon_width) - .with_height(self.style.icon_height) - .into_any() + if let Some(path) = self.path { + gpui::elements::Svg::new(path) + .with_color(self.style.color) + .constrained() + } else { + Empty::new().constrained() + } + .constrained() + .with_width(self.style.icon_width) + .with_height(self.style.icon_height) + .into_any() } } } @@ -298,7 +455,7 @@ pub mod label { use std::borrow::Cow; use gpui::{ - elements::{GeneralComponent, GeneralStyleableComponent, LabelStyle}, + elements::{Component, LabelStyle, StyleableComponent}, Element, }; @@ -316,7 +473,7 @@ pub mod label { } } - impl GeneralStyleableComponent for Label<()> { + impl StyleableComponent for Label<()> { type Style = LabelStyle; type Output = Label; @@ -329,7 +486,7 @@ pub mod label { } } - impl GeneralComponent for Label { + impl Component for Label { fn render( self, _: &mut V, diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 80e823632a..08da9af299 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -3,7 +3,7 @@ mod theme_registry; mod theme_settings; pub mod ui; -use components::ToggleIconButtonStyle; +use components::{disclosure::DisclosureStyle, ToggleIconButtonStyle}; use gpui::{ color::Color, elements::{ContainerStyle, ImageStyle, LabelStyle, Shadow, SvgStyle, TooltipStyle}, @@ -14,7 +14,7 @@ use schemars::JsonSchema; use serde::{de::DeserializeOwned, Deserialize}; use serde_json::Value; use settings::SettingsStore; -use std::{collections::HashMap, sync::Arc}; +use std::{collections::HashMap, ops::Deref, sync::Arc}; use ui::{CheckboxStyle, CopilotCTAButton, IconStyle, ModalStyle}; pub use theme_registry::*; @@ -221,6 +221,7 @@ pub struct CopilotAuthAuthorized { pub struct CollabPanel { #[serde(flatten)] pub container: ContainerStyle, + pub disclosure: DisclosureStyle<()>, pub list_empty_state: Toggleable>, pub list_empty_icon: Icon, pub list_empty_label_container: ContainerStyle, @@ -890,6 +891,14 @@ pub struct Interactive { pub disabled: Option, } +impl Deref for Interactive { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.default + } +} + impl Interactive<()> { pub fn new_blank() -> Self { Self { @@ -907,6 +916,14 @@ pub struct Toggleable { inactive: T, } +impl Deref for Toggleable { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inactive + } +} + impl Toggleable<()> { pub fn new_blank() -> Self { Self { diff --git a/styles/src/style_tree/collab_panel.ts b/styles/src/style_tree/collab_panel.ts index a102ee7691..5242f90c8d 100644 --- a/styles/src/style_tree/collab_panel.ts +++ b/styles/src/style_tree/collab_panel.ts @@ -152,6 +152,10 @@ export default function contacts_panel(): any { return { ...collab_modals(), + disclosure: { + button: toggleable_icon_button(theme, {}), + spacing: 4, + }, log_in_button: interactive({ base: { background: background(theme.middle),