Add OS file drop event handler

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Kirill Bulatov 2023-10-24 19:24:21 +02:00
parent 5b04f965fa
commit a01b507ef4
9 changed files with 141 additions and 85 deletions

View file

@ -821,7 +821,7 @@ impl<G: 'static> DerefMut for GlobalLease<G> {
} }
pub(crate) struct AnyDrag { pub(crate) struct AnyDrag {
pub drag_handle_view: AnyView, pub drag_handle_view: Option<AnyView>,
pub cursor_offset: Point<Pixels>, pub cursor_offset: Point<Pixels>,
pub state: AnyBox, pub state: AnyBox,
pub state_type: TypeId, pub state_type: TypeId,

View file

@ -374,10 +374,12 @@ pub trait StatefulInteractive: StatelessInteractive {
Some(Arc::new(move |view_state, cursor_offset, cx| { Some(Arc::new(move |view_state, cursor_offset, cx| {
let drag = listener(view_state, cx); let drag = listener(view_state, cx);
let view_handle = cx.handle().upgrade().unwrap(); let view_handle = cx.handle().upgrade().unwrap();
let drag_handle_view = view(view_handle, move |view_state, cx| { let drag_handle_view = Some(
view(view_handle, move |view_state, cx| {
(drag.render_drag_handle)(view_state, cx) (drag.render_drag_handle)(view_state, cx)
}) })
.into_any(); .into_any(),
);
AnyDrag { AnyDrag {
drag_handle_view, drag_handle_view,
cursor_offset, cursor_offset,
@ -780,11 +782,7 @@ impl GroupBounds {
} }
pub fn pop(name: &SharedString, cx: &mut AppContext) { pub fn pop(name: &SharedString, cx: &mut AppContext) {
cx.default_global::<Self>() cx.default_global::<Self>().0.get_mut(name).unwrap().pop();
.0
.get_mut(name)
.unwrap()
.pop();
} }
} }
@ -1035,16 +1033,21 @@ impl Deref for MouseExitEvent {
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct DroppedFiles(pub(crate) SmallVec<[PathBuf; 2]>);
#[derive(Debug, Clone)]
pub enum FileDropEvent { pub enum FileDropEvent {
#[default] Entered {
End, position: Point<Pixels>,
files: DroppedFiles,
},
Pending { Pending {
position: Point<Pixels>, position: Point<Pixels>,
}, },
Submit { Submit {
position: Point<Pixels>, position: Point<Pixels>,
paths: Vec<PathBuf>,
}, },
Exited,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -1054,7 +1057,7 @@ pub enum InputEvent {
ModifiersChanged(ModifiersChangedEvent), ModifiersChanged(ModifiersChangedEvent),
MouseDown(MouseDownEvent), MouseDown(MouseDownEvent),
MouseUp(MouseUpEvent), MouseUp(MouseUpEvent),
MouseMoved(MouseMoveEvent), MouseMove(MouseMoveEvent),
MouseExited(MouseExitEvent), MouseExited(MouseExitEvent),
ScrollWheel(ScrollWheelEvent), ScrollWheel(ScrollWheelEvent),
FileDrop(FileDropEvent), FileDrop(FileDropEvent),
@ -1068,12 +1071,14 @@ impl InputEvent {
InputEvent::ModifiersChanged { .. } => None, InputEvent::ModifiersChanged { .. } => None,
InputEvent::MouseDown(event) => Some(event.position), InputEvent::MouseDown(event) => Some(event.position),
InputEvent::MouseUp(event) => Some(event.position), InputEvent::MouseUp(event) => Some(event.position),
InputEvent::MouseMoved(event) => Some(event.position), InputEvent::MouseMove(event) => Some(event.position),
InputEvent::MouseExited(event) => Some(event.position), InputEvent::MouseExited(event) => Some(event.position),
InputEvent::ScrollWheel(event) => Some(event.position), InputEvent::ScrollWheel(event) => Some(event.position),
InputEvent::FileDrop(FileDropEvent::End) => None, InputEvent::FileDrop(FileDropEvent::Exited) => None,
InputEvent::FileDrop( InputEvent::FileDrop(
FileDropEvent::Pending { position } | FileDropEvent::Submit { position, .. }, FileDropEvent::Entered { position, .. }
| FileDropEvent::Pending { position, .. }
| FileDropEvent::Submit { position, .. },
) => Some(*position), ) => Some(*position),
} }
} }
@ -1085,7 +1090,7 @@ impl InputEvent {
InputEvent::ModifiersChanged { .. } => None, InputEvent::ModifiersChanged { .. } => None,
InputEvent::MouseDown(event) => Some(event), InputEvent::MouseDown(event) => Some(event),
InputEvent::MouseUp(event) => Some(event), InputEvent::MouseUp(event) => Some(event),
InputEvent::MouseMoved(event) => Some(event), InputEvent::MouseMove(event) => Some(event),
InputEvent::MouseExited(event) => Some(event), InputEvent::MouseExited(event) => Some(event),
InputEvent::ScrollWheel(event) => Some(event), InputEvent::ScrollWheel(event) => Some(event),
InputEvent::FileDrop(event) => Some(event), InputEvent::FileDrop(event) => Some(event),
@ -1099,7 +1104,7 @@ impl InputEvent {
InputEvent::ModifiersChanged(event) => Some(event), InputEvent::ModifiersChanged(event) => Some(event),
InputEvent::MouseDown(_) => None, InputEvent::MouseDown(_) => None,
InputEvent::MouseUp(_) => None, InputEvent::MouseUp(_) => None,
InputEvent::MouseMoved(_) => None, InputEvent::MouseMove(_) => None,
InputEvent::MouseExited(_) => None, InputEvent::MouseExited(_) => None,
InputEvent::ScrollWheel(_) => None, InputEvent::ScrollWheel(_) => None,
InputEvent::FileDrop(_) => None, InputEvent::FileDrop(_) => None,

View file

@ -202,7 +202,7 @@ impl InputEvent {
}; };
window_height.map(|window_height| { window_height.map(|window_height| {
Self::MouseMoved(MouseMoveEvent { Self::MouseMove(MouseMoveEvent {
pressed_button: Some(pressed_button), pressed_button: Some(pressed_button),
position: point( position: point(
px(native_event.locationInWindow().x as f32), px(native_event.locationInWindow().x as f32),
@ -213,7 +213,7 @@ impl InputEvent {
}) })
} }
NSEventType::NSMouseMoved => window_height.map(|window_height| { NSEventType::NSMouseMoved => window_height.map(|window_height| {
Self::MouseMoved(MouseMoveEvent { Self::MouseMove(MouseMoveEvent {
position: point( position: point(
px(native_event.locationInWindow().x as f32), px(native_event.locationInWindow().x as f32),
window_height - px(native_event.locationInWindow().y as f32), window_height - px(native_event.locationInWindow().y as f32),

View file

@ -1,10 +1,10 @@
use super::{display_bounds_from_native, ns_string, MacDisplay, MetalRenderer, NSRange}; use super::{display_bounds_from_native, ns_string, MacDisplay, MetalRenderer, NSRange};
use crate::{ use crate::{
display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, Executor, FileDropEvent, display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, DroppedFiles, Executor,
GlobalPixels, InputEvent, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, FileDropEvent, GlobalPixels, InputEvent, KeyDownEvent, Keystroke, Modifiers,
MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, Scene, Size, Timer, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, Scene, Size,
WindowAppearance, WindowBounds, WindowKind, WindowOptions, WindowPromptLevel, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions, WindowPromptLevel,
}; };
use block::ConcreteBlock; use block::ConcreteBlock;
use cocoa::{ use cocoa::{
@ -31,6 +31,7 @@ use objc::{
sel, sel_impl, sel, sel_impl,
}; };
use parking_lot::Mutex; use parking_lot::Mutex;
use smallvec::SmallVec;
use std::{ use std::{
any::Any, any::Any,
cell::{Cell, RefCell}, cell::{Cell, RefCell},
@ -1177,7 +1178,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
}; };
match &event { match &event {
InputEvent::MouseMoved( InputEvent::MouseMove(
event @ MouseMoveEvent { event @ MouseMoveEvent {
pressed_button: Some(_), pressed_button: Some(_),
.. ..
@ -1194,7 +1195,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
.detach(); .detach();
} }
InputEvent::MouseMoved(_) if !(is_active || lock.kind == WindowKind::PopUp) => return, InputEvent::MouseMove(_) if !(is_active || lock.kind == WindowKind::PopUp) => return,
InputEvent::MouseUp(MouseUpEvent { InputEvent::MouseUp(MouseUpEvent {
button: MouseButton::Left, button: MouseButton::Left,
@ -1633,11 +1634,14 @@ extern "C" fn accepts_first_mouse(this: &Object, _: Sel, _: id) -> BOOL {
extern "C" fn dragging_entered(this: &Object, _: Sel, dragging_info: id) -> NSDragOperation { extern "C" fn dragging_entered(this: &Object, _: Sel, dragging_info: id) -> NSDragOperation {
let window_state = unsafe { get_window_state(this) }; let window_state = unsafe { get_window_state(this) };
if send_new_event(&window_state, {
let position = drag_event_position(&window_state, dragging_info); let position = drag_event_position(&window_state, dragging_info);
if send_new_event( let paths = external_paths_from_event(dragging_info);
&window_state, InputEvent::FileDrop(FileDropEvent::Entered {
InputEvent::FileDrop(FileDropEvent::Pending { position }), position,
) { files: paths,
})
}) {
NSDragOperationCopy NSDragOperationCopy
} else { } else {
NSDragOperationNone NSDragOperationNone
@ -1659,26 +1663,17 @@ extern "C" fn dragging_updated(this: &Object, _: Sel, dragging_info: id) -> NSDr
extern "C" fn dragging_exited(this: &Object, _: Sel, _: id) { extern "C" fn dragging_exited(this: &Object, _: Sel, _: id) {
let window_state = unsafe { get_window_state(this) }; let window_state = unsafe { get_window_state(this) };
send_new_event(&window_state, InputEvent::FileDrop(FileDropEvent::End)); send_new_event(&window_state, InputEvent::FileDrop(FileDropEvent::Exited));
} }
extern "C" fn perform_drag_operation(this: &Object, _: Sel, dragging_info: id) -> BOOL { extern "C" fn perform_drag_operation(this: &Object, _: Sel, dragging_info: id) -> BOOL {
let mut paths = Vec::new(); let files = external_paths_from_event(dragging_info);
let pb: id = unsafe { msg_send![dragging_info, draggingPasteboard] };
let filenames = unsafe { NSPasteboard::propertyListForType(pb, NSFilenamesPboardType) };
for file in unsafe { filenames.iter() } {
let path = unsafe {
let f = NSString::UTF8String(file);
CStr::from_ptr(f).to_string_lossy().into_owned()
};
paths.push(PathBuf::from(path))
}
let window_state = unsafe { get_window_state(this) }; let window_state = unsafe { get_window_state(this) };
let position = drag_event_position(&window_state, dragging_info); let position = drag_event_position(&window_state, dragging_info);
if send_new_event( if send_new_event(
&window_state, &window_state,
InputEvent::FileDrop(FileDropEvent::Submit { position, paths }), InputEvent::FileDrop(FileDropEvent::Submit { position }),
) { ) {
YES YES
} else { } else {
@ -1686,9 +1681,23 @@ extern "C" fn perform_drag_operation(this: &Object, _: Sel, dragging_info: id) -
} }
} }
fn external_paths_from_event(dragging_info: *mut Object) -> DroppedFiles {
let mut paths = SmallVec::new();
let pasteboard: id = unsafe { msg_send![dragging_info, draggingPasteboard] };
let filenames = unsafe { NSPasteboard::propertyListForType(pasteboard, NSFilenamesPboardType) };
for file in unsafe { filenames.iter() } {
let path = unsafe {
let f = NSString::UTF8String(file);
CStr::from_ptr(f).to_string_lossy().into_owned()
};
paths.push(PathBuf::from(path))
}
DroppedFiles(paths)
}
extern "C" fn conclude_drag_operation(this: &Object, _: Sel, _: id) { extern "C" fn conclude_drag_operation(this: &Object, _: Sel, _: id) {
let window_state = unsafe { get_window_state(this) }; let window_state = unsafe { get_window_state(this) };
send_new_event(&window_state, InputEvent::FileDrop(FileDropEvent::End)); send_new_event(&window_state, InputEvent::FileDrop(FileDropEvent::Exited));
} }
async fn synthetic_drag( async fn synthetic_drag(
@ -1703,7 +1712,7 @@ async fn synthetic_drag(
if lock.synthetic_drag_counter == drag_id { if lock.synthetic_drag_counter == drag_id {
if let Some(mut callback) = lock.event_callback.take() { if let Some(mut callback) = lock.event_callback.take() {
drop(lock); drop(lock);
callback(InputEvent::MouseMoved(event.clone())); callback(InputEvent::MouseMove(event.clone()));
window_state.lock().event_callback = Some(callback); window_state.lock().event_callback = Some(callback);
} }
} else { } else {

View file

@ -1,13 +1,14 @@
use crate::{ use crate::{
px, size, Action, AnyBox, AnyView, AppContext, AsyncWindowContext, AvailableSpace, Bounds, px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
BoxShadow, Context, Corners, DevicePixels, DispatchContext, DisplayId, Edges, Effect, Element, Bounds, BoxShadow, Context, Corners, DevicePixels, DispatchContext, DisplayId, DroppedFiles,
EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, Handle, Edges, Effect, Element, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId,
Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId, GlobalElementId, GlyphId, Handle, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch,
MainThread, MainThreadOnly, MonochromeSprite, MouseMoveEvent, MouseUpEvent, Path, Pixels, KeyMatcher, Keystroke, LayoutId, MainThread, MainThreadOnly, Modifiers, MonochromeSprite,
PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Quad, Reference, RenderGlyphParams, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas,
RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, PlatformWindow, Point, PolychromeSprite, Quad, Reference, RenderGlyphParams, RenderImageParams,
Style, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, WeakHandle, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, Subscription,
WindowOptions, SUBPIXEL_VARIANTS, TaffyLayoutEngine, Task, Underline, UnderlineStyle, WeakHandle, WindowOptions,
SUBPIXEL_VARIANTS,
}; };
use anyhow::Result; use anyhow::Result;
use collections::HashMap; use collections::HashMap;
@ -816,7 +817,9 @@ impl<'a, 'w> WindowContext<'a, 'w> {
cx.with_element_offset(Some(offset), |cx| { cx.with_element_offset(Some(offset), |cx| {
let available_space = let available_space =
size(AvailableSpace::MinContent, AvailableSpace::MinContent); size(AvailableSpace::MinContent, AvailableSpace::MinContent);
draw_any_view(&mut active_drag.drag_handle_view, available_space, cx); if let Some(drag_handle_view) = &mut active_drag.drag_handle_view {
draw_any_view(drag_handle_view, available_space, cx);
}
cx.active_drag = Some(active_drag); cx.active_drag = Some(active_drag);
}); });
}); });
@ -889,27 +892,48 @@ impl<'a, 'w> WindowContext<'a, 'w> {
} }
fn dispatch_event(&mut self, event: InputEvent) -> bool { fn dispatch_event(&mut self, event: InputEvent) -> bool {
let event = match event {
InputEvent::MouseMove(mouse_move) => {
self.window.mouse_position = mouse_move.position;
InputEvent::MouseMove(mouse_move)
}
InputEvent::FileDrop(file_drop) => match file_drop {
FileDropEvent::Entered { position, files } => {
self.active_drag.get_or_insert_with(|| AnyDrag {
drag_handle_view: None,
cursor_offset: position,
state: Box::new(files),
state_type: TypeId::of::<DroppedFiles>(),
});
InputEvent::MouseDown(MouseDownEvent {
position,
button: MouseButton::Left,
click_count: 1,
modifiers: Modifiers::default(),
})
}
FileDropEvent::Pending { position } => InputEvent::MouseMove(MouseMoveEvent {
position,
pressed_button: Some(MouseButton::Left),
modifiers: Modifiers::default(),
}),
FileDropEvent::Submit { position } => InputEvent::MouseUp(MouseUpEvent {
button: MouseButton::Left,
position,
modifiers: Modifiers::default(),
click_count: 1,
}),
FileDropEvent::Exited => InputEvent::MouseUp(MouseUpEvent {
button: MouseButton::Left,
position: Point::default(),
modifiers: Modifiers::default(),
click_count: 1,
}),
},
_ => event,
};
if let Some(any_mouse_event) = event.mouse_event() { if let Some(any_mouse_event) = event.mouse_event() {
if let Some(MouseMoveEvent { position, .. }) = any_mouse_event.downcast_ref() {
self.window.mouse_position = *position;
}
match any_mouse_event.downcast_ref() {
Some(FileDropEvent::Pending { position }) => {
dbg!("FileDropEvent::Pending", position);
return true;
}
Some(FileDropEvent::Submit { position, paths }) => {
dbg!("FileDropEvent::Submit", position, paths);
return true;
}
Some(FileDropEvent::End) => {
self.active_drag = None;
return true;
}
_ => {}
}
// Handlers may set this to false by calling `stop_propagation` // Handlers may set this to false by calling `stop_propagation`
self.app.propagate_event = true; self.app.propagate_event = true;
self.window.default_prevented = false; self.window.default_prevented = false;

View file

@ -305,7 +305,18 @@ fn box_prefixes() -> Vec<(&'static str, bool, Vec<TokenStream2>, &'static str)>
vec![quote! { padding.right }], vec![quote! { padding.right }],
"Sets the right padding of the element. [Docs](https://tailwindcss.com/docs/padding#add-padding-to-a-single-side)" "Sets the right padding of the element. [Docs](https://tailwindcss.com/docs/padding#add-padding-to-a-single-side)"
), ),
("top", true, vec![quote! { inset.top }], "Sets the top value of a positioned element. [Docs](https://tailwindcss.com/docs/top-right-bottom-left)",), (
"inset",
true,
vec![quote! { inset.top }, quote! { inset.right }, quote! { inset.bottom }, quote! { inset.left }],
"Sets the top, right, bottom, and left values of a positioned element. [Docs](https://tailwindcss.com/docs/top-right-bottom-left)",
),
(
"top",
true,
vec![quote! { inset.top }],
"Sets the top value of a positioned element. [Docs](https://tailwindcss.com/docs/top-right-bottom-left)",
),
( (
"bottom", "bottom",
true, true,

View file

@ -159,8 +159,6 @@ pub struct ThemeColor {
impl std::fmt::Debug for ThemeColor { impl std::fmt::Debug for ThemeColor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
dbg!("ThemeColor debug");
f.debug_struct("ThemeColor") f.debug_struct("ThemeColor")
.field("transparent", &self.transparent.to_rgb().to_hex()) .field("transparent", &self.transparent.to_rgb().to_hex())
.field( .field(

View file

@ -1,6 +1,6 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use gpui2::{hsla, AnyElement, ElementId, Hsla, Length, Size}; use gpui2::{hsla, red, AnyElement, DroppedFiles, ElementId, Hsla, Length, Size};
use smallvec::SmallVec; use smallvec::SmallVec;
use crate::prelude::*; use crate::prelude::*;
@ -50,8 +50,19 @@ impl<S: 'static + Send + Sync> Pane<S> {
.bg(self.fill) .bg(self.fill)
.w(self.size.width) .w(self.size.width)
.h(self.size.height) .h(self.size.height)
.overflow_y_scroll() .relative()
.children(self.children.drain(..)) .children(cx.stack(0, |_| self.children.drain(..)))
.child(cx.stack(1, |_| {
// TODO kb! Figure out why we can't we see the red background when we drag a file over this div.
div()
.id("drag-target")
.drag_over::<DroppedFiles>(|d| d.bg(red()))
.on_drop(|_, files: DroppedFiles, _| {
dbg!("dropped files!", files);
})
.absolute()
.inset_0()
}))
} }
} }

View file

@ -179,8 +179,6 @@ impl Workspace {
let color = ThemeColor::new(cx); let color = ThemeColor::new(cx);
dbg!(color);
// HACK: This should happen inside of `debug_toggle_user_settings`, but // HACK: This should happen inside of `debug_toggle_user_settings`, but
// we don't have `cx.global::<FakeSettings>()` in event handlers at the moment. // we don't have `cx.global::<FakeSettings>()` in event handlers at the moment.
// Need to talk with Nathan/Antonio about this. // Need to talk with Nathan/Antonio about this.