extract dragged item target

This commit is contained in:
K Simmons 2022-10-22 21:32:07 -07:00
parent d7b8a189e4
commit 8cde64d3f6
4 changed files with 179 additions and 197 deletions

View file

@ -24,12 +24,13 @@ impl Element for Stack {
fn layout( fn layout(
&mut self, &mut self,
constraint: SizeConstraint, mut constraint: SizeConstraint,
cx: &mut LayoutContext, cx: &mut LayoutContext,
) -> (Vector2F, Self::LayoutState) { ) -> (Vector2F, Self::LayoutState) {
let mut size = constraint.min; let mut size = constraint.min;
for child in &mut self.children { for child in &mut self.children {
size = size.max(child.layout(constraint, cx)); size = size.max(child.layout(constraint, cx));
constraint.min = size;
} }
(size, ()) (size, ())
} }

View file

@ -9,7 +9,9 @@ use serde::Deserialize;
use settings::{DockAnchor, Settings}; use settings::{DockAnchor, Settings};
use theme::Theme; use theme::Theme;
use crate::{sidebar::SidebarSide, ItemHandle, Pane, StatusItemView, Workspace}; use crate::{
handle_dropped_item, sidebar::SidebarSide, ItemHandle, Pane, StatusItemView, Workspace,
};
#[derive(PartialEq, Clone, Deserialize)] #[derive(PartialEq, Clone, Deserialize)]
pub struct MoveDock(pub DockAnchor); pub struct MoveDock(pub DockAnchor);
@ -376,6 +378,7 @@ impl View for ToggleDockButton {
let dock_position = workspace.read(cx).dock.position; let dock_position = workspace.read(cx).dock.position;
let theme = cx.global::<Settings>().theme.clone(); let theme = cx.global::<Settings>().theme.clone();
let button = MouseEventHandler::<Self>::new(0, cx, { let button = MouseEventHandler::<Self>::new(0, cx, {
let theme = theme.clone(); let theme = theme.clone();
move |state, _| { move |state, _| {
@ -400,7 +403,7 @@ impl View for ToggleDockButton {
.on_up(MouseButton::Left, move |event, cx| { .on_up(MouseButton::Left, move |event, cx| {
let dock_pane = workspace.read(cx.app).dock_pane(); let dock_pane = workspace.read(cx.app).dock_pane();
let drop_index = dock_pane.read(cx.app).items_len() + 1; let drop_index = dock_pane.read(cx.app).items_len() + 1;
Pane::handle_dropped_item(event, &dock_pane.downgrade(), drop_index, false, None, cx); handle_dropped_item(event, &dock_pane.downgrade(), drop_index, false, None, cx);
}); });
if dock_position.is_visible() { if dock_position.is_visible() {

View file

@ -1,17 +1,19 @@
mod dragged_item_receiver;
use super::{ItemHandle, SplitDirection}; use super::{ItemHandle, SplitDirection};
use crate::{ use crate::{
dock::{icon_for_dock_anchor, AnchorDockBottom, AnchorDockRight, ExpandDock, HideDock}, dock::{icon_for_dock_anchor, AnchorDockBottom, AnchorDockRight, ExpandDock, HideDock},
toolbar::Toolbar, toolbar::Toolbar,
Item, NewFile, NewSearch, NewTerminal, SplitWithItem, WeakItemHandle, Workspace, Item, NewFile, NewSearch, NewTerminal, WeakItemHandle, Workspace,
}; };
use anyhow::Result; use anyhow::Result;
use collections::{HashMap, HashSet, VecDeque}; use collections::{HashMap, HashSet, VecDeque};
use context_menu::{ContextMenu, ContextMenuItem}; use context_menu::{ContextMenu, ContextMenuItem};
use drag_and_drop::{DragAndDrop, Draggable}; use drag_and_drop::Draggable;
pub use dragged_item_receiver::{dragged_item_receiver, handle_dropped_item};
use futures::StreamExt; use futures::StreamExt;
use gpui::{ use gpui::{
actions, actions,
color::Color,
elements::*, elements::*,
geometry::{ geometry::{
rect::RectF, rect::RectF,
@ -19,7 +21,6 @@ use gpui::{
}, },
impl_actions, impl_internal_actions, impl_actions, impl_internal_actions,
platform::{CursorStyle, NavigationDirection}, platform::{CursorStyle, NavigationDirection},
scene::MouseUp,
Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext, Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext,
ModelHandle, MouseButton, MutableAppContext, PromptLevel, Quad, RenderContext, Task, View, ModelHandle, MouseButton, MutableAppContext, PromptLevel, Quad, RenderContext, Task, View,
ViewContext, ViewHandle, WeakViewHandle, ViewContext, ViewHandle, WeakViewHandle,
@ -1056,11 +1057,7 @@ impl Pane {
fn render_tabs(&mut self, cx: &mut RenderContext<Self>) -> impl Element { fn render_tabs(&mut self, cx: &mut RenderContext<Self>) -> impl Element {
let theme = cx.global::<Settings>().theme.clone(); let theme = cx.global::<Settings>().theme.clone();
let filler_index = self.items.len();
enum Tabs {}
enum Tab {}
enum Filler {}
let pane = cx.handle(); let pane = cx.handle();
let autoscroll = if mem::take(&mut self.autoscroll) { let autoscroll = if mem::take(&mut self.autoscroll) {
Some(self.active_item_index) Some(self.active_item_index)
@ -1070,6 +1067,7 @@ impl Pane {
let pane_active = self.is_active; let pane_active = self.is_active;
enum Tabs {}
let mut row = Flex::row().scrollable::<Tabs, _>(1, autoscroll, cx); let mut row = Flex::row().scrollable::<Tabs, _>(1, autoscroll, cx);
for (ix, (item, detail)) in self for (ix, (item, detail)) in self
.items .items
@ -1082,7 +1080,8 @@ impl Pane {
let tab_active = ix == self.active_item_index; let tab_active = ix == self.active_item_index;
row.add_child({ row.add_child({
MouseEventHandler::<Tab>::above(ix, cx, { enum Tab {}
dragged_item_receiver::<Tab, _>(ix, ix, true, None, cx, {
let item = item.clone(); let item = item.clone();
let pane = pane.clone(); let pane = pane.clone();
let detail = detail.clone(); let detail = detail.clone();
@ -1092,16 +1091,7 @@ impl Pane {
move |mouse_state, cx| { move |mouse_state, cx| {
let tab_style = theme.workspace.tab_bar.tab_style(pane_active, tab_active); let tab_style = theme.workspace.tab_bar.tab_style(pane_active, tab_active);
let hovered = mouse_state.hovered(); let hovered = mouse_state.hovered();
Self::render_tab( Self::render_tab(&item, pane, ix == 0, detail, hovered, tab_style, cx)
&item,
pane,
ix == 0,
detail,
hovered,
Self::tab_overlay_color(hovered, cx),
tab_style,
cx,
)
} }
}) })
.with_cursor_style(if pane_active && tab_active { .with_cursor_style(if pane_active && tab_active {
@ -1123,10 +1113,6 @@ impl Pane {
}) })
} }
}) })
.on_up(MouseButton::Left, {
let pane = pane.clone();
move |event, cx| Pane::handle_dropped_item(event, &pane, ix, true, None, cx)
})
.as_draggable( .as_draggable(
DraggedItem { DraggedItem {
item, item,
@ -1144,7 +1130,6 @@ impl Pane {
false, false,
detail, detail,
false, false,
None,
&tab_style, &tab_style,
cx, cx,
) )
@ -1157,22 +1142,16 @@ impl Pane {
// Use the inactive tab style along with the current pane's active status to decide how to render // Use the inactive tab style along with the current pane's active status to decide how to render
// the filler // the filler
let filler_index = self.items.len();
let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false); let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false);
enum Filler {}
row.add_child( row.add_child(
MouseEventHandler::<Filler>::new(0, cx, |mouse_state, cx| { dragged_item_receiver::<Filler, _>(0, filler_index, true, None, cx, |_, _| {
let mut filler = Empty::new() Empty::new()
.contained() .contained()
.with_style(filler_style.container) .with_style(filler_style.container)
.with_border(filler_style.container.border); .with_border(filler_style.container.border)
.boxed()
if let Some(overlay) = Self::tab_overlay_color(mouse_state.hovered(), cx) {
filler = filler.with_overlay_color(overlay);
}
filler.boxed()
})
.on_up(MouseButton::Left, move |event, cx| {
Pane::handle_dropped_item(event, &pane, filler_index, true, None, cx)
}) })
.flex(1., true) .flex(1., true)
.named("filler"), .named("filler"),
@ -1224,7 +1203,6 @@ impl Pane {
first: bool, first: bool,
detail: Option<usize>, detail: Option<usize>,
hovered: bool, hovered: bool,
overlay: Option<Color>,
tab_style: &theme::Tab, tab_style: &theme::Tab,
cx: &mut RenderContext<V>, cx: &mut RenderContext<V>,
) -> ElementBox { ) -> ElementBox {
@ -1234,7 +1212,7 @@ impl Pane {
container.border.left = false; container.border.left = false;
} }
let mut tab = Flex::row() Flex::row()
.with_child( .with_child(
Align::new({ Align::new({
let diameter = 7.0; let diameter = 7.0;
@ -1312,13 +1290,10 @@ impl Pane {
.boxed(), .boxed(),
) )
.contained() .contained()
.with_style(container); .with_style(container)
.constrained()
if let Some(overlay) = overlay { .with_height(tab_style.height)
tab = tab.with_overlay_color(overlay); .boxed()
}
tab.constrained().with_height(tab_style.height).boxed()
} }
fn render_tab_bar_buttons( fn render_tab_bar_buttons(
@ -1356,79 +1331,6 @@ impl Pane {
.flex(1., false) .flex(1., false)
.boxed() .boxed()
} }
pub fn handle_dropped_item(
event: MouseUp,
pane: &WeakViewHandle<Pane>,
index: usize,
allow_same_pane: bool,
split_margin: Option<f32>,
cx: &mut EventContext,
) {
if let Some((_, dragged_item)) = cx
.global::<DragAndDrop<Workspace>>()
.currently_dragged::<DraggedItem>(cx.window_id)
{
if let Some(split_direction) = split_margin
.and_then(|margin| Self::drop_split_direction(event.position, event.region, margin))
{
cx.dispatch_action(SplitWithItem {
from: dragged_item.pane.clone(),
item_id_to_move: dragged_item.item.id(),
pane_to_split: pane.clone(),
split_direction,
});
} else if pane != &dragged_item.pane || allow_same_pane {
// If no split margin or not close enough to the edge, just move the item
cx.dispatch_action(MoveItem {
item_id: dragged_item.item.id(),
from: dragged_item.pane.clone(),
to: pane.clone(),
destination_index: index,
})
}
} else {
cx.propagate_event();
}
}
fn drop_split_direction(
position: Vector2F,
region: RectF,
split_margin: f32,
) -> Option<SplitDirection> {
let mut min_direction = None;
let mut min_distance = split_margin;
for direction in SplitDirection::all() {
let edge_distance =
(direction.edge(region) - direction.axis().component(position)).abs();
if edge_distance < min_distance {
min_direction = Some(direction);
min_distance = edge_distance;
}
}
min_direction
}
fn tab_overlay_color(hovered: bool, cx: &mut RenderContext<Self>) -> Option<Color> {
if hovered
&& cx
.global::<DragAndDrop<Workspace>>()
.currently_dragged::<DraggedItem>(cx.window_id())
.is_some()
{
Some(
cx.global::<Settings>()
.theme
.workspace
.drop_target_overlay_color,
)
} else {
None
}
}
} }
impl Entity for Pane { impl Entity for Pane {
@ -1449,8 +1351,6 @@ impl View for Pane {
.with_child( .with_child(
MouseEventHandler::<MouseNavigationHandler>::new(0, cx, |_, cx| { MouseEventHandler::<MouseNavigationHandler>::new(0, cx, |_, cx| {
if let Some(active_item) = self.active_item() { if let Some(active_item) = self.active_item() {
enum PaneContentTabDropTarget {}
Flex::column() Flex::column()
.with_child({ .with_child({
let mut tab_row = Flex::row() let mut tab_row = Flex::row()
@ -1471,78 +1371,29 @@ impl View for Pane {
.named("tab bar") .named("tab bar")
}) })
.with_child({ .with_child({
let drop_index = self.active_item_index + 1; enum PaneContentTabDropTarget {}
MouseEventHandler::<PaneContentTabDropTarget>::above( dragged_item_receiver::<PaneContentTabDropTarget, _>(
0, 0,
self.active_item_index + 1,
false,
Some(100.),
cx, cx,
|state, cx| { {
let overlay_color = Self::tab_overlay_color(true, cx); let toolbar = self.toolbar.clone();
// Hovered will cause a render when the mouse enters regardless move |_, cx| {
// of if mouse position was accessed before Flex::column()
let hovered = state.hovered(); .with_child(
let drag_position = cx ChildView::new(&toolbar, cx).expanded().boxed(),
.global::<DragAndDrop<Workspace>>() )
.currently_dragged::<DraggedItem>(cx.window_id()) .with_child(
.filter(|_| hovered) ChildView::new(active_item, cx)
.map(|_| state.mouse_position()); .flex(1., true)
.boxed(),
Stack::new() )
.with_child(
Flex::column()
.with_child(
ChildView::new(&self.toolbar, cx)
.expanded()
.boxed(),
)
.with_child(
ChildView::new(active_item, cx)
.flex(1., true)
.boxed(),
)
.boxed(),
)
.with_children(drag_position.map(|drag_position| {
Canvas::new(move |region, _, cx| {
if region.contains_point(drag_position) {
let overlay_region = if let Some(
split_direction,
) =
Self::drop_split_direction(
drag_position,
region,
100., /* Replace with theme value */
) {
split_direction.along_edge(region, 100.)
} else {
region
};
cx.scene.push_quad(Quad {
bounds: overlay_region,
background: overlay_color,
border: Default::default(),
corner_radius: 0.,
});
}
})
.boxed() .boxed()
})) }
.boxed()
}, },
) )
.on_up(MouseButton::Left, {
let pane = cx.handle();
move |event, cx| {
Pane::handle_dropped_item(
event,
&pane,
drop_index,
false,
Some(100.), /* Use theme value */
cx,
)
}
})
.flex(1., true) .flex(1., true)
.boxed() .boxed()
}) })
@ -1551,7 +1402,7 @@ impl View for Pane {
enum EmptyPane {} enum EmptyPane {}
let theme = cx.global::<Settings>().theme.clone(); let theme = cx.global::<Settings>().theme.clone();
MouseEventHandler::<EmptyPane>::new(0, cx, |_, _| { dragged_item_receiver::<EmptyPane, _>(0, 0, false, None, cx, |_, _| {
Empty::new() Empty::new()
.contained() .contained()
.with_background_color(theme.workspace.background) .with_background_color(theme.workspace.background)
@ -1560,12 +1411,6 @@ impl View for Pane {
.on_down(MouseButton::Left, |_, cx| { .on_down(MouseButton::Left, |_, cx| {
cx.focus_parent_view(); cx.focus_parent_view();
}) })
.on_up(MouseButton::Left, {
let pane = this.clone();
move |event, cx| {
Pane::handle_dropped_item(event, &pane, 0, true, None, cx)
}
})
.boxed() .boxed()
} }
}) })

View file

@ -0,0 +1,133 @@
use drag_and_drop::DragAndDrop;
use gpui::{
color::Color,
elements::{Canvas, MouseEventHandler, ParentElement, Stack},
geometry::{rect::RectF, vector::Vector2F},
scene::MouseUp,
AppContext, Element, ElementBox, EventContext, MouseButton, MouseState, Quad, RenderContext,
WeakViewHandle,
};
use settings::Settings;
use crate::{MoveItem, Pane, SplitDirection, SplitWithItem, Workspace};
use super::DraggedItem;
pub fn dragged_item_receiver<Tag, F>(
region_id: usize,
drop_index: usize,
allow_same_pane: bool,
split_margin: Option<f32>,
cx: &mut RenderContext<Pane>,
render_child: F,
) -> MouseEventHandler<Tag>
where
Tag: 'static,
F: FnOnce(&mut MouseState, &mut RenderContext<Pane>) -> ElementBox,
{
MouseEventHandler::<Tag>::above(region_id, cx, |state, cx| {
// Observing hovered will cause a render when the mouse enters regardless
// of if mouse position was accessed before
let hovered = state.hovered();
let drag_position = cx
.global::<DragAndDrop<Workspace>>()
.currently_dragged::<DraggedItem>(cx.window_id())
.filter(|_| hovered)
.map(|_| state.mouse_position());
Stack::new()
.with_child(render_child(state, cx))
.with_children(drag_position.map(|drag_position| {
Canvas::new(move |bounds, _, cx| {
if bounds.contains_point(drag_position) {
let overlay_region = split_margin
.and_then(|split_margin| {
drop_split_direction(drag_position, bounds, split_margin)
.map(|dir| (dir, split_margin))
})
.map(|(dir, margin)| dir.along_edge(bounds, margin))
.unwrap_or(bounds);
cx.paint_stacking_context(None, |cx| {
cx.scene.push_quad(Quad {
bounds: overlay_region,
background: Some(overlay_color(cx)),
border: Default::default(),
corner_radius: 0.,
});
});
}
})
.boxed()
}))
.boxed()
})
.on_up(MouseButton::Left, {
let pane = cx.handle();
move |event, cx| {
handle_dropped_item(event, &pane, drop_index, allow_same_pane, split_margin, cx);
cx.notify();
}
})
}
pub fn handle_dropped_item(
event: MouseUp,
pane: &WeakViewHandle<Pane>,
index: usize,
allow_same_pane: bool,
split_margin: Option<f32>,
cx: &mut EventContext,
) {
if let Some((_, dragged_item)) = cx
.global::<DragAndDrop<Workspace>>()
.currently_dragged::<DraggedItem>(cx.window_id)
{
if let Some(split_direction) = split_margin
.and_then(|margin| drop_split_direction(event.position, event.region, margin))
{
cx.dispatch_action(SplitWithItem {
from: dragged_item.pane.clone(),
item_id_to_move: dragged_item.item.id(),
pane_to_split: pane.clone(),
split_direction,
});
} else if pane != &dragged_item.pane || allow_same_pane {
// If no split margin or not close enough to the edge, just move the item
cx.dispatch_action(MoveItem {
item_id: dragged_item.item.id(),
from: dragged_item.pane.clone(),
to: pane.clone(),
destination_index: index,
})
}
} else {
cx.propagate_event();
}
}
fn drop_split_direction(
position: Vector2F,
region: RectF,
split_margin: f32,
) -> Option<SplitDirection> {
let mut min_direction = None;
let mut min_distance = split_margin;
for direction in SplitDirection::all() {
let edge_distance = (direction.edge(region) - direction.axis().component(position)).abs();
if edge_distance < min_distance {
min_direction = Some(direction);
min_distance = edge_distance;
}
}
min_direction
}
fn overlay_color(cx: &AppContext) -> Color {
cx.global::<Settings>()
.theme
.workspace
.drop_target_overlay_color
}