diff --git a/crates/gpui/examples/drag_drop.rs b/crates/gpui/examples/drag_drop.rs new file mode 100644 index 0000000000..cf5048f6d3 --- /dev/null +++ b/crates/gpui/examples/drag_drop.rs @@ -0,0 +1,137 @@ +use gpui::{ + App, Application, Bounds, Context, Half, Hsla, Pixels, Point, Window, WindowBounds, + WindowOptions, div, prelude::*, px, rgb, size, +}; + +#[derive(Clone, Copy)] +struct DragInfo { + ix: usize, + color: Hsla, + position: Point, +} + +impl DragInfo { + fn new(ix: usize, color: Hsla) -> Self { + Self { + ix, + color, + position: Point::default(), + } + } + + fn position(mut self, pos: Point) -> Self { + self.position = pos; + self + } +} + +impl Render for DragInfo { + fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement { + let size = gpui::size(px(120.), px(50.)); + + div() + .pl(self.position.x - size.width.half()) + .pt(self.position.y - size.height.half()) + .child( + div() + .flex() + .justify_center() + .items_center() + .w(size.width) + .h(size.height) + .bg(self.color.opacity(0.5)) + .text_color(gpui::white()) + .text_xs() + .shadow_md() + .child(format!("Item {}", self.ix)), + ) + } +} + +struct DragDrop { + drop_on: Option, +} + +impl DragDrop { + fn new() -> Self { + Self { drop_on: None } + } +} + +impl Render for DragDrop { + fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + let items = [gpui::blue(), gpui::red(), gpui::green()]; + + div() + .size_full() + .flex() + .flex_col() + .gap_5() + .bg(gpui::white()) + .justify_center() + .items_center() + .text_color(rgb(0x333333)) + .child(div().text_xl().text_center().child("Drop & Drop")) + .child( + div() + .w_full() + .mb_10() + .justify_center() + .flex() + .flex_row() + .gap_4() + .items_center() + .children(items.into_iter().enumerate().map(|(ix, color)| { + let drag_info = DragInfo::new(ix, color); + + div() + .id(("item", ix)) + .size_32() + .flex() + .justify_center() + .items_center() + .border_2() + .border_color(color) + .text_color(color) + .cursor_move() + .hover(|this| this.bg(color.opacity(0.2))) + .child(format!("Item ({})", ix)) + .on_drag(drag_info, |info: &DragInfo, position, _, cx| { + cx.new(|_| info.position(position)) + }) + })), + ) + .child( + div() + .id("drop-target") + .w_128() + .h_32() + .flex() + .justify_center() + .items_center() + .border_3() + .border_color(self.drop_on.map(|info| info.color).unwrap_or(gpui::black())) + .when_some(self.drop_on, |this, info| this.bg(info.color.opacity(0.5))) + .on_drop(cx.listener(|this, info: &DragInfo, _, _| { + this.drop_on = Some(*info); + })) + .child("Drop items here"), + ) + } +} + +fn main() { + Application::new().run(|cx: &mut App| { + let bounds = Bounds::centered(None, size(px(800.), px(600.0)), cx); + cx.open_window( + WindowOptions { + window_bounds: Some(WindowBounds::Windowed(bounds)), + ..Default::default() + }, + |_, cx| cx.new(|_| DragDrop::new()), + ) + .unwrap(); + + cx.activate(true); + }); +} diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 8d0b186a52..c7c2818b7e 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -31,10 +31,10 @@ use util::ResultExt; use crate::{ Action, ActionBuildError, ActionRegistry, Any, AnyView, AnyWindowHandle, AppContext, Asset, - AssetSource, BackgroundExecutor, Bounds, ClipboardItem, DispatchPhase, DisplayId, EventEmitter, - FocusHandle, FocusMap, ForegroundExecutor, Global, KeyBinding, Keymap, Keystroke, LayoutId, - Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, - PromptBuilder, PromptHandle, PromptLevel, Render, RenderablePromptHandle, Reservation, + AssetSource, BackgroundExecutor, Bounds, ClipboardItem, CursorStyle, DispatchPhase, DisplayId, + EventEmitter, FocusHandle, FocusMap, ForegroundExecutor, Global, KeyBinding, Keymap, Keystroke, + LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay, + Point, PromptBuilder, PromptHandle, PromptLevel, Render, RenderablePromptHandle, Reservation, ScreenCaptureSource, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, Window, WindowAppearance, WindowHandle, WindowId, WindowInvalidator, current_platform, hash, init_app_menus, @@ -1803,6 +1803,9 @@ pub struct AnyDrag { /// This is used to render the dragged item in the same place /// on the original element that the drag was initiated pub cursor_offset: Point, + + /// The cursor style to use while dragging + pub cursor_style: Option, } /// Contains state associated with a tooltip. You'll only need this struct if you're implementing diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index 6e52e917e4..543881fbfd 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -1615,7 +1615,11 @@ impl Interactivity { global_id, hitbox, &style, window, cx, ); - if !cx.has_active_drag() { + if let Some(drag) = cx.active_drag.as_ref() { + if let Some(mouse_cursor) = drag.cursor_style { + window.set_cursor_style(mouse_cursor, None); + } + } else { if let Some(mouse_cursor) = style.mouse_cursor { window.set_cursor_style(mouse_cursor, Some(hitbox)); } @@ -1838,6 +1842,7 @@ impl Interactivity { } }); } + let drag_cursor_style = self.base_style.as_ref().mouse_cursor; let mut drag_listener = mem::take(&mut self.drag_listener); let drop_listeners = mem::take(&mut self.drop_listeners); @@ -1929,6 +1934,7 @@ impl Interactivity { view: drag, value: drag_value, cursor_offset, + cursor_style: drag_cursor_style, }); pending_mouse_down.take(); window.refresh(); @@ -2269,6 +2275,7 @@ impl Interactivity { } } + style.mouse_cursor = drag.cursor_style; cx.active_drag = Some(drag); } } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 35b53a2829..de044ea9df 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -3093,6 +3093,7 @@ impl Window { value: Arc::new(paths.clone()), view: cx.new(|_| paths).into(), cursor_offset: position, + cursor_style: None, }); } PlatformInput::MouseMove(MouseMoveEvent {