1#![cfg(feature = "drag_drop")]
2
3use std::mem;
23
24use parking_lot::Mutex;
25use zng_app::{
26 event::{event, event_args},
27 hn, static_id,
28 view_process::raw_events::{
29 RAW_APP_DRAG_ENDED_EVENT, RAW_DRAG_CANCELLED_EVENT, RAW_DRAG_DROPPED_EVENT, RAW_DRAG_HOVERED_EVENT, RAW_DRAG_MOVED_EVENT,
30 },
31 widget::{
32 WidgetId,
33 info::{HitTestInfo, InteractionPath, WidgetInfo, WidgetInfoBuilder},
34 },
35 window::WindowId,
36};
37use zng_app_context::app_local;
38use zng_ext_window::{NestedWindowWidgetInfoExt as _, WINDOWS, WINDOWS_DRAG_DROP};
39use zng_handle::{Handle, HandleOwner, WeakHandle};
40use zng_layout::unit::{DipPoint, DipToPx as _, PxToDip as _};
41use zng_state_map::StateId;
42use zng_task::channel::IpcBytes;
43use zng_txt::{Txt, formatx};
44use zng_var::{ArcEq, Var, var};
45use zng_view_api::{DragDropId, mouse::ButtonState, touch::TouchPhase};
46
47use crate::{mouse::MOUSE_INPUT_EVENT, touch::TOUCH_INPUT_EVENT};
48
49pub use zng_view_api::drag_drop::{DragDropData, DragDropEffect};
50
51#[allow(non_camel_case_types)]
53pub struct DRAG_DROP;
54impl DRAG_DROP {
55 pub fn dragging_data(&self) -> Var<Vec<DragDropData>> {
57 DRAG_DROP_SV.read().data.read_only()
58 }
59
60 pub fn drag(&self, data: DragDropData, allowed_effects: DragDropEffect) -> DragHandle {
71 let mut sv = DRAG_DROP_SV.write();
72 if let Some(d) = &mut sv.app_drag {
73 if allowed_effects.is_empty() {
74 tracing::error!("cannot drag, no `allowed_effects`");
75 return DragHandle::dummy();
76 }
77
78 if d.allowed.is_empty() {
79 d.allowed = allowed_effects;
80 } else {
81 if !d.allowed.contains(allowed_effects) {
82 tracing::error!("cannot drag, other data already set with incompatible `allowed_effects`");
83 return DragHandle::dummy();
84 }
85 d.allowed |= allowed_effects
86 }
87
88 d.data.push(data);
89 let (owner, handle) = DragHandle::new();
90 d.handles.push(owner);
91 return handle;
92 }
93 tracing::error!("cannot drag, not in `DRAG_START_EVENT` interval");
94 DragHandle::dummy()
95 }
96}
97
98app_local! {
99 static DRAG_DROP_SV: DragDropService = {
100 hooks();
101 DragDropService {
102 data: var(vec![]),
103 system_dragging: vec![],
104 app_drag: None,
105 app_dragging: vec![],
106
107 pos: Default::default(),
108 pos_window: None,
109 hits: None,
110 hovered: Default::default(),
111 }
112 };
113}
114struct DragDropService {
115 data: Var<Vec<DragDropData>>,
116
117 system_dragging: Vec<DragDropData>,
118
119 app_drag: Option<AppDragging>,
120 app_dragging: Vec<AppDragging>,
121
122 pos: DipPoint,
124 pos_window: Option<WindowId>,
126 hits: Option<HitTestInfo>,
128 hovered: Option<InteractionPath>,
129}
130struct AppDragging {
131 target: InteractionPath,
132 data: Vec<DragDropData>,
133 handles: Vec<HandleOwner<()>>,
134 allowed: DragDropEffect,
135 view_id: DragDropId,
136}
137
138fn hooks() {
139 RAW_DRAG_DROPPED_EVENT
140 .hook(|args| {
141 let mut s = DRAG_DROP_SV.write();
143 let s = &mut *s;
144 let len = s.system_dragging.len();
145 for data in &args.data {
146 s.system_dragging.retain(|d| d != data);
147 }
148 if s.system_dragging.len() != len {
149 s.data.set(s.system_dragging.clone());
150 }
151
152 if s.pos_window == Some(args.window_id)
153 && let Some(hovered) = &s.hovered
154 {
155 let hits = s.hits.take().unwrap_or_else(|| HitTestInfo::no_hits(args.window_id));
156 DRAG_HOVERED_EVENT.notify(DragHoveredArgs::now(Some(hovered.clone()), None, s.pos, hits.clone()));
157 DROP_EVENT.notify(DropArgs::now(
158 hovered.clone(),
159 args.data.clone(),
160 args.allowed,
161 s.pos,
162 hits,
163 args.drop_id,
164 ArcEq::new(Mutex::new(DragDropEffect::empty())),
165 ));
166 }
167
168 true
169 })
170 .perm();
171
172 RAW_DRAG_HOVERED_EVENT
173 .hook(|args| {
174 let mut s = DRAG_DROP_SV.write();
175 s.system_dragging.extend(args.data.iter().cloned());
176 s.data.set(s.system_dragging.clone());
177 true
178 })
179 .perm();
180
181 RAW_DRAG_MOVED_EVENT
182 .hook(|args| {
183 let mut s = DRAG_DROP_SV.write();
184 let moved = s.pos != args.position || s.pos_window != Some(args.window_id);
185 if moved {
186 s.pos = args.position;
187 s.pos_window = Some(args.window_id);
188
189 let mut position = args.position;
190
191 let mut frame_info = match WINDOWS.widget_tree(args.window_id) {
193 Some(f) => f,
194 None => {
195 if let Some(hovered) = s.hovered.take() {
197 DRAG_HOVERED_EVENT.notify(DragHoveredArgs::now(
198 Some(hovered),
199 None,
200 position,
201 HitTestInfo::no_hits(args.window_id),
202 ));
203 s.pos_window = None;
204 }
205 return true;
206 }
207 };
208
209 let mut pos_hits = frame_info.root().hit_test(position.to_px(frame_info.scale_factor()));
210
211 let target = if let Some(t) = pos_hits.target() {
212 if let Some(w) = frame_info.get(t.widget_id) {
213 if let Some(f) = w.nested_window_tree() {
214 frame_info = f;
216 let factor = frame_info.scale_factor();
217 let pos = position.to_px(factor);
218 let pos = w.inner_transform().inverse().and_then(|t| t.transform_point(pos)).unwrap_or(pos);
219 pos_hits = frame_info.root().hit_test(pos);
220 position = pos.to_dip(factor);
221 pos_hits
222 .target()
223 .and_then(|h| frame_info.get(h.widget_id))
224 .map(|w| w.interaction_path())
225 .unwrap_or_else(|| frame_info.root().interaction_path())
226 } else {
227 w.interaction_path()
228 }
229 } else {
230 tracing::error!("hits target `{}` not found", t.widget_id);
231 frame_info.root().interaction_path()
232 }
233 } else {
234 frame_info.root().interaction_path()
235 }
236 .unblocked();
237
238 s.hits = Some(pos_hits.clone());
239
240 let hovered_args = if s.hovered != target {
242 let prev_target = mem::replace(&mut s.hovered, target.clone());
243 let args = DragHoveredArgs::now(prev_target, target.clone(), position, pos_hits.clone());
244 Some(args)
245 } else {
246 None
247 };
248
249 if let Some(target) = target {
251 let args = DragMoveArgs::now(frame_info.window_id(), args.coalesced_pos.clone(), position, pos_hits, target);
252 DRAG_MOVE_EVENT.notify(args);
253 }
254
255 if let Some(args) = hovered_args {
256 DRAG_HOVERED_EVENT.notify(args);
257 }
258 }
259 true
260 })
261 .perm();
262
263 RAW_DRAG_CANCELLED_EVENT
265 .hook(|args| {
266 let mut s = DRAG_DROP_SV.write();
267 let changed = !s.system_dragging.is_empty();
268 s.system_dragging.clear();
269 if changed {
270 s.data.set(s.system_dragging.clone());
271 }
272
273 if let Some(prev) = s.hovered.take() {
274 s.pos_window = None;
275 DRAG_HOVERED_EVENT.notify(DragHoveredArgs::now(
276 Some(prev),
277 None,
278 s.pos,
279 s.hits.take().unwrap_or_else(|| HitTestInfo::no_hits(args.window_id)),
280 ));
281 }
282
283 true
284 })
285 .perm();
286
287 RAW_APP_DRAG_ENDED_EVENT
288 .hook(|args| {
289 let mut s = DRAG_DROP_SV.write();
290 s.app_dragging.retain(|d| {
291 if d.view_id != args.id {
292 return true;
293 }
294
295 if !args.applied.is_empty() && !d.allowed.contains(args.applied) {
296 tracing::error!(
297 "drop target applied disallowed effect, allowed={:?}, applied={:?}",
298 d.allowed,
299 args.applied
300 );
301 }
302
303 DRAG_END_EVENT.notify(DragEndArgs::now(d.target.clone(), args.applied));
304
305 false
306 });
307 true
308 })
309 .perm();
310
311 MOUSE_INPUT_EVENT
312 .on_event(
313 false,
314 hn!(|args| {
315 if matches!(args.state, ButtonState::Pressed)
316 && let Some(wgt) = WINDOWS.widget_info(args.target.widget_id())
317 && let Some(wgt) = wgt.self_and_ancestors().find(|w| w.is_draggable())
318 {
319 args.propagation.stop();
321 let target = wgt.interaction_path();
322 let args = DragStartArgs::now(target.clone());
323 DRAG_START_EVENT.notify(args);
324 DRAG_DROP_SV.write().app_drag = Some(AppDragging {
325 target,
326 data: vec![],
327 handles: vec![],
328 allowed: DragDropEffect::empty(),
329 view_id: DragDropId(0),
330 }); }
332 }),
333 )
334 .perm();
335
336 TOUCH_INPUT_EVENT
337 .on_event(
338 false,
339 hn!(|args| {
340 if matches!(args.phase, TouchPhase::Start)
341 && let Some(wgt) = WINDOWS.widget_info(args.target.widget_id())
342 && let Some(wgt) = wgt.self_and_ancestors().find(|w| w.is_draggable())
343 {
344 args.propagation.stop();
346 let target = wgt.interaction_path();
347 let args = DragStartArgs::now(target.clone());
348 DRAG_START_EVENT.notify(args);
349 DRAG_DROP_SV.write().app_drag = Some(AppDragging {
350 target,
351 data: vec![],
352 handles: vec![],
353 allowed: DragDropEffect::empty(),
354 view_id: DragDropId(0),
355 }); }
357 }),
358 )
359 .perm();
360
361 DRAG_START_EVENT
362 .hook(|args| {
363 let mut s = DRAG_DROP_SV.write();
365 let mut data = s.app_drag.take();
366 let mut cancel = args.propagation.is_stopped();
367 if !cancel {
368 if let Some(d) = &mut data {
369 if d.data.is_empty() {
370 d.data.push(encode_widget_id(args.target.widget_id()));
371 d.allowed = DragDropEffect::all();
372 }
373 match WINDOWS_DRAG_DROP.start_drag_drop(d.target.window_id(), mem::take(&mut d.data), d.allowed) {
374 Ok(id) => {
375 d.view_id = id;
376 s.app_dragging.push(data.take().unwrap());
377 }
378 Err(e) => {
379 tracing::error!("cannot start drag&drop, {e}");
380 cancel = true;
381 }
382 }
383 } else {
384 tracing::warn!("external notification of DRAG_START_EVENT ignored")
385 }
386 }
387 if cancel && let Some(d) = data {
388 DRAG_END_EVENT.notify(DragEndArgs::now(d.target, DragDropEffect::empty()));
389 }
390
391 true
392 })
393 .perm();
394
395 DROP_EVENT
396 .hook(|args| {
397 WINDOWS_DRAG_DROP.drag_dropped(args.target.window_id(), args.drop_id, *args.applied.lock());
398 true
399 })
400 .perm();
401}
402
403#[derive(Clone, PartialEq, Eq, Hash, Debug)]
407#[repr(transparent)]
408#[must_use = "dropping the handle cancels the drag operation"]
409pub struct DragHandle(Handle<()>);
410impl DragHandle {
411 fn new() -> (HandleOwner<()>, Self) {
412 let (owner, handle) = Handle::new(());
413 (owner, Self(handle))
414 }
415
416 pub fn dummy() -> Self {
418 Self(Handle::dummy(()))
419 }
420
421 pub fn perm(self) {
425 self.0.perm();
426 }
427
428 pub fn is_permanent(&self) -> bool {
432 self.0.is_permanent()
433 }
434
435 pub fn cancel(self) {
437 self.0.force_drop()
438 }
439
440 pub fn is_canceled(&self) -> bool {
442 self.0.is_dropped()
443 }
444
445 pub fn downgrade(&self) -> WeakDragHandle {
447 WeakDragHandle(self.0.downgrade())
448 }
449}
450#[derive(Clone, PartialEq, Eq, Hash, Default, Debug)]
452pub struct WeakDragHandle(WeakHandle<()>);
453impl WeakDragHandle {
454 pub fn new() -> Self {
456 Self(WeakHandle::new())
457 }
458
459 pub fn upgrade(&self) -> Option<DragHandle> {
461 self.0.upgrade().map(DragHandle)
462 }
463}
464
465pub trait WidgetInfoDragDropExt {
467 fn is_draggable(&self) -> bool;
469}
470impl WidgetInfoDragDropExt for WidgetInfo {
471 fn is_draggable(&self) -> bool {
472 self.meta().flagged(*IS_DRAGGABLE_ID)
473 }
474}
475
476pub trait WidgetInfoBuilderDragDropExt {
478 fn draggable(&mut self);
480}
481impl WidgetInfoBuilderDragDropExt for WidgetInfoBuilder {
482 fn draggable(&mut self) {
483 self.flag_meta(*IS_DRAGGABLE_ID);
484 }
485}
486
487static_id! {
488 static ref IS_DRAGGABLE_ID: StateId<()>;
489}
490
491event_args! {
492 pub struct DropArgs {
494 pub target: InteractionPath,
496 pub data: Vec<DragDropData>,
498 pub allowed: DragDropEffect,
500 pub position: DipPoint,
502 pub hits: HitTestInfo,
504
505 drop_id: DragDropId,
506 applied: ArcEq<Mutex<DragDropEffect>>,
507
508 ..
509
510 fn is_in_target(&self, id: WidgetId) -> bool {
514 self.target.contains(id)
515 }
516 }
517
518 pub struct DragHoveredArgs {
520 pub prev_target: Option<InteractionPath>,
522 pub target: Option<InteractionPath>,
524 pub position: DipPoint,
526 pub hits: HitTestInfo,
528
529 ..
530
531 fn is_in_target(&self, id: WidgetId) -> bool {
536 if let Some(p) = &self.prev_target
537 && p.contains(id)
538 {
539 return true;
540 }
541 if let Some(p) = &self.target
542 && p.contains(id)
543 {
544 return true;
545 }
546 false
547 }
548 }
549
550 pub struct DragMoveArgs {
552 pub window_id: WindowId,
554
555 pub coalesced_pos: Vec<DipPoint>,
559
560 pub position: DipPoint,
562
563 pub hits: HitTestInfo,
565
566 pub target: InteractionPath,
568
569 ..
570
571 fn is_in_target(&self, id: WidgetId) -> bool {
575 self.target.contains(id)
576 }
577 }
578
579 pub struct DragStartArgs {
581 pub target: InteractionPath,
583
584 ..
585
586 fn is_in_target(&self, id: WidgetId) -> bool {
590 self.target.contains(id)
591 }
592 }
593
594 pub struct DragEndArgs {
596 pub target: InteractionPath,
598
599 pub applied: DragDropEffect,
603
604 ..
605
606 fn is_in_target(&self, id: WidgetId) -> bool {
610 self.target.contains(id)
611 }
612
613 fn validate(&self) -> Result<(), Txt> {
615 if self.applied.is_empty() && self.applied.len() > 1 {
616 return Err("only one or none `DragDropEffect` can be applied".into());
617 }
618 Ok(())
619 }
620 }
621}
622event! {
623 pub static DROP_EVENT: DropArgs {
625 let _ = DRAG_DROP_SV.read();
626 };
627 pub static DRAG_HOVERED_EVENT: DragHoveredArgs {
629 let _ = DRAG_DROP_SV.read();
630 };
631 pub static DRAG_MOVE_EVENT: DragMoveArgs {
633 let _ = DRAG_DROP_SV.read();
634 };
635 pub static DRAG_START_EVENT: DragStartArgs {
642 let _ = DRAG_DROP_SV.read();
643 };
644
645 pub static DRAG_END_EVENT: DragEndArgs {
647 let _ = DRAG_DROP_SV.read();
648 };
649}
650
651impl DropArgs {
652 pub fn applied(&self, effect: DragDropEffect) {
662 assert!(effect.len() > 1, "can only apply one effect");
663 assert!(self.allowed.contains(effect), "source does not allow this effect");
664
665 let mut e = self.applied.lock();
666 if !self.propagation.is_stopped() {
667 self.propagation.stop();
668 *e = effect;
669 } else {
670 tracing::error!("drop already handled");
671 }
672 }
673}
674
675impl DragHoveredArgs {
676 pub fn data(&self) -> Var<Vec<DragDropData>> {
680 DRAG_DROP.dragging_data()
681 }
682
683 pub fn is_drag_enter(&self, wgt: WidgetId) -> bool {
685 !self.was_over(wgt) && self.is_over(wgt)
686 }
687
688 pub fn is_drag_leave(&self, wgt: WidgetId) -> bool {
690 self.was_over(wgt) && !self.is_over(wgt)
691 }
692
693 pub fn was_over(&self, wgt: WidgetId) -> bool {
697 if let Some(t) = &self.prev_target {
698 return t.contains(wgt);
699 }
700 false
701 }
702
703 pub fn is_over(&self, wgt: WidgetId) -> bool {
707 if let Some(t) = &self.target {
708 return t.contains(wgt);
709 }
710 false
711 }
712
713 pub fn was_enabled(&self, widget_id: WidgetId) -> bool {
717 match &self.prev_target {
718 Some(t) => t.contains_enabled(widget_id),
719 None => false,
720 }
721 }
722
723 pub fn was_disabled(&self, widget_id: WidgetId) -> bool {
727 match &self.prev_target {
728 Some(t) => t.contains_disabled(widget_id),
729 None => false,
730 }
731 }
732
733 pub fn is_enabled(&self, widget_id: WidgetId) -> bool {
737 match &self.target {
738 Some(t) => t.contains_enabled(widget_id),
739 None => false,
740 }
741 }
742
743 pub fn is_disabled(&self, widget_id: WidgetId) -> bool {
747 match &self.target {
748 Some(t) => t.contains_disabled(widget_id),
749 None => false,
750 }
751 }
752
753 pub fn is_drag_enter_enabled(&self, wgt: WidgetId) -> bool {
755 (!self.was_over(wgt) || self.was_disabled(wgt)) && self.is_over(wgt) && self.is_enabled(wgt)
756 }
757
758 pub fn is_drag_leave_enabled(&self, wgt: WidgetId) -> bool {
760 self.was_over(wgt) && self.was_enabled(wgt) && (!self.is_over(wgt) || self.is_disabled(wgt))
761 }
762
763 pub fn is_drag_enter_disabled(&self, wgt: WidgetId) -> bool {
765 (!self.was_over(wgt) || self.was_enabled(wgt)) && self.is_over(wgt) && self.is_disabled(wgt)
766 }
767
768 pub fn is_drag_leave_disabled(&self, wgt: WidgetId) -> bool {
770 self.was_over(wgt) && self.was_disabled(wgt) && (!self.is_over(wgt) || self.is_enabled(wgt))
771 }
772}
773
774impl DragEndArgs {
775 pub fn was_dropped(&self) -> bool {
777 !self.applied.is_empty()
778 }
779
780 pub fn was_canceled(&self) -> bool {
782 self.applied.is_empty()
783 }
784}
785
786pub fn encode_widget_id(id: WidgetId) -> DragDropData {
788 DragDropData::Extension {
789 data_type: formatx!("zng/{}", APP_INSTANCE_GUID.read().simple()),
790 data: IpcBytes::from_slice_blocking(&id.get().to_le_bytes()).unwrap(),
791 }
792}
793
794pub fn decode_widget_id(data: &DragDropData) -> Option<WidgetId> {
798 if let DragDropData::Extension { data_type, data } = data
799 && data.len() == 8
800 && let Some(guid) = data_type.strip_prefix("zng/")
801 && guid == APP_INSTANCE_GUID.read().simple().to_string()
802 {
803 let mut id = [0u8; 8];
804 id.copy_from_slice(data);
805 return Some(WidgetId::from_raw(u64::from_le_bytes(id)));
806 }
807 None
808}
809
810app_local! {
811 static APP_INSTANCE_GUID: uuid::Uuid = uuid::Uuid::new_v4();
812}