Detailed changes
@@ -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()
}
@@ -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<Client>, 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<Self>,
subscriptions: Vec<Subscription>,
collapsed_sections: Vec<Section>,
+ collapsed_channels: Vec<ChannelId>,
workspace: WeakViewHandle<Workspace>,
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<Self> {
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<Self>,
) -> AnyElement<Self> {
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, _>(channel.id as usize, cx, |state, cx| {
- Flex::row()
+ Flex::<Self>::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<Self>) {
+ 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<Self>) {
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<S> {
- disclosure: ToggleIconButtonStyle,
- spacing: f32,
- content: S,
- }
-
- pub 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: GeneralStyleableComponent> GeneralStyleableComponent 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: GeneralStyleableComponent> 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: GeneralStyleableComponent> DisclosureExt for C {
- fn disclosable(self, disclosed: bool, action: Box<dyn Action>) -> Disclosable<Self, ()> {
- Disclosable::new(disclosed, self, action)
- }
- }
-}
@@ -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<V: View> Component<V> for ToggleableButton<V> {
+ impl<V: View> StatefulComponent<V> for ToggleableButton<V> {
fn render(self, v: &mut V, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
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<V: View> Component<V> for Button<V> {
+ impl<V: View> StatefulComponent<V> for Button<V> {
fn render(self, _: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V> {
let click_handler = self.click_handler;
@@ -230,19 +230,26 @@ pub trait Element<V: View>: 'static {
MouseEventHandler::for_child::<Tag>(self.into_any(), region_id)
}
- fn component(self) -> ElementAdapter<V>
+ fn stateful_component(self) -> ElementAdapter<V>
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<DynamicElementAdapter>
+ where
+ Self: Sized,
+ {
+ DynamicElementAdapter::new(self.into_any()).stylable()
+ }
}
pub trait RenderElement {
@@ -9,35 +9,35 @@ use crate::{
use super::Empty;
-pub trait GeneralComponent {
+pub trait Component {
fn render<V: View>(self, v: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V>;
- fn element<V: View>(self) -> ComponentAdapter<V, Self>
+ fn element<V: View>(self) -> StatefulAdapter<V, Self>
where
Self: Sized,
{
- ComponentAdapter::new(self)
+ StatefulAdapter::new(self)
}
- fn stylable(self) -> GeneralStylableComponentAdapter<Self>
+ fn stylable(self) -> StylableAdapter<Self>
where
Self: Sized,
{
- GeneralStylableComponentAdapter::new(self)
+ StylableAdapter::new(self)
}
}
-pub struct GeneralStylableComponentAdapter<C: GeneralComponent> {
+pub struct StylableAdapter<C: Component> {
component: C,
}
-impl<C: GeneralComponent> GeneralStylableComponentAdapter<C> {
+impl<C: Component> StylableAdapter<C> {
pub fn new(component: C) -> Self {
Self { component }
}
}
-impl<C: GeneralComponent> GeneralStyleableComponent for GeneralStylableComponentAdapter<C> {
+impl<C: Component> StyleableComponent for StylableAdapter<C> {
type Style = ();
type Output = C;
@@ -47,20 +47,20 @@ impl<C: GeneralComponent> 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<V: View>(self, _: &mut V, _: &mut ViewContext<V>) -> AnyElement<V> {
Empty::new().into_any()
}
}
-impl GeneralStyleableComponent for () {
+impl StyleableComponent for () {
type Style = ();
type Output = ();
@@ -69,54 +69,54 @@ impl GeneralStyleableComponent for () {
}
}
-pub trait StyleableComponent<V: View> {
+pub trait StatefulStyleableComponent<V: View> {
type Style: Clone;
- type Output: Component<V>;
+ type Output: StatefulComponent<V>;
- fn c_with_style(self, style: Self::Style) -> Self::Output;
+ fn stateful_with_style(self, style: Self::Style) -> Self::Output;
}
-impl<V: View, C: GeneralStyleableComponent> StyleableComponent<V> for C {
+impl<V: View, C: StyleableComponent> StatefulStyleableComponent<V> 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<V: View> {
+pub trait StatefulComponent<V: View> {
fn render(self, v: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V>;
- fn c_element(self) -> ComponentAdapter<V, Self>
+ fn stateful_element(self) -> StatefulAdapter<V, Self>
where
Self: Sized,
{
- ComponentAdapter::new(self)
+ StatefulAdapter::new(self)
}
- fn c_styleable(self) -> StylableComponentAdapter<Self, V>
+ fn stateful_styleable(self) -> StatefulStylableAdapter<Self, V>
where
Self: Sized,
{
- StylableComponentAdapter::new(self)
+ StatefulStylableAdapter::new(self)
}
}
-impl<V: View, C: GeneralComponent> Component<V> for C {
+impl<V: View, C: Component> StatefulComponent<V> for C {
fn render(self, v: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V> {
self.render(v, cx)
}
}
// StylableComponent -> Component
-pub struct StylableComponentAdapter<C: Component<V>, V: View> {
+pub struct StatefulStylableAdapter<C: StatefulComponent<V>, V: View> {
component: C,
phantom: std::marker::PhantomData<V>,
}
-impl<C: Component<V>, V: View> StylableComponentAdapter<C, V> {
+impl<C: StatefulComponent<V>, V: View> StatefulStylableAdapter<C, V> {
pub fn new(component: C) -> Self {
Self {
component,
@@ -125,12 +125,14 @@ impl<C: Component<V>, V: View> StylableComponentAdapter<C, V> {
}
}
-impl<C: Component<V>, V: View> StyleableComponent<V> for StylableComponentAdapter<C, V> {
+impl<C: StatefulComponent<V>, V: View> StatefulStyleableComponent<V>
+ for StatefulStylableAdapter<C, V>
+{
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<V: View>(self, _: &mut V, _: &mut ViewContext<V>) -> AnyElement<V> {
let element = self
.element
@@ -174,20 +176,20 @@ impl<V: View> ElementAdapter<V> {
}
}
-impl<V: View> Component<V> for ElementAdapter<V> {
+impl<V: View> StatefulComponent<V> for ElementAdapter<V> {
fn render(self, _: &mut V, _: &mut ViewContext<V>) -> AnyElement<V> {
self.element
}
}
// Component -> Element
-pub struct ComponentAdapter<V: View, E> {
+pub struct StatefulAdapter<V: View, E> {
component: Option<E>,
element: Option<AnyElement<V>>,
phantom: PhantomData<V>,
}
-impl<E, V: View> ComponentAdapter<V, E> {
+impl<E, V: View> StatefulAdapter<V, E> {
pub fn new(e: E) -> Self {
Self {
component: Some(e),
@@ -197,7 +199,7 @@ impl<E, V: View> ComponentAdapter<V, E> {
}
}
-impl<V: View, C: Component<V> + 'static> Element<V> for ComponentAdapter<V, C> {
+impl<V: View, C: StatefulComponent<V> + 'static> Element<V> for StatefulAdapter<V, C> {
type LayoutState = ();
type PaintState = ();
@@ -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<V: View> {
@@ -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()
}
}
@@ -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<Interactive<ButtonStyle<SvgStyle>>>;
-pub trait ComponentExt<C: GeneralStyleableComponent> {
+pub trait ComponentExt<C: StyleableComponent> {
fn toggleable(self, active: bool) -> Toggle<C, ()>;
+ fn disclosable(
+ self,
+ disclosed: Option<bool>,
+ action: Box<dyn Action>,
+ id: usize,
+ ) -> Disclosable<C, ()>;
}
-impl<C: GeneralStyleableComponent> ComponentExt<C> for C {
+impl<C: StyleableComponent> ComponentExt<C> for C {
fn toggleable(self, active: bool) -> Toggle<C, ()> {
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<bool>,
+ action: Box<dyn Action>,
+ id: usize,
+ ) -> Disclosable<C, ()> {
+ 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<S> {
+ pub button: ToggleIconButtonStyle,
+ pub spacing: f32,
+ #[serde(flatten)]
+ content: S,
+ }
+
+ impl<S> DisclosureStyle<S> {
+ pub fn button_space(&self) -> f32 {
+ self.spacing + self.button.button_width.unwrap()
+ }
+ }
+
+ pub struct Disclosable<C, S> {
+ disclosed: Option<bool>,
+ action: Box<dyn Action>,
+ id: usize,
+ content: C,
+ style: S,
+ }
+
+ impl Disclosable<(), ()> {
+ pub fn new<C>(
+ disclosed: Option<bool>,
+ content: C,
+ action: Box<dyn Action>,
+ id: usize,
+ ) -> Disclosable<C, ()> {
+ Disclosable {
+ disclosed,
+ content,
+ action,
+ id,
+ 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,
+ id: self.id,
+ style,
+ }
+ }
+ }
+
+ impl<C: StyleableComponent> Component 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(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<C: GeneralStyleableComponent> Toggle<C, ()> {
+ impl<C: StyleableComponent> Toggle<C, ()> {
pub fn new(component: C, active: bool) -> Self {
Toggle {
active,
@@ -37,7 +161,7 @@ pub mod toggle {
}
}
- impl<C: GeneralStyleableComponent> GeneralStyleableComponent for Toggle<C, ()> {
+ impl<C: StyleableComponent> StyleableComponent for Toggle<C, ()> {
type Style = Toggleable<C::Style>;
type Output = Toggle<C, Self::Style>;
@@ -51,7 +175,7 @@ pub mod toggle {
}
}
- impl<C: GeneralStyleableComponent> GeneralComponent for Toggle<C, Toggleable<C::Style>> {
+ impl<C: StyleableComponent> Component for Toggle<C, Toggleable<C::Style>> {
fn render<V: gpui::View>(
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<C> {
+ #[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<f32>,
+ pub button_height: Option<f32>,
+ #[serde(flatten)]
+ contents: C,
+ }
+
pub struct ActionButton<C, S> {
action: Box<dyn Action>,
tooltip: Option<(Cow<'static, str>, TooltipStyle)>,
tag: TypeTag,
+ id: usize,
contents: C,
style: Interactive<S>,
}
- #[derive(Clone, Deserialize, Default, JsonSchema)]
- pub struct ButtonStyle<C> {
- #[serde(flatten)]
- container: ContainerStyle,
- button_width: Option<f32>,
- button_height: Option<f32>,
- #[serde(flatten)]
- contents: C,
- }
-
impl ActionButton<(), ()> {
pub fn new_dynamic(action: Box<dyn Action>) -> 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<C: GeneralStyleableComponent>(
- self,
- contents: C,
- ) -> ActionButton<C, ()> {
+ pub fn with_id(mut self, id: usize) -> Self {
+ self.id = id;
+ self
+ }
+
+ pub fn with_contents<C: StyleableComponent>(self, contents: C) -> ActionButton<C, ()> {
ActionButton {
action: self.action,
tag: self.tag,
style: self.style,
tooltip: self.tooltip,
+ id: self.id,
contents,
}
}
}
- impl<C: GeneralStyleableComponent> GeneralStyleableComponent for ActionButton<C, ()> {
+ impl<C: StyleableComponent> StyleableComponent for ActionButton<C, ()> {
type Style = Interactive<ButtonStyle<C::Style>>;
type Output = ActionButton<C, ButtonStyle<C::Style>>;
@@ -146,15 +277,15 @@ pub mod action_button {
tag: self.tag,
contents: self.contents,
tooltip: self.tooltip,
-
+ id: self.id,
style,
}
}
}
- impl<C: GeneralStyleableComponent> GeneralComponent for ActionButton<C, ButtonStyle<C::Style>> {
+ impl<C: StyleableComponent> Component for ActionButton<C, ButtonStyle<C::Style>> {
fn render<V: View>(self, v: &mut V, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
- 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<S> {
- path: Cow<'static, str>,
+ path: Option<Cow<'static, str>>,
style: S,
}
impl Svg<()> {
pub fn new(path: impl Into<Cow<'static, str>>) -> Self {
Self {
- path: path.into(),
+ path: Some(path.into()),
+ style: (),
+ }
+ }
+
+ pub fn optional(path: Option<impl Into<Cow<'static, str>>>) -> Self {
+ Self {
+ path: path.map(Into::into),
style: (),
}
}
}
- impl GeneralStyleableComponent for Svg<()> {
+ impl StyleableComponent for Svg<()> {
type Style = SvgStyle;
type Output = Svg<SvgStyle>;
@@ -278,18 +430,23 @@ pub mod svg {
}
}
- impl GeneralComponent for Svg<SvgStyle> {
+ impl Component for Svg<SvgStyle> {
fn render<V: gpui::View>(
self,
_: &mut V,
_: &mut gpui::ViewContext<V>,
) -> gpui::AnyElement<V> {
- 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<LabelStyle>;
@@ -329,7 +486,7 @@ pub mod label {
}
}
- impl GeneralComponent for Label<LabelStyle> {
+ impl Component for Label<LabelStyle> {
fn render<V: gpui::View>(
self,
_: &mut V,
@@ -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<Interactive<ContainedText>>,
pub list_empty_icon: Icon,
pub list_empty_label_container: ContainerStyle,
@@ -890,6 +891,14 @@ pub struct Interactive<T> {
pub disabled: Option<T>,
}
+impl<T> Deref for Interactive<T> {
+ 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<T> {
inactive: T,
}
+impl<T> Deref for Toggleable<T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.inactive
+ }
+}
+
impl Toggleable<()> {
pub fn new_blank() -> Self {
Self {
@@ -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),