Detailed changes
@@ -55,7 +55,7 @@ struct RemoveChannel {
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
-struct ToggleCollapsed {
+struct ToggleCollapse {
channel_id: u64,
}
@@ -79,7 +79,16 @@ struct RenameChannel {
channel_id: u64,
}
-actions!(collab_panel, [ToggleFocus, Remove, Secondary]);
+actions!(
+ collab_panel,
+ [
+ ToggleFocus,
+ Remove,
+ Secondary,
+ CollapseSelectedChannel,
+ ExpandSelectedChannel
+ ]
+);
impl_actions!(
collab_panel,
@@ -89,7 +98,7 @@ impl_actions!(
InviteMembers,
ManageMembers,
RenameChannel,
- ToggleCollapsed
+ ToggleCollapse
]
);
@@ -113,6 +122,8 @@ pub fn init(_client: Arc<Client>, cx: &mut AppContext) {
cx.add_action(CollabPanel::rename_selected_channel);
cx.add_action(CollabPanel::rename_channel);
cx.add_action(CollabPanel::toggle_channel_collapsed);
+ cx.add_action(CollabPanel::collapse_selected_channel);
+ cx.add_action(CollabPanel::expand_selected_channel)
}
#[derive(Debug)]
@@ -1356,7 +1367,7 @@ impl CollabPanel {
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, this, cx| {
if can_collapse {
- this.toggle_expanded(section, cx);
+ this.toggle_section_expanded(section, cx);
}
})
}
@@ -1633,7 +1644,7 @@ impl CollabPanel {
})
.align_children_center()
.styleable_component()
- .disclosable(disclosed, Box::new(ToggleCollapsed { channel_id }))
+ .disclosable(disclosed, Box::new(ToggleCollapse { channel_id }))
.with_id(channel_id as usize)
.with_style(theme.disclosure.clone())
.element()
@@ -1863,6 +1874,12 @@ impl CollabPanel {
OverlayPositionMode::Window
});
+ let expand_action_name = if self.is_channel_collapsed(channel_id) {
+ "Expand Subchannels"
+ } else {
+ "Collapse Subchannels"
+ };
+
context_menu.show(
position.unwrap_or_default(),
if self.context_menu_on_selected {
@@ -1871,6 +1888,7 @@ impl CollabPanel {
gpui::elements::AnchorCorner::BottomLeft
},
vec![
+ ContextMenuItem::action(expand_action_name, ToggleCollapse { channel_id }),
ContextMenuItem::action("New Subchannel", NewChannel { channel_id }),
ContextMenuItem::Separator,
ContextMenuItem::action("Invite to Channel", InviteMembers { channel_id }),
@@ -1950,7 +1968,7 @@ impl CollabPanel {
| Section::Online
| Section::Offline
| Section::ChannelInvites => {
- self.toggle_expanded(*section, cx);
+ self.toggle_section_expanded(*section, cx);
}
},
ListEntry::Contact { contact, calling } => {
@@ -2038,7 +2056,7 @@ impl CollabPanel {
}
}
- fn toggle_expanded(&mut self, section: Section, cx: &mut ViewContext<Self>) {
+ fn toggle_section_expanded(&mut self, section: Section, cx: &mut ViewContext<Self>) {
if let Some(ix) = self.collapsed_sections.iter().position(|s| *s == section) {
self.collapsed_sections.remove(ix);
} else {
@@ -2047,8 +2065,37 @@ impl CollabPanel {
self.update_entries(false, cx);
}
- fn toggle_channel_collapsed(&mut self, action: &ToggleCollapsed, cx: &mut ViewContext<Self>) {
+ fn collapse_selected_channel(
+ &mut self,
+ _: &CollapseSelectedChannel,
+ cx: &mut ViewContext<Self>,
+ ) {
+ let Some(channel_id) = self.selected_channel().map(|channel| channel.id) else {
+ return;
+ };
+
+ if self.is_channel_collapsed(channel_id) {
+ return;
+ }
+
+ self.toggle_channel_collapsed(&ToggleCollapse { channel_id }, cx)
+ }
+
+ fn expand_selected_channel(&mut self, _: &ExpandSelectedChannel, cx: &mut ViewContext<Self>) {
+ let Some(channel_id) = self.selected_channel().map(|channel| channel.id) else {
+ return;
+ };
+
+ if !self.is_channel_collapsed(channel_id) {
+ return;
+ }
+
+ self.toggle_channel_collapsed(&ToggleCollapse { channel_id }, cx)
+ }
+
+ fn toggle_channel_collapsed(&mut self, action: &ToggleCollapse, 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);
@@ -2057,8 +2104,9 @@ impl CollabPanel {
self.collapsed_channels.insert(ix, channel_id);
}
};
- self.update_entries(false, cx);
+ self.update_entries(true, cx);
cx.notify();
+ cx.focus_self();
}
fn is_channel_collapsed(&self, channel: ChannelId) -> bool {
@@ -2104,6 +2152,8 @@ impl CollabPanel {
}
fn new_subchannel(&mut self, action: &NewChannel, cx: &mut ViewContext<Self>) {
+ self.collapsed_channels
+ .retain(|&channel| channel != action.channel_id);
self.channel_editing_state = Some(ChannelEditingState::Create {
parent_id: Some(action.channel_id),
pending_name: None,
@@ -4,7 +4,8 @@ use crate::{Interactive, Toggleable};
use self::{action_button::ButtonStyle, disclosure::Disclosable, svg::SvgStyle, toggle::Toggle};
-pub type ToggleIconButtonStyle = Toggleable<Interactive<ButtonStyle<SvgStyle>>>;
+pub type IconButtonStyle = Interactive<ButtonStyle<SvgStyle>>;
+pub type ToggleIconButtonStyle = Toggleable<IconButtonStyle>;
pub trait ComponentExt<C: SafeStylable> {
fn toggleable(self, active: bool) -> Toggle<C, ()>;
@@ -27,17 +28,19 @@ impl<C: SafeStylable> ComponentExt<C> for C {
pub mod disclosure {
use gpui::{
- elements::{Component, Empty, Flex, ParentElement, SafeStylable},
+ elements::{Component, ContainerStyle, Empty, Flex, ParentElement, SafeStylable},
Action, Element,
};
use schemars::JsonSchema;
use serde_derive::Deserialize;
- use super::{action_button::Button, svg::Svg, ComponentExt, ToggleIconButtonStyle};
+ use super::{action_button::Button, svg::Svg, ComponentExt, IconButtonStyle};
#[derive(Clone, Default, Deserialize, JsonSchema)]
pub struct DisclosureStyle<S> {
- pub button: ToggleIconButtonStyle,
+ pub button: IconButtonStyle,
+ #[serde(flatten)]
+ pub container: ContainerStyle,
pub spacing: f32,
#[serde(flatten)]
content: S,
@@ -99,6 +102,7 @@ pub mod disclosure {
impl<C: SafeStylable> Component for Disclosable<C, DisclosureStyle<C::Style>> {
fn render<V: gpui::View>(self, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
Flex::row()
+ .with_spacing(self.style.spacing)
.with_child(if let Some(disclosed) = self.disclosed {
Button::dynamic_action(self.action)
.with_id(self.id)
@@ -107,7 +111,6 @@ pub mod disclosure {
} else {
"icons/file_icons/chevron_right.svg"
}))
- .toggleable(disclosed)
.with_style(self.style.button)
.element()
.into_any()
@@ -119,7 +122,6 @@ pub mod disclosure {
.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)
@@ -127,6 +129,8 @@ pub mod disclosure {
.flex(1., true),
)
.align_children_center()
+ .contained()
+ .with_style(self.style.container)
.into_any()
}
}
@@ -44,10 +44,10 @@ export function icon_button({ color, margin, layer, variant, size }: IconButtonO
}
const padding = {
- top: size === Button.size.Small ? 0 : 2,
- bottom: size === Button.size.Small ? 0 : 2,
- left: size === Button.size.Small ? 0 : 4,
- right: size === Button.size.Small ? 0 : 4,
+ top: size === Button.size.Small ? 2 : 2,
+ bottom: size === Button.size.Small ? 2 : 2,
+ left: size === Button.size.Small ? 2 : 4,
+ right: size === Button.size.Small ? 2 : 4,
}
return interactive({
@@ -55,10 +55,10 @@ export function icon_button({ color, margin, layer, variant, size }: IconButtonO
corner_radius: 6,
padding: padding,
margin: m,
- icon_width: 14,
+ icon_width: 12,
icon_height: 14,
- button_width: 20,
- button_height: 16,
+ button_width: size === Button.size.Small ? 16 : 20,
+ button_height: 14,
},
state: {
default: {
@@ -14,6 +14,7 @@ import { indicator } from "../component/indicator"
export default function contacts_panel(): any {
const theme = useTheme()
+ const CHANNEL_SPACING = 4 as const
const NAME_MARGIN = 6 as const
const SPACING = 12 as const
const INDENT_SIZE = 8 as const
@@ -153,8 +154,8 @@ export default function contacts_panel(): any {
return {
...collab_modals(),
disclosure: {
- button: toggleable_icon_button(theme, {}),
- spacing: 4,
+ button: icon_button({ variant: "ghost", size: "sm" }),
+ spacing: CHANNEL_SPACING,
},
log_in_button: interactive({
base: {
@@ -198,7 +199,7 @@ export default function contacts_panel(): any {
add_channel_button: header_icon_button,
leave_call_button: header_icon_button,
row_height: ITEM_HEIGHT,
- channel_indent: INDENT_SIZE * 2,
+ channel_indent: INDENT_SIZE * 2 + 2,
section_icon_size: 14,
header_row: {
...text(layer, "sans", { size: "sm", weight: "bold" }),
@@ -268,7 +269,7 @@ export default function contacts_panel(): any {
channel_name: {
...text(layer, "sans", { size: "sm" }),
margin: {
- left: NAME_MARGIN,
+ left: CHANNEL_SPACING,
},
},
list_empty_label_container: {
@@ -1,18 +1,26 @@
-import { toggle_label_button_style } from "../component/label_button"
+
import { useTheme } from "../common"
import { text_button } from "../component/text_button"
-import { toggleable_icon_button } from "../component/icon_button"
+import { icon_button } from "../component/icon_button"
import { text } from "./components"
+import { toggleable } from "../element"
export default function contacts_panel(): any {
const theme = useTheme()
return {
button: text_button({}),
- toggle: toggle_label_button_style({ active_color: "accent" }),
+ toggle: toggleable({
+ base: text_button({}),
+ state: {
+ active: {
+ ...text_button({ color: "accent" })
+ }
+ }
+ }),
disclosure: {
...text(theme.lowest, "sans", "base"),
- button: toggleable_icon_button(theme, {}),
+ button: icon_button({ variant: "ghost" }),
spacing: 4,
}
}