Add channel drag'n'drop

Co-Authored-By: Max <max@zed.dev>
This commit is contained in:
Conrad Irwin 2023-11-29 12:24:04 -07:00
parent 41e7653906
commit e377bd805b
3 changed files with 192 additions and 116 deletions

View file

@ -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()))
}
}

View file

@ -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;

View file

@ -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),
)
} }
} }