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 drag_handle_view: AnyView,
pub drag_handle_view: Option<AnyView>,
pub cursor_offset: Point<Pixels>,
pub state: AnyBox,
pub state_type: TypeId,

View file

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

View file

@ -202,7 +202,7 @@ impl InputEvent {
};
window_height.map(|window_height| {
Self::MouseMoved(MouseMoveEvent {
Self::MouseMove(MouseMoveEvent {
pressed_button: Some(pressed_button),
position: point(
px(native_event.locationInWindow().x as f32),
@ -213,7 +213,7 @@ impl InputEvent {
})
}
NSEventType::NSMouseMoved => window_height.map(|window_height| {
Self::MouseMoved(MouseMoveEvent {
Self::MouseMove(MouseMoveEvent {
position: point(
px(native_event.locationInWindow().x 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 crate::{
display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, Executor, FileDropEvent,
GlobalPixels, InputEvent, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent,
MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas,
PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, Scene, Size, Timer,
WindowAppearance, WindowBounds, WindowKind, WindowOptions, WindowPromptLevel,
display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, DroppedFiles, Executor,
FileDropEvent, GlobalPixels, InputEvent, KeyDownEvent, Keystroke, Modifiers,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, Scene, Size,
Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions, WindowPromptLevel,
};
use block::ConcreteBlock;
use cocoa::{
@ -31,6 +31,7 @@ use objc::{
sel, sel_impl,
};
use parking_lot::Mutex;
use smallvec::SmallVec;
use std::{
any::Any,
cell::{Cell, RefCell},
@ -1177,7 +1178,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
};
match &event {
InputEvent::MouseMoved(
InputEvent::MouseMove(
event @ MouseMoveEvent {
pressed_button: Some(_),
..
@ -1194,7 +1195,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
.detach();
}
InputEvent::MouseMoved(_) if !(is_active || lock.kind == WindowKind::PopUp) => return,
InputEvent::MouseMove(_) if !(is_active || lock.kind == WindowKind::PopUp) => return,
InputEvent::MouseUp(MouseUpEvent {
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 {
let window_state = unsafe { get_window_state(this) };
let position = drag_event_position(&window_state, dragging_info);
if send_new_event(
&window_state,
InputEvent::FileDrop(FileDropEvent::Pending { position }),
) {
if send_new_event(&window_state, {
let position = drag_event_position(&window_state, dragging_info);
let paths = external_paths_from_event(dragging_info);
InputEvent::FileDrop(FileDropEvent::Entered {
position,
files: paths,
})
}) {
NSDragOperationCopy
} else {
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) {
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 {
let mut paths = Vec::new();
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 files = external_paths_from_event(dragging_info);
let window_state = unsafe { get_window_state(this) };
let position = drag_event_position(&window_state, dragging_info);
if send_new_event(
&window_state,
InputEvent::FileDrop(FileDropEvent::Submit { position, paths }),
InputEvent::FileDrop(FileDropEvent::Submit { position }),
) {
YES
} 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) {
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(
@ -1703,7 +1712,7 @@ async fn synthetic_drag(
if lock.synthetic_drag_counter == drag_id {
if let Some(mut callback) = lock.event_callback.take() {
drop(lock);
callback(InputEvent::MouseMoved(event.clone()));
callback(InputEvent::MouseMove(event.clone()));
window_state.lock().event_callback = Some(callback);
}
} else {

View file

@ -1,13 +1,14 @@
use crate::{
px, size, Action, AnyBox, AnyView, AppContext, AsyncWindowContext, AvailableSpace, Bounds,
BoxShadow, Context, Corners, DevicePixels, DispatchContext, DisplayId, Edges, Effect, Element,
EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, Handle,
Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId,
MainThread, MainThreadOnly, MonochromeSprite, MouseMoveEvent, MouseUpEvent, Path, Pixels,
PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Quad, Reference, RenderGlyphParams,
RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size,
Style, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, WeakHandle,
WindowOptions, SUBPIXEL_VARIANTS,
px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
Bounds, BoxShadow, Context, Corners, DevicePixels, DispatchContext, DisplayId, DroppedFiles,
Edges, Effect, Element, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId,
GlobalElementId, GlyphId, Handle, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch,
KeyMatcher, Keystroke, LayoutId, MainThread, MainThreadOnly, Modifiers, MonochromeSprite,
MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas,
PlatformWindow, Point, PolychromeSprite, Quad, Reference, RenderGlyphParams, RenderImageParams,
RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, Subscription,
TaffyLayoutEngine, Task, Underline, UnderlineStyle, WeakHandle, WindowOptions,
SUBPIXEL_VARIANTS,
};
use anyhow::Result;
use collections::HashMap;
@ -816,7 +817,9 @@ impl<'a, 'w> WindowContext<'a, 'w> {
cx.with_element_offset(Some(offset), |cx| {
let available_space =
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);
});
});
@ -889,27 +892,48 @@ impl<'a, 'w> WindowContext<'a, 'w> {
}
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(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`
self.app.propagate_event = true;
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 }],
"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",
true,

View file

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

View file

@ -1,6 +1,6 @@
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 crate::prelude::*;
@ -50,8 +50,19 @@ impl<S: 'static + Send + Sync> Pane<S> {
.bg(self.fill)
.w(self.size.width)
.h(self.size.height)
.overflow_y_scroll()
.children(self.children.drain(..))
.relative()
.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);
dbg!(color);
// HACK: This should happen inside of `debug_toggle_user_settings`, but
// we don't have `cx.global::<FakeSettings>()` in event handlers at the moment.
// Need to talk with Nathan/Antonio about this.