Context menu, Dragon Drop, for collab panel (#3441)
Release Notes: - N/A
This commit is contained in:
commit
fb377aed73
11 changed files with 490 additions and 478 deletions
|
@ -19,6 +19,7 @@ mod contact_finder;
|
||||||
use contact_finder::ContactFinder;
|
use contact_finder::ContactFinder;
|
||||||
use menu::Confirm;
|
use menu::Confirm;
|
||||||
use rpc::proto;
|
use rpc::proto;
|
||||||
|
use theme::{ActiveTheme, ThemeSettings};
|
||||||
// use context_menu::{ContextMenu, ContextMenuItem};
|
// use context_menu::{ContextMenu, ContextMenuItem};
|
||||||
// use db::kvp::KEY_VALUE_STORE;
|
// use db::kvp::KEY_VALUE_STORE;
|
||||||
// use drag_and_drop::{DragAndDrop, Draggable};
|
// use drag_and_drop::{DragAndDrop, Draggable};
|
||||||
|
@ -151,10 +152,10 @@ actions!(
|
||||||
// ]
|
// ]
|
||||||
// );
|
// );
|
||||||
|
|
||||||
// #[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
// struct ChannelMoveClipboard {
|
struct ChannelMoveClipboard {
|
||||||
// channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
// }
|
}
|
||||||
|
|
||||||
const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel";
|
const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel";
|
||||||
|
|
||||||
|
@ -168,17 +169,18 @@ use editor::Editor;
|
||||||
use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt};
|
use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt};
|
||||||
use fuzzy::{match_strings, StringMatchCandidate};
|
use fuzzy::{match_strings, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, img, prelude::*, serde_json, Action, AppContext, AsyncWindowContext, Div,
|
actions, div, img, overlay, prelude::*, px, rems, serde_json, Action, AppContext,
|
||||||
EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement, IntoElement, Model,
|
AsyncWindowContext, ClipboardItem, DismissEvent, Div, EventEmitter, FocusHandle, Focusable,
|
||||||
ParentElement, PromptLevel, Render, RenderOnce, SharedString, Styled, Subscription, Task, View,
|
FocusableView, InteractiveElement, IntoElement, Model, MouseDownEvent, ParentElement, Pixels,
|
||||||
ViewContext, VisualContext, WeakView,
|
Point, PromptLevel, Render, RenderOnce, SharedString, Stateful, Styled, Subscription, Task,
|
||||||
|
View, ViewContext, VisualContext, WeakView,
|
||||||
};
|
};
|
||||||
use project::Fs;
|
use project::Fs;
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use settings::{Settings, SettingsStore};
|
use settings::{Settings, SettingsStore};
|
||||||
use ui::{
|
use ui::{
|
||||||
h_stack, v_stack, Avatar, Button, Color, Icon, IconButton, IconElement, Label, List,
|
h_stack, v_stack, Avatar, Button, Color, ContextMenu, Icon, IconButton, IconElement, IconSize,
|
||||||
ListHeader, ListItem, Toggleable, Tooltip,
|
Label, List, ListHeader, ListItem, Tooltip,
|
||||||
};
|
};
|
||||||
use util::{maybe, ResultExt, TryFutureExt};
|
use util::{maybe, ResultExt, TryFutureExt};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
|
@ -230,26 +232,6 @@ pub fn init(cx: &mut AppContext) {
|
||||||
// },
|
// },
|
||||||
// );
|
// );
|
||||||
|
|
||||||
// cx.add_action(
|
|
||||||
// |panel: &mut CollabPanel,
|
|
||||||
// action: &StartMoveChannelFor,
|
|
||||||
// _: &mut ViewContext<CollabPanel>| {
|
|
||||||
// panel.channel_clipboard = Some(ChannelMoveClipboard {
|
|
||||||
// channel_id: action.channel_id,
|
|
||||||
// });
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
|
|
||||||
// cx.add_action(
|
|
||||||
// |panel: &mut CollabPanel, _: &StartMoveChannel, _: &mut ViewContext<CollabPanel>| {
|
|
||||||
// if let Some(channel) = panel.selected_channel() {
|
|
||||||
// panel.channel_clipboard = Some(ChannelMoveClipboard {
|
|
||||||
// channel_id: channel.id,
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
|
|
||||||
// cx.add_action(
|
// cx.add_action(
|
||||||
// |panel: &mut CollabPanel, _: &MoveSelected, cx: &mut ViewContext<CollabPanel>| {
|
// |panel: &mut CollabPanel, _: &MoveSelected, cx: &mut ViewContext<CollabPanel>| {
|
||||||
// let Some(clipboard) = panel.channel_clipboard.take() else {
|
// let Some(clipboard) = panel.channel_clipboard.take() else {
|
||||||
|
@ -303,12 +285,12 @@ impl ChannelEditingState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CollabPanel {
|
pub struct CollabPanel {
|
||||||
width: Option<f32>,
|
width: Option<Pixels>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
// channel_clipboard: Option<ChannelMoveClipboard>,
|
channel_clipboard: Option<ChannelMoveClipboard>,
|
||||||
pending_serialization: Task<Option<()>>,
|
pending_serialization: Task<Option<()>>,
|
||||||
// context_menu: ViewHandle<ContextMenu>,
|
context_menu: Option<(View<ContextMenu>, Point<Pixels>, Subscription)>,
|
||||||
filter_editor: View<Editor>,
|
filter_editor: View<Editor>,
|
||||||
channel_name_editor: View<Editor>,
|
channel_name_editor: View<Editor>,
|
||||||
channel_editing_state: Option<ChannelEditingState>,
|
channel_editing_state: Option<ChannelEditingState>,
|
||||||
|
@ -337,7 +319,7 @@ enum ChannelDragTarget {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct SerializedCollabPanel {
|
struct SerializedCollabPanel {
|
||||||
width: Option<f32>,
|
width: Option<Pixels>,
|
||||||
collapsed_channels: Option<Vec<u64>>,
|
collapsed_channels: Option<Vec<u64>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -589,10 +571,10 @@ impl CollabPanel {
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
width: None,
|
width: None,
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
// channel_clipboard: None,
|
channel_clipboard: None,
|
||||||
fs: workspace.app_state().fs.clone(),
|
fs: workspace.app_state().fs.clone(),
|
||||||
pending_serialization: Task::ready(None),
|
pending_serialization: Task::ready(None),
|
||||||
// context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)),
|
context_menu: None,
|
||||||
channel_name_editor,
|
channel_name_editor,
|
||||||
filter_editor,
|
filter_editor,
|
||||||
entries: Vec::default(),
|
entries: Vec::default(),
|
||||||
|
@ -1685,156 +1667,123 @@ impl CollabPanel {
|
||||||
// .into_any()
|
// .into_any()
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// fn has_subchannels(&self, ix: usize) -> bool {
|
fn has_subchannels(&self, ix: usize) -> bool {
|
||||||
// self.entries.get(ix).map_or(false, |entry| {
|
self.entries.get(ix).map_or(false, |entry| {
|
||||||
// if let ListEntry::Channel { has_children, .. } = entry {
|
if let ListEntry::Channel { has_children, .. } = entry {
|
||||||
// *has_children
|
*has_children
|
||||||
// } else {
|
} else {
|
||||||
// false
|
false
|
||||||
// }
|
}
|
||||||
// })
|
})
|
||||||
// }
|
}
|
||||||
|
|
||||||
// fn deploy_channel_context_menu(
|
fn deploy_channel_context_menu(
|
||||||
// &mut self,
|
&mut self,
|
||||||
// position: Option<Vector2F>,
|
position: Point<Pixels>,
|
||||||
// channel: &Channel,
|
channel_id: ChannelId,
|
||||||
// ix: usize,
|
ix: usize,
|
||||||
// cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
// ) {
|
) {
|
||||||
// self.context_menu_on_selected = position.is_none();
|
// self.context_menu_on_selected = position.is_none();
|
||||||
|
|
||||||
// let clipboard_channel_name = self.channel_clipboard.as_ref().and_then(|clipboard| {
|
let clipboard_channel_name = self.channel_clipboard.as_ref().and_then(|clipboard| {
|
||||||
// self.channel_store
|
self.channel_store
|
||||||
// .read(cx)
|
.read(cx)
|
||||||
// .channel_for_id(clipboard.channel_id)
|
.channel_for_id(clipboard.channel_id)
|
||||||
// .map(|channel| channel.name.clone())
|
.map(|channel| channel.name.clone())
|
||||||
// });
|
});
|
||||||
|
let this = cx.view().clone();
|
||||||
|
|
||||||
// self.context_menu.update(cx, |context_menu, cx| {
|
let context_menu = ContextMenu::build(cx, |mut context_menu, cx| {
|
||||||
// context_menu.set_position_mode(if self.context_menu_on_selected {
|
if self.has_subchannels(ix) {
|
||||||
// OverlayPositionMode::Local
|
let expand_action_name = if self.is_channel_collapsed(channel_id) {
|
||||||
// } else {
|
"Expand Subchannels"
|
||||||
// OverlayPositionMode::Window
|
} else {
|
||||||
// });
|
"Collapse Subchannels"
|
||||||
|
};
|
||||||
|
context_menu = context_menu.entry(
|
||||||
|
expand_action_name,
|
||||||
|
cx.handler_for(&this, move |this, cx| {
|
||||||
|
this.toggle_channel_collapsed(channel_id, cx)
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// let mut items = Vec::new();
|
context_menu = context_menu
|
||||||
|
.entry(
|
||||||
|
"Open Notes",
|
||||||
|
cx.handler_for(&this, move |this, cx| {
|
||||||
|
this.open_channel_notes(channel_id, cx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.entry(
|
||||||
|
"Open Chat",
|
||||||
|
cx.handler_for(&this, move |this, cx| {
|
||||||
|
this.join_channel_chat(channel_id, cx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.entry(
|
||||||
|
"Copy Channel Link",
|
||||||
|
cx.handler_for(&this, move |this, cx| {
|
||||||
|
this.copy_channel_link(channel_id, cx)
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
// let select_action_name = if self.selection == Some(ix) {
|
if self.channel_store.read(cx).is_channel_admin(channel_id) {
|
||||||
// "Unselect"
|
context_menu = context_menu
|
||||||
// } else {
|
.separator()
|
||||||
// "Select"
|
.entry(
|
||||||
// };
|
"New Subchannel",
|
||||||
|
cx.handler_for(&this, move |this, cx| this.new_subchannel(channel_id, cx)),
|
||||||
|
)
|
||||||
|
.entry(
|
||||||
|
"Rename",
|
||||||
|
cx.handler_for(&this, move |this, cx| this.rename_channel(channel_id, cx)),
|
||||||
|
)
|
||||||
|
.entry(
|
||||||
|
"Move this channel",
|
||||||
|
cx.handler_for(&this, move |this, cx| {
|
||||||
|
this.start_move_channel(channel_id, cx)
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
// items.push(ContextMenuItem::action(
|
if let Some(channel_name) = clipboard_channel_name {
|
||||||
// select_action_name,
|
context_menu = context_menu.separator().entry(
|
||||||
// ToggleSelectedIx { ix },
|
format!("Move '#{}' here", channel_name),
|
||||||
// ));
|
cx.handler_for(&this, move |this, cx| {
|
||||||
|
this.move_channel_on_clipboard(channel_id, cx)
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// if self.has_subchannels(ix) {
|
context_menu = context_menu
|
||||||
// let expand_action_name = if self.is_channel_collapsed(channel.id) {
|
.separator()
|
||||||
// "Expand Subchannels"
|
.entry(
|
||||||
// } else {
|
"Invite Members",
|
||||||
// "Collapse Subchannels"
|
cx.handler_for(&this, move |this, cx| this.invite_members(channel_id, cx)),
|
||||||
// };
|
)
|
||||||
// items.push(ContextMenuItem::action(
|
.entry(
|
||||||
// expand_action_name,
|
"Manage Members",
|
||||||
// ToggleCollapse {
|
cx.handler_for(&this, move |this, cx| this.manage_members(channel_id, cx)),
|
||||||
// location: channel.id,
|
)
|
||||||
// },
|
.entry(
|
||||||
// ));
|
"Delete",
|
||||||
// }
|
cx.handler_for(&this, move |this, cx| this.remove_channel(channel_id, cx)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// items.push(ContextMenuItem::action(
|
context_menu
|
||||||
// "Open Notes",
|
});
|
||||||
// OpenChannelNotes {
|
|
||||||
// channel_id: channel.id,
|
|
||||||
// },
|
|
||||||
// ));
|
|
||||||
|
|
||||||
// items.push(ContextMenuItem::action(
|
cx.focus_view(&context_menu);
|
||||||
// "Open Chat",
|
let subscription = cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| {
|
||||||
// JoinChannelChat {
|
this.context_menu.take();
|
||||||
// channel_id: channel.id,
|
cx.notify();
|
||||||
// },
|
});
|
||||||
// ));
|
self.context_menu = Some((context_menu, position, subscription));
|
||||||
|
|
||||||
// items.push(ContextMenuItem::action(
|
cx.notify();
|
||||||
// "Copy Channel Link",
|
}
|
||||||
// CopyChannelLink {
|
|
||||||
// channel_id: channel.id,
|
|
||||||
// },
|
|
||||||
// ));
|
|
||||||
|
|
||||||
// if self.channel_store.read(cx).is_channel_admin(channel.id) {
|
|
||||||
// items.extend([
|
|
||||||
// ContextMenuItem::Separator,
|
|
||||||
// ContextMenuItem::action(
|
|
||||||
// "New Subchannel",
|
|
||||||
// NewChannel {
|
|
||||||
// location: channel.id,
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// ContextMenuItem::action(
|
|
||||||
// "Rename",
|
|
||||||
// RenameChannel {
|
|
||||||
// channel_id: channel.id,
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// ContextMenuItem::action(
|
|
||||||
// "Move this channel",
|
|
||||||
// StartMoveChannelFor {
|
|
||||||
// channel_id: channel.id,
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// ]);
|
|
||||||
|
|
||||||
// if let Some(channel_name) = clipboard_channel_name {
|
|
||||||
// items.push(ContextMenuItem::Separator);
|
|
||||||
// items.push(ContextMenuItem::action(
|
|
||||||
// format!("Move '#{}' here", channel_name),
|
|
||||||
// MoveChannel { to: channel.id },
|
|
||||||
// ));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// items.extend([
|
|
||||||
// ContextMenuItem::Separator,
|
|
||||||
// ContextMenuItem::action(
|
|
||||||
// "Invite Members",
|
|
||||||
// InviteMembers {
|
|
||||||
// channel_id: channel.id,
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// ContextMenuItem::action(
|
|
||||||
// "Manage Members",
|
|
||||||
// ManageMembers {
|
|
||||||
// channel_id: channel.id,
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// ContextMenuItem::Separator,
|
|
||||||
// ContextMenuItem::action(
|
|
||||||
// "Delete",
|
|
||||||
// RemoveChannel {
|
|
||||||
// channel_id: channel.id,
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// ]);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// context_menu.show(
|
|
||||||
// position.unwrap_or_default(),
|
|
||||||
// if self.context_menu_on_selected {
|
|
||||||
// gpui::elements::AnchorCorner::TopRight
|
|
||||||
// } else {
|
|
||||||
// gpui::elements::AnchorCorner::BottomLeft
|
|
||||||
// },
|
|
||||||
// items,
|
|
||||||
// cx,
|
|
||||||
// );
|
|
||||||
// });
|
|
||||||
|
|
||||||
// cx.notify();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
|
// fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
|
||||||
// if self.take_editing_state(cx) {
|
// if self.take_editing_state(cx) {
|
||||||
|
@ -2116,65 +2065,88 @@ impl CollabPanel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn new_subchannel(&mut self, action: &NewChannel, cx: &mut ViewContext<Self>) {
|
fn new_subchannel(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
|
||||||
// self.collapsed_channels
|
self.collapsed_channels
|
||||||
// .retain(|channel| *channel != action.location);
|
.retain(|channel| *channel != channel_id);
|
||||||
// self.channel_editing_state = Some(ChannelEditingState::Create {
|
self.channel_editing_state = Some(ChannelEditingState::Create {
|
||||||
// location: Some(action.location.to_owned()),
|
location: Some(channel_id),
|
||||||
// pending_name: None,
|
pending_name: None,
|
||||||
// });
|
});
|
||||||
// self.update_entries(false, cx);
|
self.update_entries(false, cx);
|
||||||
// self.select_channel_editor();
|
self.select_channel_editor();
|
||||||
// cx.focus(self.channel_name_editor.as_any());
|
cx.focus_view(&self.channel_name_editor);
|
||||||
// cx.notify();
|
cx.notify();
|
||||||
// }
|
}
|
||||||
|
|
||||||
// fn invite_members(&mut self, action: &InviteMembers, cx: &mut ViewContext<Self>) {
|
fn invite_members(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
|
||||||
// self.show_channel_modal(action.channel_id, channel_modal::Mode::InviteMembers, cx);
|
todo!();
|
||||||
// }
|
// self.show_channel_modal(channel_id, channel_modal::Mode::InviteMembers, cx);
|
||||||
|
}
|
||||||
|
|
||||||
// fn manage_members(&mut self, action: &ManageMembers, cx: &mut ViewContext<Self>) {
|
fn manage_members(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
|
||||||
// self.show_channel_modal(action.channel_id, channel_modal::Mode::ManageMembers, cx);
|
todo!();
|
||||||
// }
|
// self.show_channel_modal(channel_id, channel_modal::Mode::ManageMembers, cx);
|
||||||
|
}
|
||||||
|
|
||||||
// fn remove(&mut self, _: &Remove, cx: &mut ViewContext<Self>) {
|
fn remove_selected_channel(&mut self, _: &Remove, cx: &mut ViewContext<Self>) {
|
||||||
// if let Some(channel) = self.selected_channel() {
|
if let Some(channel) = self.selected_channel() {
|
||||||
// self.remove_channel(channel.id, cx)
|
self.remove_channel(channel.id, cx)
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// fn rename_selected_channel(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
|
fn rename_selected_channel(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
|
||||||
// if let Some(channel) = self.selected_channel() {
|
if let Some(channel) = self.selected_channel() {
|
||||||
// self.rename_channel(
|
self.rename_channel(channel.id, cx);
|
||||||
// &RenameChannel {
|
}
|
||||||
// channel_id: channel.id,
|
}
|
||||||
// },
|
|
||||||
// cx,
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn rename_channel(&mut self, action: &RenameChannel, cx: &mut ViewContext<Self>) {
|
fn rename_channel(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
|
||||||
// let channel_store = self.channel_store.read(cx);
|
let channel_store = self.channel_store.read(cx);
|
||||||
// if !channel_store.is_channel_admin(action.channel_id) {
|
if !channel_store.is_channel_admin(channel_id) {
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
// if let Some(channel) = channel_store.channel_for_id(action.channel_id).cloned() {
|
if let Some(channel) = channel_store.channel_for_id(channel_id).cloned() {
|
||||||
// self.channel_editing_state = Some(ChannelEditingState::Rename {
|
self.channel_editing_state = Some(ChannelEditingState::Rename {
|
||||||
// location: action.channel_id.to_owned(),
|
location: channel_id,
|
||||||
// pending_name: None,
|
pending_name: None,
|
||||||
// });
|
});
|
||||||
// self.channel_name_editor.update(cx, |editor, cx| {
|
self.channel_name_editor.update(cx, |editor, cx| {
|
||||||
// editor.set_text(channel.name.clone(), cx);
|
editor.set_text(channel.name.clone(), cx);
|
||||||
// editor.select_all(&Default::default(), cx);
|
editor.select_all(&Default::default(), cx);
|
||||||
// });
|
});
|
||||||
// cx.focus(self.channel_name_editor.as_any());
|
cx.focus_view(&self.channel_name_editor);
|
||||||
// self.update_entries(false, cx);
|
self.update_entries(false, cx);
|
||||||
// self.select_channel_editor();
|
self.select_channel_editor();
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
fn open_channel_notes(&mut self, action: &OpenChannelNotes, cx: &mut ViewContext<Self>) {
|
fn start_move_channel(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
|
||||||
|
self.channel_clipboard = Some(ChannelMoveClipboard { channel_id });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_move_selected_channel(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
|
||||||
|
if let Some(channel) = self.selected_channel() {
|
||||||
|
self.channel_clipboard = Some(ChannelMoveClipboard {
|
||||||
|
channel_id: channel.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_channel_on_clipboard(
|
||||||
|
&mut self,
|
||||||
|
to_channel_id: ChannelId,
|
||||||
|
cx: &mut ViewContext<CollabPanel>,
|
||||||
|
) {
|
||||||
|
if let Some(clipboard) = self.channel_clipboard.take() {
|
||||||
|
self.channel_store.update(cx, |channel_store, cx| {
|
||||||
|
channel_store
|
||||||
|
.move_channel(clipboard.channel_id, Some(to_channel_id), cx)
|
||||||
|
.detach_and_log_err(cx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_channel_notes(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
|
||||||
if let Some(workspace) = self.workspace.upgrade() {
|
if let Some(workspace) = self.workspace.upgrade() {
|
||||||
todo!();
|
todo!();
|
||||||
// ChannelView::open(action.channel_id, workspace, cx).detach();
|
// ChannelView::open(action.channel_id, workspace, cx).detach();
|
||||||
|
@ -2235,35 +2207,29 @@ impl CollabPanel {
|
||||||
// self.remove_channel(action.channel_id, cx)
|
// self.remove_channel(action.channel_id, cx)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// fn remove_channel(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
|
fn remove_channel(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
|
||||||
// let channel_store = self.channel_store.clone();
|
let channel_store = self.channel_store.clone();
|
||||||
// if let Some(channel) = channel_store.read(cx).channel_for_id(channel_id) {
|
if let Some(channel) = channel_store.read(cx).channel_for_id(channel_id) {
|
||||||
// let prompt_message = format!(
|
let prompt_message = format!(
|
||||||
// "Are you sure you want to remove the channel \"{}\"?",
|
"Are you sure you want to remove the channel \"{}\"?",
|
||||||
// channel.name
|
channel.name
|
||||||
// );
|
);
|
||||||
// let mut answer =
|
let mut answer =
|
||||||
// cx.prompt(PromptLevel::Warning, &prompt_message, &["Remove", "Cancel"]);
|
cx.prompt(PromptLevel::Warning, &prompt_message, &["Remove", "Cancel"]);
|
||||||
// let window = cx.window();
|
let window = cx.window();
|
||||||
// cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
// if answer.next().await == Some(0) {
|
if answer.await? == 0 {
|
||||||
// if let Err(e) = channel_store
|
channel_store
|
||||||
// .update(&mut cx, |channels, _| channels.remove_channel(channel_id))
|
.update(&mut cx, |channels, _| channels.remove_channel(channel_id))?
|
||||||
// .await
|
.await
|
||||||
// {
|
.notify_async_err(&mut cx);
|
||||||
// window.prompt(
|
this.update(&mut cx, |_, cx| cx.focus_self()).ok();
|
||||||
// PromptLevel::Info,
|
}
|
||||||
// &format!("Failed to remove channel: {}", e),
|
anyhow::Ok(())
|
||||||
// &["Ok"],
|
})
|
||||||
// &mut cx,
|
.detach();
|
||||||
// );
|
}
|
||||||
// }
|
}
|
||||||
// this.update(&mut cx, |_, cx| cx.focus_self()).ok();
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// .detach();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Should move to the filter editor if clicking on it
|
// // Should move to the filter editor if clicking on it
|
||||||
// // Should move selection to the channel editor if activating it
|
// // Should move selection to the channel editor if activating it
|
||||||
|
@ -2344,11 +2310,13 @@ impl CollabPanel {
|
||||||
.detach()
|
.detach()
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn join_channel_chat(&mut self, action: &JoinChannelChat, cx: &mut ViewContext<Self>) {
|
fn join_channel_chat(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
|
||||||
// let channel_id = action.channel_id;
|
let Some(workspace) = self.workspace.upgrade() else {
|
||||||
// if let Some(workspace) = self.workspace.upgrade(cx) {
|
return;
|
||||||
// cx.app_context().defer(move |cx| {
|
};
|
||||||
// workspace.update(cx, |workspace, cx| {
|
cx.window_context().defer(move |cx| {
|
||||||
|
workspace.update(cx, |workspace, cx| {
|
||||||
|
todo!();
|
||||||
// if let Some(panel) = workspace.focus_panel::<ChatPanel>(cx) {
|
// if let Some(panel) = workspace.focus_panel::<ChatPanel>(cx) {
|
||||||
// panel.update(cx, |panel, cx| {
|
// panel.update(cx, |panel, cx| {
|
||||||
// panel
|
// panel
|
||||||
|
@ -2356,19 +2324,18 @@ impl CollabPanel {
|
||||||
// .detach_and_log_err(cx);
|
// .detach_and_log_err(cx);
|
||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
// });
|
});
|
||||||
// });
|
});
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// fn copy_channel_link(&mut self, action: &CopyChannelLink, cx: &mut ViewContext<Self>) {
|
fn copy_channel_link(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
|
||||||
// let channel_store = self.channel_store.read(cx);
|
let channel_store = self.channel_store.read(cx);
|
||||||
// let Some(channel) = channel_store.channel_for_id(action.channel_id) else {
|
let Some(channel) = channel_store.channel_for_id(channel_id) else {
|
||||||
// return;
|
return;
|
||||||
// };
|
};
|
||||||
// let item = ClipboardItem::new(channel.link());
|
let item = ClipboardItem::new(channel.link());
|
||||||
// cx.write_to_clipboard(item)
|
cx.write_to_clipboard(item)
|
||||||
// }
|
}
|
||||||
|
|
||||||
fn render_signed_out(&mut self, cx: &mut ViewContext<Self>) -> Div {
|
fn render_signed_out(&mut self, cx: &mut ViewContext<Self>) -> Div {
|
||||||
v_stack().child(Button::new("Sign in to collaborate").on_click(cx.listener(
|
v_stack().child(Button::new("Sign in to collaborate").on_click(cx.listener(
|
||||||
|
@ -2388,8 +2355,12 @@ impl CollabPanel {
|
||||||
fn render_signed_in(&mut self, cx: &mut ViewContext<Self>) -> List {
|
fn render_signed_in(&mut self, cx: &mut ViewContext<Self>) -> List {
|
||||||
let is_selected = false; // todo!() this.selection == Some(ix);
|
let is_selected = false; // todo!() this.selection == Some(ix);
|
||||||
|
|
||||||
List::new().children(self.entries.clone().into_iter().map(|entry| {
|
List::new().children(
|
||||||
match entry {
|
self.entries
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(ix, entry)| match entry {
|
||||||
ListEntry::Header(section) => {
|
ListEntry::Header(section) => {
|
||||||
let is_collapsed = self.collapsed_sections.contains(§ion);
|
let is_collapsed = self.collapsed_sections.contains(§ion);
|
||||||
self.render_header(section, is_selected, is_collapsed, cx)
|
self.render_header(section, is_selected, is_collapsed, cx)
|
||||||
|
@ -2412,13 +2383,13 @@ impl CollabPanel {
|
||||||
depth,
|
depth,
|
||||||
has_children,
|
has_children,
|
||||||
} => self
|
} => self
|
||||||
.render_channel(&*channel, depth, has_children, is_selected, cx)
|
.render_channel(&*channel, depth, has_children, is_selected, ix, cx)
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
ListEntry::ChannelEditor { depth } => {
|
ListEntry::ChannelEditor { depth } => {
|
||||||
self.render_channel_editor(depth, cx).into_any_element()
|
self.render_channel_editor(depth, cx).into_any_element()
|
||||||
}
|
}
|
||||||
}
|
}),
|
||||||
}))
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_header(
|
fn render_header(
|
||||||
|
@ -2530,14 +2501,31 @@ impl CollabPanel {
|
||||||
| Section::Offline => true,
|
| Section::Offline => true,
|
||||||
};
|
};
|
||||||
|
|
||||||
ListHeader::new(text)
|
let header = ListHeader::new(text)
|
||||||
.when_some(button, |el, button| el.right_button(button))
|
.when_some(button, |el, button| el.right_button(button))
|
||||||
.selected(is_selected)
|
.selected(is_selected)
|
||||||
.when(can_collapse, |el| {
|
.when(can_collapse, |el| {
|
||||||
el.toggle(Toggleable::Toggleable(is_collapsed.into()))
|
el.toggle(Some(is_collapsed)).on_toggle(
|
||||||
.on_toggle(
|
|
||||||
cx.listener(move |this, _, cx| this.toggle_section_expanded(section, cx)),
|
cx.listener(move |this, _, cx| this.toggle_section_expanded(section, cx)),
|
||||||
)
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
h_stack()
|
||||||
|
.w_full()
|
||||||
|
.child(header)
|
||||||
|
.when(section == Section::Channels, |el| {
|
||||||
|
el.drag_over::<DraggedChannelView>(|style| {
|
||||||
|
style.bg(cx.theme().colors().ghost_element_hover)
|
||||||
|
})
|
||||||
|
.on_drop(cx.listener(
|
||||||
|
move |this, view: &View<DraggedChannelView>, cx| {
|
||||||
|
this.channel_store
|
||||||
|
.update(cx, |channel_store, cx| {
|
||||||
|
channel_store.move_channel(view.read(cx).channel.id, None, cx)
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx)
|
||||||
|
},
|
||||||
|
))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2748,6 +2736,7 @@ impl CollabPanel {
|
||||||
depth: usize,
|
depth: usize,
|
||||||
has_children: bool,
|
has_children: bool,
|
||||||
is_selected: bool,
|
is_selected: bool,
|
||||||
|
ix: usize,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
let channel_id = channel.id;
|
let channel_id = channel.id;
|
||||||
|
@ -2768,9 +2757,8 @@ impl CollabPanel {
|
||||||
.map(|channel| channel.visibility)
|
.map(|channel| channel.visibility)
|
||||||
== Some(proto::ChannelVisibility::Public);
|
== Some(proto::ChannelVisibility::Public);
|
||||||
let other_selected = self.selected_channel().map(|channel| channel.id) == Some(channel.id);
|
let other_selected = self.selected_channel().map(|channel| channel.id) == Some(channel.id);
|
||||||
let disclosed = has_children
|
let disclosed =
|
||||||
.then(|| !self.collapsed_channels.binary_search(&channel.id).is_ok())
|
has_children.then(|| !self.collapsed_channels.binary_search(&channel.id).is_ok());
|
||||||
.unwrap_or(false);
|
|
||||||
|
|
||||||
let has_messages_notification = channel.unseen_message_id.is_some();
|
let has_messages_notification = channel.unseen_message_id.is_some();
|
||||||
let has_notes_notification = channel.unseen_note_version.is_some();
|
let has_notes_notification = channel.unseen_note_version.is_some();
|
||||||
|
@ -2801,9 +2789,38 @@ impl CollabPanel {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
div().group("").child(
|
let width = self.width.unwrap_or(px(240.));
|
||||||
|
|
||||||
|
div()
|
||||||
|
.id(channel_id as usize)
|
||||||
|
.group("")
|
||||||
|
.on_drag({
|
||||||
|
let channel = channel.clone();
|
||||||
|
move |cx| {
|
||||||
|
let channel = channel.clone();
|
||||||
|
cx.build_view({ |cx| DraggedChannelView { channel, width } })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.drag_over::<DraggedChannelView>(|style| {
|
||||||
|
style.bg(cx.theme().colors().ghost_element_hover)
|
||||||
|
})
|
||||||
|
.on_drop(
|
||||||
|
cx.listener(move |this, view: &View<DraggedChannelView>, cx| {
|
||||||
|
this.channel_store
|
||||||
|
.update(cx, |channel_store, cx| {
|
||||||
|
channel_store.move_channel(
|
||||||
|
view.read(cx).channel.id,
|
||||||
|
Some(channel_id),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
ListItem::new(channel_id as usize)
|
ListItem::new(channel_id as usize)
|
||||||
.indent_level(depth)
|
.indent_level(depth)
|
||||||
|
.indent_step_size(cx.rem_size() * 14.0 / 16.0) // @todo()! @nate this is to step over the disclosure toggle
|
||||||
.left_icon(if is_public { Icon::Public } else { Icon::Hash })
|
.left_icon(if is_public { Icon::Public } else { Icon::Hash })
|
||||||
.selected(is_selected || is_active)
|
.selected(is_selected || is_active)
|
||||||
.child(
|
.child(
|
||||||
|
@ -2825,7 +2842,10 @@ impl CollabPanel {
|
||||||
.when(!has_messages_notification, |el| el.invisible())
|
.when(!has_messages_notification, |el| el.invisible())
|
||||||
.group_hover("", |style| style.visible())
|
.group_hover("", |style| style.visible())
|
||||||
.child(
|
.child(
|
||||||
IconButton::new("channel_chat", Icon::MessageBubbles)
|
IconButton::new(
|
||||||
|
"channel_chat",
|
||||||
|
Icon::MessageBubbles,
|
||||||
|
)
|
||||||
.color(if has_messages_notification {
|
.color(if has_messages_notification {
|
||||||
Color::Default
|
Color::Default
|
||||||
} else {
|
} else {
|
||||||
|
@ -2853,26 +2873,26 @@ impl CollabPanel {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toggle(if has_children {
|
.toggle(disclosed)
|
||||||
Toggleable::Toggleable(disclosed.into())
|
|
||||||
} else {
|
|
||||||
Toggleable::NotToggleable
|
|
||||||
})
|
|
||||||
.on_toggle(
|
.on_toggle(
|
||||||
cx.listener(move |this, _, cx| this.toggle_channel_collapsed(channel_id, cx)),
|
cx.listener(move |this, _, cx| {
|
||||||
|
this.toggle_channel_collapsed(channel_id, cx)
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
.on_click(cx.listener(move |this, _, cx| {
|
.on_click(cx.listener(move |this, _, cx| {
|
||||||
if this.drag_target_channel == ChannelDragTarget::None {
|
if this.drag_target_channel == ChannelDragTarget::None {
|
||||||
if is_active {
|
if is_active {
|
||||||
this.open_channel_notes(&OpenChannelNotes { channel_id }, cx)
|
this.open_channel_notes(channel_id, cx)
|
||||||
} else {
|
} else {
|
||||||
this.join_channel(channel_id, cx)
|
this.join_channel(channel_id, cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.on_secondary_mouse_down(cx.listener(|this, _, cx| {
|
.on_secondary_mouse_down(cx.listener(
|
||||||
todo!() // open context menu
|
move |this, event: &MouseDownEvent, cx| {
|
||||||
})),
|
this.deploy_channel_context_menu(event.position, channel_id, ix, cx)
|
||||||
|
},
|
||||||
|
)),
|
||||||
)
|
)
|
||||||
|
|
||||||
// let channel_id = channel.id;
|
// let channel_id = channel.id;
|
||||||
|
@ -3101,7 +3121,8 @@ impl CollabPanel {
|
||||||
// )
|
// )
|
||||||
// })
|
// })
|
||||||
// .on_click(MouseButton::Left, move |_, this, cx| {
|
// .on_click(MouseButton::Left, move |_, this, cx| {
|
||||||
// if this.drag_target_channel == ChannelDragTarget::None {
|
// if this.
|
||||||
|
// drag_target_channel == ChannelDragTarget::None {
|
||||||
// if is_active {
|
// if is_active {
|
||||||
// this.open_channel_notes(&OpenChannelNotes { channel_id }, cx)
|
// this.open_channel_notes(&OpenChannelNotes { channel_id }, cx)
|
||||||
// } else {
|
// } else {
|
||||||
|
@ -3270,6 +3291,12 @@ impl Render for CollabPanel {
|
||||||
el.child(self.render_signed_in(cx))
|
el.child(self.render_signed_in(cx))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.children(self.context_menu.as_ref().map(|(menu, position, _)| {
|
||||||
|
overlay()
|
||||||
|
.position(*position)
|
||||||
|
.anchor(gpui::AnchorCorner::TopLeft)
|
||||||
|
.child(menu.clone())
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3392,14 +3419,15 @@ impl Panel for CollabPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size(&self, cx: &gpui::WindowContext) -> f32 {
|
fn size(&self, cx: &gpui::WindowContext) -> f32 {
|
||||||
self.width
|
self.width.map_or_else(
|
||||||
.unwrap_or_else(|| CollaborationPanelSettings::get_global(cx).default_width)
|
|| CollaborationPanelSettings::get_global(cx).default_width,
|
||||||
|
|width| width.0,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_size(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>) {
|
fn set_size(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>) {
|
||||||
self.width = size;
|
self.width = size.map(|s| px(s));
|
||||||
// todo!()
|
self.serialize(cx);
|
||||||
// self.serialize(cx);
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3541,3 +3569,34 @@ impl FocusableView for CollabPanel {
|
||||||
// .contained()
|
// .contained()
|
||||||
// .with_style(style.container)
|
// .with_style(style.container)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
struct DraggedChannelView {
|
||||||
|
channel: Channel,
|
||||||
|
width: Pixels,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for DraggedChannelView {
|
||||||
|
type Element = Div;
|
||||||
|
|
||||||
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
|
let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone();
|
||||||
|
h_stack()
|
||||||
|
.font(ui_font)
|
||||||
|
.bg(cx.theme().colors().background)
|
||||||
|
.w(self.width)
|
||||||
|
.p_1()
|
||||||
|
.gap_1()
|
||||||
|
.child(
|
||||||
|
IconElement::new(
|
||||||
|
if self.channel.visibility == proto::ChannelVisibility::Public {
|
||||||
|
Icon::Public
|
||||||
|
} else {
|
||||||
|
Icon::Hash
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
)
|
||||||
|
.child(Label::new(self.channel.name.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -824,7 +824,6 @@ impl Interactivity {
|
||||||
.and_then(|group_hover| GroupBounds::get(&group_hover.group, cx));
|
.and_then(|group_hover| GroupBounds::get(&group_hover.group, cx));
|
||||||
|
|
||||||
if let Some(group_bounds) = hover_group_bounds {
|
if let Some(group_bounds) = hover_group_bounds {
|
||||||
// todo!() needs cx.was_top_layer
|
|
||||||
let hovered = group_bounds.contains_point(&cx.mouse_position());
|
let hovered = group_bounds.contains_point(&cx.mouse_position());
|
||||||
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
||||||
if phase == DispatchPhase::Capture {
|
if phase == DispatchPhase::Capture {
|
||||||
|
@ -836,13 +835,13 @@ impl Interactivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.hover_style.is_some()
|
if self.hover_style.is_some()
|
||||||
|| (cx.active_drag.is_some() && !self.drag_over_styles.is_empty())
|
|| cx.active_drag.is_some() && !self.drag_over_styles.is_empty()
|
||||||
{
|
{
|
||||||
let interactive_bounds = interactive_bounds.clone();
|
let bounds = bounds.intersect(&cx.content_mask().bounds);
|
||||||
let hovered = interactive_bounds.visibly_contains(&cx.mouse_position(), cx);
|
let hovered = bounds.contains_point(&cx.mouse_position());
|
||||||
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
||||||
if phase == DispatchPhase::Capture {
|
if phase == DispatchPhase::Capture {
|
||||||
if interactive_bounds.visibly_contains(&event.position, cx) != hovered {
|
if bounds.contains_point(&event.position) != hovered {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1143,7 +1142,9 @@ impl Interactivity {
|
||||||
let mouse_position = cx.mouse_position();
|
let mouse_position = cx.mouse_position();
|
||||||
if let Some(group_hover) = self.group_hover_style.as_ref() {
|
if let Some(group_hover) = self.group_hover_style.as_ref() {
|
||||||
if let Some(group_bounds) = GroupBounds::get(&group_hover.group, cx) {
|
if let Some(group_bounds) = GroupBounds::get(&group_hover.group, cx) {
|
||||||
if group_bounds.contains_point(&mouse_position) {
|
if group_bounds.contains_point(&mouse_position)
|
||||||
|
&& cx.was_top_layer(&mouse_position, cx.stacking_order())
|
||||||
|
{
|
||||||
style.refine(&group_hover.style);
|
style.refine(&group_hover.style);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1162,7 +1163,6 @@ impl Interactivity {
|
||||||
for (state_type, group_drag_style) in &self.group_drag_over_styles {
|
for (state_type, group_drag_style) in &self.group_drag_over_styles {
|
||||||
if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) {
|
if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) {
|
||||||
if *state_type == drag.view.entity_type()
|
if *state_type == drag.view.entity_type()
|
||||||
// todo!() needs to handle cx.content_mask() and cx.is_top()
|
|
||||||
&& group_bounds.contains_point(&mouse_position)
|
&& group_bounds.contains_point(&mouse_position)
|
||||||
{
|
{
|
||||||
style.refine(&group_drag_style.style);
|
style.refine(&group_drag_style.style);
|
||||||
|
@ -1175,7 +1175,6 @@ impl Interactivity {
|
||||||
&& bounds
|
&& bounds
|
||||||
.intersect(&cx.content_mask().bounds)
|
.intersect(&cx.content_mask().bounds)
|
||||||
.contains_point(&mouse_position)
|
.contains_point(&mouse_position)
|
||||||
&& cx.was_top_layer(&mouse_position, cx.stacking_order())
|
|
||||||
{
|
{
|
||||||
style.refine(drag_over_style);
|
style.refine(drag_over_style);
|
||||||
}
|
}
|
||||||
|
|
|
@ -740,7 +740,7 @@ impl<T> Copy for Corners<T> where T: Copy + Clone + Default + Debug {}
|
||||||
Deserialize,
|
Deserialize,
|
||||||
)]
|
)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct Pixels(pub(crate) f32);
|
pub struct Pixels(pub f32);
|
||||||
|
|
||||||
impl std::ops::Div for Pixels {
|
impl std::ops::Div for Pixels {
|
||||||
type Output = f32;
|
type Output = f32;
|
||||||
|
|
|
@ -1480,7 +1480,7 @@ impl Render for ProjectPanel {
|
||||||
.children(self.context_menu.as_ref().map(|(menu, position, _)| {
|
.children(self.context_menu.as_ref().map(|(menu, position, _)| {
|
||||||
overlay()
|
overlay()
|
||||||
.position(*position)
|
.position(*position)
|
||||||
.anchor(gpui::AnchorCorner::BottomLeft)
|
.anchor(gpui::AnchorCorner::TopLeft)
|
||||||
.child(menu.clone())
|
.child(menu.clone())
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -3,29 +3,22 @@ use std::rc::Rc;
|
||||||
use gpui::ClickEvent;
|
use gpui::ClickEvent;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::{Color, Icon, IconButton, IconSize, ToggleState, Toggleable};
|
use crate::{Color, Icon, IconButton, IconSize};
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct Disclosure {
|
pub struct Disclosure {
|
||||||
state: ToggleState,
|
is_open: bool,
|
||||||
on_toggle: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
on_toggle: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Disclosure {
|
impl Disclosure {
|
||||||
pub fn new(state: ToggleState) -> Self {
|
pub fn new(is_open: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
state,
|
is_open,
|
||||||
on_toggle: None,
|
on_toggle: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_toggleable(toggleable: Toggleable) -> Option<Self> {
|
|
||||||
match toggleable {
|
|
||||||
Toggleable::Toggleable(state) => Some(Self::new(state)),
|
|
||||||
Toggleable::NotToggleable => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_toggle(
|
pub fn on_toggle(
|
||||||
mut self,
|
mut self,
|
||||||
handler: impl Into<Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>>,
|
handler: impl Into<Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>>,
|
||||||
|
@ -41,9 +34,10 @@ impl RenderOnce for Disclosure {
|
||||||
fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
|
fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
|
||||||
IconButton::new(
|
IconButton::new(
|
||||||
"toggle",
|
"toggle",
|
||||||
match self.state {
|
if self.is_open {
|
||||||
ToggleState::Toggled => Icon::ChevronDown,
|
Icon::ChevronDown
|
||||||
ToggleState::NotToggled => Icon::ChevronRight,
|
} else {
|
||||||
|
Icon::ChevronRight
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.color(Color::Muted)
|
.color(Color::Muted)
|
||||||
|
|
|
@ -7,7 +7,7 @@ use gpui::{AnyElement, Div};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::{v_stack, Label, ToggleState, Toggleable};
|
use crate::{v_stack, Label};
|
||||||
|
|
||||||
pub use list_header::*;
|
pub use list_header::*;
|
||||||
pub use list_item::*;
|
pub use list_item::*;
|
||||||
|
@ -20,7 +20,7 @@ pub struct List {
|
||||||
/// Defaults to "No items"
|
/// Defaults to "No items"
|
||||||
empty_message: SharedString,
|
empty_message: SharedString,
|
||||||
header: Option<ListHeader>,
|
header: Option<ListHeader>,
|
||||||
toggle: Toggleable,
|
toggle: Option<bool>,
|
||||||
children: SmallVec<[AnyElement; 2]>,
|
children: SmallVec<[AnyElement; 2]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ impl List {
|
||||||
Self {
|
Self {
|
||||||
empty_message: "No items".into(),
|
empty_message: "No items".into(),
|
||||||
header: None,
|
header: None,
|
||||||
toggle: Toggleable::NotToggleable,
|
toggle: None,
|
||||||
children: SmallVec::new(),
|
children: SmallVec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ impl List {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle(mut self, toggle: Toggleable) -> Self {
|
pub fn toggle(mut self, toggle: Option<bool>) -> Self {
|
||||||
self.toggle = toggle;
|
self.toggle = toggle;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ impl RenderOnce for List {
|
||||||
.children(self.header.map(|header| header))
|
.children(self.header.map(|header| header))
|
||||||
.map(|this| match (self.children.is_empty(), self.toggle) {
|
.map(|this| match (self.children.is_empty(), self.toggle) {
|
||||||
(false, _) => this.children(self.children),
|
(false, _) => this.children(self.children),
|
||||||
(true, Toggleable::Toggleable(ToggleState::NotToggled)) => this,
|
(true, Some(false)) => this,
|
||||||
(true, _) => this.child(Label::new(self.empty_message.clone()).color(Color::Muted)),
|
(true, _) => this.child(Label::new(self.empty_message.clone()).color(Color::Muted)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::rc::Rc;
|
||||||
use gpui::{ClickEvent, Div};
|
use gpui::{ClickEvent, Div};
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::{h_stack, Disclosure, Icon, IconButton, IconElement, IconSize, Label, Toggleable};
|
use crate::{h_stack, Disclosure, Icon, IconButton, IconElement, IconSize, Label};
|
||||||
|
|
||||||
pub enum ListHeaderMeta {
|
pub enum ListHeaderMeta {
|
||||||
Tools(Vec<IconButton>),
|
Tools(Vec<IconButton>),
|
||||||
|
@ -17,7 +17,7 @@ pub struct ListHeader {
|
||||||
label: SharedString,
|
label: SharedString,
|
||||||
left_icon: Option<Icon>,
|
left_icon: Option<Icon>,
|
||||||
meta: Option<ListHeaderMeta>,
|
meta: Option<ListHeaderMeta>,
|
||||||
toggle: Toggleable,
|
toggle: Option<bool>,
|
||||||
on_toggle: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
on_toggle: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||||
inset: bool,
|
inset: bool,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
|
@ -30,13 +30,13 @@ impl ListHeader {
|
||||||
left_icon: None,
|
left_icon: None,
|
||||||
meta: None,
|
meta: None,
|
||||||
inset: false,
|
inset: false,
|
||||||
toggle: Toggleable::NotToggleable,
|
toggle: None,
|
||||||
on_toggle: None,
|
on_toggle: None,
|
||||||
selected: false,
|
selected: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle(mut self, toggle: Toggleable) -> Self {
|
pub fn toggle(mut self, toggle: Option<bool>) -> Self {
|
||||||
self.toggle = toggle;
|
self.toggle = toggle;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -114,8 +114,8 @@ impl RenderOnce for ListHeader {
|
||||||
.child(Label::new(self.label.clone()).color(Color::Muted)),
|
.child(Label::new(self.label.clone()).color(Color::Muted)),
|
||||||
)
|
)
|
||||||
.children(
|
.children(
|
||||||
Disclosure::from_toggleable(self.toggle)
|
self.toggle
|
||||||
.map(|disclosure| disclosure.on_toggle(self.on_toggle)),
|
.map(|is_open| Disclosure::new(is_open).on_toggle(self.on_toggle)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(meta),
|
.child(meta),
|
||||||
|
|
|
@ -6,7 +6,7 @@ use gpui::{
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::{Avatar, Disclosure, GraphicSlot, Icon, IconElement, IconSize, Toggleable};
|
use crate::{Avatar, Disclosure, GraphicSlot, Icon, IconElement, IconSize};
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct ListItem {
|
pub struct ListItem {
|
||||||
|
@ -17,7 +17,7 @@ pub struct ListItem {
|
||||||
indent_level: usize,
|
indent_level: usize,
|
||||||
indent_step_size: Pixels,
|
indent_step_size: Pixels,
|
||||||
left_slot: Option<GraphicSlot>,
|
left_slot: Option<GraphicSlot>,
|
||||||
toggle: Toggleable,
|
toggle: Option<bool>,
|
||||||
inset: bool,
|
inset: bool,
|
||||||
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||||
on_toggle: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
on_toggle: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||||
|
@ -33,7 +33,7 @@ impl ListItem {
|
||||||
indent_level: 0,
|
indent_level: 0,
|
||||||
indent_step_size: px(12.),
|
indent_step_size: px(12.),
|
||||||
left_slot: None,
|
left_slot: None,
|
||||||
toggle: Toggleable::NotToggleable,
|
toggle: None,
|
||||||
inset: false,
|
inset: false,
|
||||||
on_click: None,
|
on_click: None,
|
||||||
on_secondary_mouse_down: None,
|
on_secondary_mouse_down: None,
|
||||||
|
@ -70,7 +70,7 @@ impl ListItem {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle(mut self, toggle: Toggleable) -> Self {
|
pub fn toggle(mut self, toggle: Option<bool>) -> Self {
|
||||||
self.toggle = toggle;
|
self.toggle = toggle;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -151,8 +151,8 @@ impl RenderOnce for ListItem {
|
||||||
.items_center()
|
.items_center()
|
||||||
.relative()
|
.relative()
|
||||||
.children(
|
.children(
|
||||||
Disclosure::from_toggleable(self.toggle)
|
self.toggle
|
||||||
.map(|disclosure| disclosure.on_toggle(self.on_toggle)),
|
.map(|is_open| Disclosure::new(is_open).on_toggle(self.on_toggle)),
|
||||||
)
|
)
|
||||||
.map(|this| match self.left_slot {
|
.map(|this| match self.left_slot {
|
||||||
Some(GraphicSlot::Icon(i)) => this.child(
|
Some(GraphicSlot::Icon(i)) => this.child(
|
||||||
|
|
|
@ -2,7 +2,7 @@ use gpui::{Div, Render};
|
||||||
use story::Story;
|
use story::Story;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::{Disclosure, ToggleState};
|
use crate::Disclosure;
|
||||||
|
|
||||||
pub struct DisclosureStory;
|
pub struct DisclosureStory;
|
||||||
|
|
||||||
|
@ -13,8 +13,8 @@ impl Render for DisclosureStory {
|
||||||
Story::container()
|
Story::container()
|
||||||
.child(Story::title_for::<Disclosure>())
|
.child(Story::title_for::<Disclosure>())
|
||||||
.child(Story::label("Toggled"))
|
.child(Story::label("Toggled"))
|
||||||
.child(Disclosure::new(ToggleState::Toggled))
|
.child(Disclosure::new(true))
|
||||||
.child(Story::label("Not Toggled"))
|
.child(Story::label("Not Toggled"))
|
||||||
.child(Disclosure::new(ToggleState::NotToggled))
|
.child(Disclosure::new(false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
/// Whether an element is able to be toggled.
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
|
||||||
pub enum Toggleable {
|
|
||||||
Toggleable(ToggleState),
|
|
||||||
NotToggleable,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The current state of a [`Toggleable`] element.
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
|
||||||
pub enum ToggleState {
|
|
||||||
Toggled,
|
|
||||||
NotToggled,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToggleState {
|
|
||||||
/// Returns whether an entry is toggled.
|
|
||||||
pub fn is_toggled(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
ToggleState::Toggled => true,
|
|
||||||
ToggleState::NotToggled => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<bool> for ToggleState {
|
|
||||||
fn from(toggled: bool) -> Self {
|
|
||||||
match toggled {
|
|
||||||
true => Self::Toggled,
|
|
||||||
false => Self::NotToggled,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ToggleState> for bool {
|
|
||||||
fn from(value: ToggleState) -> Self {
|
|
||||||
value.is_toggled()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -21,7 +21,6 @@ mod selectable;
|
||||||
mod slot;
|
mod slot;
|
||||||
mod styled_ext;
|
mod styled_ext;
|
||||||
mod styles;
|
mod styles;
|
||||||
mod toggleable;
|
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
pub use clickable::*;
|
pub use clickable::*;
|
||||||
|
@ -33,4 +32,3 @@ pub use selectable::*;
|
||||||
pub use slot::*;
|
pub use slot::*;
|
||||||
pub use styled_ext::*;
|
pub use styled_ext::*;
|
||||||
pub use styles::*;
|
pub use styles::*;
|
||||||
pub use toggleable::*;
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue