Add channel drag'n'drop
Co-Authored-By: Max <max@zed.dev>
This commit is contained in:
parent
41e7653906
commit
e377bd805b
3 changed files with 192 additions and 116 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};
|
||||||
|
@ -171,8 +172,8 @@ use gpui::{
|
||||||
actions, div, img, overlay, prelude::*, px, rems, serde_json, Action, AppContext,
|
actions, div, img, overlay, prelude::*, px, rems, serde_json, Action, AppContext,
|
||||||
AsyncWindowContext, ClipboardItem, DismissEvent, Div, EventEmitter, FocusHandle, Focusable,
|
AsyncWindowContext, ClipboardItem, DismissEvent, Div, EventEmitter, FocusHandle, Focusable,
|
||||||
FocusableView, InteractiveElement, IntoElement, Model, MouseDownEvent, ParentElement, Pixels,
|
FocusableView, InteractiveElement, IntoElement, Model, MouseDownEvent, ParentElement, Pixels,
|
||||||
Point, PromptLevel, Render, RenderOnce, SharedString, Styled, Subscription, Task, View,
|
Point, PromptLevel, Render, RenderOnce, SharedString, Stateful, Styled, Subscription, Task,
|
||||||
ViewContext, VisualContext, WeakView,
|
View, ViewContext, VisualContext, WeakView,
|
||||||
};
|
};
|
||||||
use project::Fs;
|
use project::Fs;
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
@ -284,7 +285,7 @@ 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>,
|
||||||
|
@ -318,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>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2500,13 +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(ui::Toggle::Toggled(is_collapsed)).on_toggle(
|
el.toggle(ui::Toggle::Toggled(is_collapsed)).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)
|
||||||
|
},
|
||||||
|
))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2771,80 +2790,112 @@ impl CollabPanel {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
div().group("").child(
|
let width = self.width.unwrap_or(px(240.));
|
||||||
ListItem::new(channel_id as usize)
|
|
||||||
.indent_level(depth)
|
div()
|
||||||
.indent_step_size(cx.rem_size() * 14.0 / 16.0) // @todo()! @nate this is to step over the disclosure toggle
|
.id(channel_id as usize)
|
||||||
.left_icon(if is_public { Icon::Public } else { Icon::Hash })
|
.group("")
|
||||||
.selected(is_selected || is_active)
|
.on_drag({
|
||||||
.child(
|
let channel = channel.clone();
|
||||||
h_stack()
|
move |cx| {
|
||||||
.w_full()
|
let channel = channel.clone();
|
||||||
.justify_between()
|
cx.build_view({ |cx| DraggedChannelView { channel, width } })
|
||||||
.child(
|
}
|
||||||
h_stack()
|
})
|
||||||
.id(channel_id as usize)
|
.on_drop(
|
||||||
.child(Label::new(channel.name.clone()))
|
cx.listener(move |this, view: &View<DraggedChannelView>, cx| {
|
||||||
.children(face_pile.map(|face_pile| face_pile.render(cx)))
|
this.channel_store
|
||||||
.tooltip(|cx| Tooltip::text("Join channel", cx)),
|
.update(cx, |channel_store, cx| {
|
||||||
)
|
channel_store.move_channel(
|
||||||
.child(
|
view.read(cx).channel.id,
|
||||||
h_stack()
|
Some(channel_id),
|
||||||
.child(
|
cx,
|
||||||
div()
|
)
|
||||||
.id("channel_chat")
|
})
|
||||||
.when(!has_messages_notification, |el| el.invisible())
|
.detach_and_log_err(cx)
|
||||||
.group_hover("", |style| style.visible())
|
}),
|
||||||
.child(
|
)
|
||||||
IconButton::new("channel_chat", Icon::MessageBubbles)
|
.child(
|
||||||
|
ListItem::new(channel_id as usize)
|
||||||
|
.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 })
|
||||||
|
.selected(is_selected || is_active)
|
||||||
|
.child(
|
||||||
|
h_stack()
|
||||||
|
.w_full()
|
||||||
|
.justify_between()
|
||||||
|
.child(
|
||||||
|
h_stack()
|
||||||
|
.id(channel_id as usize)
|
||||||
|
.child(Label::new(channel.name.clone()))
|
||||||
|
.children(face_pile.map(|face_pile| face_pile.render(cx)))
|
||||||
|
.tooltip(|cx| Tooltip::text("Join channel", cx)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
h_stack()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.id("channel_chat")
|
||||||
|
.when(!has_messages_notification, |el| el.invisible())
|
||||||
|
.group_hover("", |style| style.visible())
|
||||||
|
.child(
|
||||||
|
IconButton::new(
|
||||||
|
"channel_chat",
|
||||||
|
Icon::MessageBubbles,
|
||||||
|
)
|
||||||
.color(if has_messages_notification {
|
.color(if has_messages_notification {
|
||||||
Color::Default
|
Color::Default
|
||||||
} else {
|
} else {
|
||||||
Color::Muted
|
Color::Muted
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.tooltip(|cx| Tooltip::text("Open channel chat", cx)),
|
.tooltip(|cx| Tooltip::text("Open channel chat", cx)),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.id("channel_notes")
|
.id("channel_notes")
|
||||||
.when(!has_notes_notification, |el| el.invisible())
|
.when(!has_notes_notification, |el| el.invisible())
|
||||||
.group_hover("", |style| style.visible())
|
.group_hover("", |style| style.visible())
|
||||||
.child(
|
.child(
|
||||||
IconButton::new("channel_notes", Icon::File)
|
IconButton::new("channel_notes", Icon::File)
|
||||||
.color(if has_notes_notification {
|
.color(if has_notes_notification {
|
||||||
Color::Default
|
Color::Default
|
||||||
} else {
|
} else {
|
||||||
Color::Muted
|
Color::Muted
|
||||||
})
|
})
|
||||||
.tooltip(|cx| {
|
.tooltip(|cx| {
|
||||||
Tooltip::text("Open channel notes", cx)
|
Tooltip::text("Open channel notes", cx)
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toggle(if has_children {
|
.toggle(if has_children {
|
||||||
Toggle::Toggled(disclosed)
|
Toggle::Toggled(disclosed)
|
||||||
} else {
|
} else {
|
||||||
Toggle::NotToggleable
|
Toggle::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| {
|
}),
|
||||||
if this.drag_target_channel == ChannelDragTarget::None {
|
)
|
||||||
if is_active {
|
.on_click(cx.listener(move |this, _, cx| {
|
||||||
this.open_channel_notes(channel_id, cx)
|
if this.drag_target_channel == ChannelDragTarget::None {
|
||||||
} else {
|
if is_active {
|
||||||
this.join_channel(channel_id, cx)
|
this.open_channel_notes(channel_id, cx)
|
||||||
|
} else {
|
||||||
|
this.join_channel(channel_id, cx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}))
|
||||||
}))
|
.on_secondary_mouse_down(cx.listener(
|
||||||
.on_secondary_mouse_down(cx.listener(move |this, event: &MouseDownEvent, cx| {
|
move |this, event: &MouseDownEvent, cx| {
|
||||||
this.deploy_channel_context_menu(event.position, channel_id, ix, cx)
|
this.deploy_channel_context_menu(event.position, channel_id, ix, cx)
|
||||||
})),
|
},
|
||||||
)
|
)),
|
||||||
|
)
|
||||||
|
|
||||||
// let channel_id = channel.id;
|
// let channel_id = channel.id;
|
||||||
// let collab_theme = &theme.collab_panel;
|
// let collab_theme = &theme.collab_panel;
|
||||||
|
@ -3072,7 +3123,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 {
|
||||||
|
@ -3369,14 +3421,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3518,3 +3571,30 @@ 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
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.child(Label::new(self.channel.name.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -94,42 +94,38 @@ impl RenderOnce for ListHeader {
|
||||||
None => div(),
|
None => div(),
|
||||||
};
|
};
|
||||||
|
|
||||||
h_stack()
|
h_stack().w_full().relative().child(
|
||||||
.w_full()
|
div()
|
||||||
.bg(cx.theme().colors().surface_background)
|
.h_5()
|
||||||
.relative()
|
.when(self.inset, |this| this.px_2())
|
||||||
.child(
|
.when(self.selected, |this| {
|
||||||
div()
|
this.bg(cx.theme().colors().ghost_element_selected)
|
||||||
.h_5()
|
})
|
||||||
.when(self.inset, |this| this.px_2())
|
.flex()
|
||||||
.when(self.selected, |this| {
|
.flex_1()
|
||||||
this.bg(cx.theme().colors().ghost_element_selected)
|
.items_center()
|
||||||
})
|
.justify_between()
|
||||||
.flex()
|
.w_full()
|
||||||
.flex_1()
|
.gap_1()
|
||||||
.items_center()
|
.child(
|
||||||
.justify_between()
|
h_stack()
|
||||||
.w_full()
|
.gap_1()
|
||||||
.gap_1()
|
.child(
|
||||||
.child(
|
div()
|
||||||
h_stack()
|
.flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.child(
|
.items_center()
|
||||||
div()
|
.children(self.left_icon.map(|i| {
|
||||||
.flex()
|
IconElement::new(i)
|
||||||
.gap_1()
|
.color(Color::Muted)
|
||||||
.items_center()
|
.size(IconSize::Small)
|
||||||
.children(self.left_icon.map(|i| {
|
}))
|
||||||
IconElement::new(i)
|
.child(Label::new(self.label.clone()).color(Color::Muted)),
|
||||||
.color(Color::Muted)
|
)
|
||||||
.size(IconSize::Small)
|
.child(disclosure_control),
|
||||||
}))
|
)
|
||||||
.child(Label::new(self.label.clone()).color(Color::Muted)),
|
.child(meta),
|
||||||
)
|
)
|
||||||
.child(disclosure_control),
|
|
||||||
)
|
|
||||||
.child(meta),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue