zng_ext_input/
drag_drop.rs

1#![cfg(feature = "drag_drop")]
2
3//! Drag & drop gesture events and service.
4
5use std::{mem, sync::Arc};
6
7use parking_lot::Mutex;
8use zng_app::{
9    AppExtension,
10    event::{AnyEventArgs, event, event_args},
11    static_id,
12    update::{EventUpdate, UPDATES},
13    view_process::raw_events::{
14        RAW_APP_DRAG_ENDED_EVENT, RAW_DRAG_CANCELLED_EVENT, RAW_DRAG_DROPPED_EVENT, RAW_DRAG_HOVERED_EVENT, RAW_DRAG_MOVED_EVENT,
15    },
16    widget::{
17        WIDGET, WidgetId,
18        info::{HitTestInfo, InteractionPath, WidgetInfo, WidgetInfoBuilder},
19    },
20    window::WindowId,
21};
22use zng_app_context::app_local;
23use zng_ext_window::{NestedWindowWidgetInfoExt as _, WINDOWS, WINDOWS_DRAG_DROP};
24use zng_handle::{Handle, HandleOwner, WeakHandle};
25use zng_layout::unit::{DipPoint, DipToPx as _, PxToDip as _};
26use zng_state_map::StateId;
27use zng_txt::{Txt, formatx};
28use zng_var::{ArcVar, ReadOnlyArcVar, Var, var};
29use zng_view_api::{DragDropId, mouse::ButtonState, touch::TouchPhase};
30
31use crate::{mouse::MOUSE_INPUT_EVENT, touch::TOUCH_INPUT_EVENT};
32
33pub use zng_view_api::drag_drop::{DragDropData, DragDropEffect};
34
35/// Application extension that provides drag&drop events and service.
36///
37/// # Events
38///
39/// Events this extension provides.
40///
41/// * [`DROP_EVENT`]
42/// * [`DRAG_HOVERED_EVENT`]
43/// * [`DRAG_MOVE_EVENT`]
44/// * [`DRAG_START_EVENT`]
45/// * [`DRAG_END_EVENT`]
46/// * [`DROP_EVENT`]
47///
48/// # Services
49///
50/// Services this extension provides.
51///
52/// * [`DRAG_DROP`]
53#[derive(Default)]
54pub struct DragDropManager {
55    // last cursor move position (scaled).
56    pos: DipPoint,
57    // last cursor move over `pos_window` and source device.
58    pos_window: Option<WindowId>,
59    // last cursor move hit-test (on the pos_window or a nested window).
60    hits: Option<HitTestInfo>,
61    hovered: Option<InteractionPath>,
62}
63
64impl AppExtension for DragDropManager {
65    fn event_preview(&mut self, update: &mut EventUpdate) {
66        let mut update_sv = false;
67        if let Some(args) = RAW_DRAG_DROPPED_EVENT.on(update) {
68            // system drop
69            let mut sv = DRAG_DROP_SV.write();
70            let len = sv.system_dragging.len();
71            for data in &args.data {
72                sv.system_dragging.retain(|d| d != data);
73            }
74            update_sv = len != sv.system_dragging.len();
75
76            // view-process can notify multiple drops in sequence with the same ID, so we only notify DROP_EVENT
77            // on he next update
78            if self.pos_window == Some(args.window_id) {
79                if let Some(hovered) = &self.hovered {
80                    match &mut sv.pending_drop {
81                        Some((id, target, data, allowed)) => {
82                            if target != hovered {
83                                tracing::error!("drop sequence across different hovered")
84                            } else if *id != args.drop_id {
85                                tracing::error!("drop_id changed mid sequence")
86                            } else if *allowed != args.allowed {
87                                tracing::error!("allowed effects changed mid sequence")
88                            } else {
89                                data.extend(args.data.iter().cloned());
90                            }
91                        }
92                        None => sv.pending_drop = Some((args.drop_id, hovered.clone(), args.data.clone(), args.allowed)),
93                    }
94                }
95            }
96            UPDATES.update(None);
97        } else if let Some(args) = RAW_DRAG_HOVERED_EVENT.on(update) {
98            // system drag hover window
99            update_sv = true;
100            DRAG_DROP_SV.write().system_dragging.extend(args.data.iter().cloned());
101        } else if let Some(args) = RAW_DRAG_MOVED_EVENT.on(update) {
102            // code adapted from the MouseManager implementation for mouse hovered
103            let moved = self.pos != args.position || self.pos_window != Some(args.window_id);
104            if moved {
105                self.pos = args.position;
106                self.pos_window = Some(args.window_id);
107
108                let mut position = args.position;
109
110                // mouse_move data
111                let mut frame_info = match WINDOWS.widget_tree(args.window_id) {
112                    Ok(f) => f,
113                    Err(_) => {
114                        // window not found
115                        if let Some(hovered) = self.hovered.take() {
116                            DRAG_HOVERED_EVENT.notify(DragHoveredArgs::now(
117                                Some(hovered),
118                                None,
119                                position,
120                                HitTestInfo::no_hits(args.window_id),
121                            ));
122                            self.pos_window = None;
123                        }
124                        return;
125                    }
126                };
127
128                let mut pos_hits = frame_info.root().hit_test(position.to_px(frame_info.scale_factor()));
129
130                let target = if let Some(t) = pos_hits.target() {
131                    if let Some(w) = frame_info.get(t.widget_id) {
132                        if let Some(f) = w.nested_window_tree() {
133                            // nested window hit
134                            frame_info = f;
135                            let factor = frame_info.scale_factor();
136                            let pos = position.to_px(factor);
137                            let pos = w.inner_transform().inverse().and_then(|t| t.transform_point(pos)).unwrap_or(pos);
138                            pos_hits = frame_info.root().hit_test(pos);
139                            position = pos.to_dip(factor);
140                            pos_hits
141                                .target()
142                                .and_then(|h| frame_info.get(h.widget_id))
143                                .map(|w| w.interaction_path())
144                                .unwrap_or_else(|| frame_info.root().interaction_path())
145                        } else {
146                            w.interaction_path()
147                        }
148                    } else {
149                        tracing::error!("hits target `{}` not found", t.widget_id);
150                        frame_info.root().interaction_path()
151                    }
152                } else {
153                    frame_info.root().interaction_path()
154                }
155                .unblocked();
156
157                self.hits = Some(pos_hits.clone());
158
159                // drag_enter/leave.
160                let hovered_args = if self.hovered != target {
161                    let prev_target = mem::replace(&mut self.hovered, target.clone());
162                    let args = DragHoveredArgs::now(prev_target, target.clone(), position, pos_hits.clone());
163                    Some(args)
164                } else {
165                    None
166                };
167
168                // mouse_move
169                if let Some(target) = target {
170                    let args = DragMoveArgs::now(frame_info.window_id(), args.coalesced_pos.clone(), position, pos_hits, target);
171                    DRAG_MOVE_EVENT.notify(args);
172                }
173
174                if let Some(args) = hovered_args {
175                    DRAG_HOVERED_EVENT.notify(args);
176                }
177            }
178        } else if let Some(args) = RAW_DRAG_CANCELLED_EVENT.on(update) {
179            // system drag cancelled of dragged out of all app windows
180            let mut sv = DRAG_DROP_SV.write();
181            update_sv = !sv.system_dragging.is_empty();
182            sv.system_dragging.clear();
183
184            if let Some(prev) = self.hovered.take() {
185                self.pos_window = None;
186                DRAG_HOVERED_EVENT.notify(DragHoveredArgs::now(
187                    Some(prev),
188                    None,
189                    self.pos,
190                    self.hits.take().unwrap_or_else(|| HitTestInfo::no_hits(args.window_id)),
191                ));
192            }
193        } else if let Some(args) = RAW_APP_DRAG_ENDED_EVENT.on(update) {
194            let mut sv = DRAG_DROP_SV.write();
195            sv.app_dragging.retain(|d| {
196                if d.view_id != args.id {
197                    return true;
198                }
199
200                if !args.applied.is_empty() && !d.allowed.contains(args.applied) {
201                    tracing::error!(
202                        "drop target applied disallowed effect, allowed={:?}, applied={:?}",
203                        d.allowed,
204                        args.applied
205                    );
206                }
207
208                DRAG_END_EVENT.notify(DragEndArgs::now(d.target.clone(), args.applied));
209
210                false
211            });
212        }
213
214        if update_sv {
215            DRAG_DROP.update_var();
216        }
217    }
218
219    fn event(&mut self, update: &mut EventUpdate) {
220        if let Some(args) = MOUSE_INPUT_EVENT.on_unhandled(update) {
221            if matches!(args.state, ButtonState::Pressed) {
222                if let Some(wgt) = WINDOWS.widget_info(args.target.widget_id()) {
223                    if let Some(wgt) = wgt.self_and_ancestors().find(|w| w.is_draggable()) {
224                        // unhandled mouse press on draggable
225                        args.propagation().stop();
226                        let target = wgt.interaction_path();
227                        let args = DragStartArgs::now(target.clone());
228                        DRAG_START_EVENT.notify(args);
229                        DRAG_DROP_SV.write().app_drag = Some(AppDragging {
230                            target,
231                            data: vec![],
232                            handles: vec![],
233                            allowed: DragDropEffect::empty(),
234                            view_id: DragDropId(0),
235                        }); // calls to DRAG_DROP.drag are now valid
236                    }
237                }
238            }
239        } else if let Some(args) = TOUCH_INPUT_EVENT.on_unhandled(update) {
240            if matches!(args.phase, TouchPhase::Start) {
241                if let Some(wgt) = WINDOWS.widget_info(args.target.widget_id()) {
242                    if let Some(wgt) = wgt.self_and_ancestors().find(|w| w.is_draggable()) {
243                        // unhandled touch start on draggable
244                        args.propagation().stop();
245                        let target = wgt.interaction_path();
246                        let args = DragStartArgs::now(target.clone());
247                        DRAG_START_EVENT.notify(args);
248                        DRAG_DROP_SV.write().app_drag = Some(AppDragging {
249                            target,
250                            data: vec![],
251                            handles: vec![],
252                            allowed: DragDropEffect::empty(),
253                            view_id: DragDropId(0),
254                        }); // calls to DRAG_DROP.drag are now valid
255                    }
256                }
257            }
258        } else if let Some(args) = DRAG_START_EVENT.on(update) {
259            // finished notifying draggable drag start
260            let mut sv = DRAG_DROP_SV.write();
261            let mut data = sv.app_drag.take();
262            let mut cancel = args.propagation_handle.is_stopped();
263            if !cancel {
264                if let Some(d) = &mut data {
265                    if d.data.is_empty() {
266                        d.data.push(encode_widget_id(args.target.widget_id()));
267                        d.allowed = DragDropEffect::all();
268                    }
269                    match WINDOWS_DRAG_DROP.start_drag_drop(d.target.window_id(), mem::take(&mut d.data), d.allowed) {
270                        Ok(id) => {
271                            d.view_id = id;
272                            sv.app_dragging.push(data.take().unwrap());
273                        }
274                        Err(e) => {
275                            tracing::error!("cannot start drag&drop, {e}");
276                            cancel = true;
277                        }
278                    }
279                } else {
280                    tracing::warn!("external notification of DRAG_START_EVENT ignored")
281                }
282            }
283            if cancel {
284                if let Some(d) = data {
285                    DRAG_END_EVENT.notify(DragEndArgs::now(d.target, DragDropEffect::empty()));
286                }
287            }
288        } else if let Some(args) = DROP_EVENT.on(update) {
289            let _ = WINDOWS_DRAG_DROP.drag_dropped(args.target.window_id(), args.drop_id, *args.applied.lock());
290        }
291    }
292
293    fn update_preview(&mut self) {
294        let mut sv = DRAG_DROP_SV.write();
295
296        // fulfill drop requests
297        if let Some((id, target, data, allowed)) = sv.pending_drop.take() {
298            let window_id = self.pos_window.take().unwrap();
299            let hits = self.hits.take().unwrap_or_else(|| HitTestInfo::no_hits(window_id));
300            DRAG_HOVERED_EVENT.notify(DragHoveredArgs::now(Some(target.clone()), None, self.pos, hits.clone()));
301            DROP_EVENT.notify(DropArgs::now(
302                target,
303                data,
304                allowed,
305                self.pos,
306                hits,
307                id,
308                Arc::new(Mutex::new(DragDropEffect::empty())),
309            ));
310        }
311    }
312}
313
314/// Drag & drop service.
315#[allow(non_camel_case_types)]
316pub struct DRAG_DROP;
317impl DRAG_DROP {
318    /// All data current dragging.
319    pub fn dragging_data(&self) -> ReadOnlyArcVar<Vec<DragDropData>> {
320        DRAG_DROP_SV.read().data.read_only()
321    }
322
323    /// Start dragging `data`.
324    ///
325    /// This method will only work if a [`DRAG_START_EVENT`] is notifying. Handlers of draggable widgets
326    /// can provide custom drag data using this method.
327    ///
328    /// Returns a handle that can be dropped to cancel the drag operation. A [`DRAG_END_EVENT`] notifies
329    /// the draggable widget on cancel or drop. Logs an error message and returns a dummy handle on error.
330    ///
331    /// Note that the `allowed_effects` apply to all data, if a previous handler already set data with an incompatible
332    /// effect the call is an error and the data ignored.
333    pub fn drag(&self, data: DragDropData, allowed_effects: DragDropEffect) -> DragHandle {
334        let mut sv = DRAG_DROP_SV.write();
335        if let Some(d) = &mut sv.app_drag {
336            if allowed_effects.is_empty() {
337                tracing::error!("cannot drag, no `allowed_effects`");
338                return DragHandle::dummy();
339            }
340
341            if d.allowed.is_empty() {
342                d.allowed = allowed_effects;
343            } else {
344                if !d.allowed.contains(allowed_effects) {
345                    tracing::error!("cannot drag, other data already set with incompatible `allowed_effects`");
346                    return DragHandle::dummy();
347                }
348                d.allowed |= allowed_effects
349            }
350
351            d.data.push(data);
352            let (owner, handle) = DragHandle::new();
353            d.handles.push(owner);
354            return handle;
355        }
356        tracing::error!("cannot drag, not in `DRAG_START_EVENT` interval");
357        DragHandle::dummy()
358    }
359
360    fn update_var(&self) {
361        let sv = DRAG_DROP_SV.read();
362        sv.data.set(sv.system_dragging.clone());
363    }
364}
365
366app_local! {
367    static DRAG_DROP_SV: DragDropService = DragDropService {
368        data: var(vec![]),
369        system_dragging: vec![],
370        app_drag: None,
371        app_dragging: vec![],
372        pending_drop: None,
373    };
374}
375struct DragDropService {
376    data: ArcVar<Vec<DragDropData>>,
377
378    system_dragging: Vec<DragDropData>,
379
380    app_drag: Option<AppDragging>,
381    app_dragging: Vec<AppDragging>,
382
383    pending_drop: Option<(DragDropId, InteractionPath, Vec<DragDropData>, DragDropEffect)>,
384}
385struct AppDragging {
386    target: InteractionPath,
387    data: Vec<DragDropData>,
388    handles: Vec<HandleOwner<()>>,
389    allowed: DragDropEffect,
390    view_id: DragDropId,
391}
392
393/// Represents dragging data.
394///
395/// Drop all clones of this handle to cancel the drag operation.
396#[derive(Clone, PartialEq, Eq, Hash, Debug)]
397#[repr(transparent)]
398#[must_use = "dropping the handle cancels the drag operation"]
399pub struct DragHandle(Handle<()>);
400impl DragHandle {
401    fn new() -> (HandleOwner<()>, Self) {
402        let (owner, handle) = Handle::new(());
403        (owner, Self(handle))
404    }
405
406    /// New handle to nothing.
407    pub fn dummy() -> Self {
408        Self(Handle::dummy(()))
409    }
410
411    /// Drops the handle but does **not** cancel the drag operation.
412    ///
413    /// The drag data stays alive until the user completes or cancels the operation.
414    pub fn perm(self) {
415        self.0.perm();
416    }
417
418    /// If another handle has called [`perm`](Self::perm).
419    ///
420    /// If `true` operation will run to completion.
421    pub fn is_permanent(&self) -> bool {
422        self.0.is_permanent()
423    }
424
425    /// Drops the handle and forces operation the cancel.
426    pub fn cancel(self) {
427        self.0.force_drop()
428    }
429
430    /// If another handle has called [`cancel`](Self::cancel).
431    pub fn is_canceled(&self) -> bool {
432        self.0.is_dropped()
433    }
434
435    /// Create a weak handle.
436    pub fn downgrade(&self) -> WeakDragHandle {
437        WeakDragHandle(self.0.downgrade())
438    }
439}
440/// Weak [`DragHandle`].
441#[derive(Clone, PartialEq, Eq, Hash, Default, Debug)]
442pub struct WeakDragHandle(WeakHandle<()>);
443impl WeakDragHandle {
444    /// New weak handle that does not upgrade.
445    pub fn new() -> Self {
446        Self(WeakHandle::new())
447    }
448
449    /// Gets the strong handle if it is still subscribed.
450    pub fn upgrade(&self) -> Option<DragHandle> {
451        self.0.upgrade().map(DragHandle)
452    }
453}
454
455/// [`WidgetInfo`] extensions for drag & drop service.
456pub trait WidgetInfoDragDropExt {
457    /// If this widget can be dragged and dropped.
458    fn is_draggable(&self) -> bool;
459}
460impl WidgetInfoDragDropExt for WidgetInfo {
461    fn is_draggable(&self) -> bool {
462        self.meta().flagged(*IS_DRAGGABLE_ID)
463    }
464}
465
466/// [`WidgetInfoBuilder`] extensions for drag & drop service.
467pub trait WidgetInfoBuilderDragDropExt {
468    /// Flag the widget as draggable.
469    fn draggable(&mut self);
470}
471impl WidgetInfoBuilderDragDropExt for WidgetInfoBuilder {
472    fn draggable(&mut self) {
473        self.flag_meta(*IS_DRAGGABLE_ID);
474    }
475}
476
477static_id! {
478    static ref IS_DRAGGABLE_ID: StateId<()>;
479}
480
481event_args! {
482    /// Arguments for [`DROP_EVENT`].
483    pub struct DropArgs {
484        /// Hovered target of the drag&drop gesture.
485        pub target: InteractionPath,
486        /// Drag&drop data payload.
487        pub data: Vec<DragDropData>,
488        /// Drop effects that the drag source allows.
489        pub allowed: DragDropEffect,
490        /// Position of the cursor in the window's content area.
491        pub position: DipPoint,
492        /// Hit-test result for the cursor point in the window.
493        pub hits: HitTestInfo,
494
495        drop_id: DragDropId,
496        applied: Arc<Mutex<DragDropEffect>>,
497
498        ..
499
500        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
501            list.insert_wgt(&self.target);
502        }
503    }
504
505    /// Arguments for [`DRAG_HOVERED_EVENT`].
506    pub struct DragHoveredArgs {
507        /// Previous hovered target.
508        pub prev_target: Option<InteractionPath>,
509        /// New hovered target.
510        pub target: Option<InteractionPath>,
511        /// Position of the cursor in the window's content area.
512        pub position: DipPoint,
513        /// Hit-test result for the cursor point in the window.
514        pub hits: HitTestInfo,
515
516        ..
517
518        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
519            if let Some(p) = &self.prev_target {
520                list.insert_wgt(p);
521            }
522            if let Some(p) = &self.target {
523                list.insert_wgt(p);
524            }
525        }
526    }
527
528    /// [`DRAG_MOVE_EVENT`] arguments.
529    pub struct DragMoveArgs {
530        /// Id of window that received the event.
531        pub window_id: WindowId,
532
533        /// Positions of the cursor in between the previous event and this one.
534        ///
535        /// Drag move events can be coalesced, i.e. multiple moves packed into a single event.
536        pub coalesced_pos: Vec<DipPoint>,
537
538        /// Position of the cursor in the window's content area.
539        pub position: DipPoint,
540
541        /// Hit-test result for the cursor point in the window.
542        pub hits: HitTestInfo,
543
544        /// Full path to the top-most hit in [`hits`](DragMoveArgs::hits).
545        pub target: InteractionPath,
546
547        ..
548
549        /// The [`target`].
550        ///
551        /// [`target`]: Self::target
552        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
553            list.insert_wgt(&self.target);
554        }
555    }
556
557    /// Arguments for [`DRAG_START_EVENT`].
558    pub struct DragStartArgs {
559        /// Draggable widget that has started dragging.
560        pub target: InteractionPath,
561
562        ..
563
564        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
565            list.insert_wgt(&self.target);
566        }
567    }
568
569    /// Arguments for [`DRAG_END_EVENT`].
570    pub struct DragEndArgs {
571        /// Draggable widget that was dragging.
572        pub target: InteractionPath,
573
574        /// Effect applied by the drop target on the data.
575        ///
576        /// Is empty or a single flag.
577        pub applied: DragDropEffect,
578
579        ..
580
581        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
582            list.insert_wgt(&self.target);
583        }
584
585        /// The `applied` field can only be empty or only have a single flag set.
586        fn validate(&self) -> Result<(), Txt> {
587            if self.applied.is_empty() && self.applied.len() > 1 {
588                return Err("only one or none `DragDropEffect` can be applied".into());
589            }
590            Ok(())
591        }
592    }
593}
594event! {
595    /// Drag&drop action finished over some drop target widget.
596    pub static DROP_EVENT: DropArgs;
597    /// Drag&drop enter or exit a drop target widget.
598    pub static DRAG_HOVERED_EVENT: DragHoveredArgs;
599    /// Drag&drop is dragging over the target widget.
600    pub static DRAG_MOVE_EVENT: DragMoveArgs;
601    /// Drag&drop started dragging a draggable widget.
602    ///
603    /// If propagation is stopped the drag operation is cancelled. Handlers can use
604    /// [`DRAG_DROP.drag`] to set the data, otherwise the widget ID will be dragged.
605    ///
606    /// [`DRAG_DROP.drag`]: DRAG_DROP::drag
607    pub static DRAG_START_EVENT: DragStartArgs;
608
609    /// Drag&drop gesture started from the draggable widget has ended.
610    pub static DRAG_END_EVENT: DragEndArgs;
611}
612
613impl DropArgs {
614    /// If the `widget_id` is in the [`target`] is enabled.
615    ///
616    /// [`target`]: Self::target
617    pub fn is_enabled(&self, widget_id: WidgetId) -> bool {
618        self.target.interactivity_of(widget_id).map(|i| i.is_enabled()).unwrap_or(false)
619    }
620
621    /// If the `widget_id` is in the [`target`] is disabled.
622    ///
623    /// [`target`]: Self::target
624    pub fn is_disabled(&self, widget_id: WidgetId) -> bool {
625        self.target.interactivity_of(widget_id).map(|i| i.is_disabled()).unwrap_or(false)
626    }
627
628    /// Stop propagation and set the `effect` that was applied to the data.
629    ///
630    /// Logs an error if propagation is already stopped.
631    ///
632    /// # Panics
633    ///
634    /// Panics if `effect` sets more then one flag or is not [`allowed`].
635    ///
636    /// [`allowed`]: Self::allowed
637    pub fn applied(&self, effect: DragDropEffect) {
638        assert!(effect.len() > 1, "can only apply one effect");
639        assert!(self.allowed.contains(effect), "source does not allow this effect");
640
641        let mut e = self.applied.lock();
642        if !self.propagation().is_stopped() {
643            self.propagation().stop();
644            *e = effect;
645        } else {
646            tracing::error!("drop already handled");
647        }
648    }
649}
650
651impl DragHoveredArgs {
652    /// Gets the [`DRAG_DROP.dragging_data`].
653    ///
654    /// [`DRAG_DROP.dragging_data`]: DRAG_DROP::dragging_data
655    pub fn data(&self) -> ReadOnlyArcVar<Vec<DragDropData>> {
656        DRAG_DROP.dragging_data()
657    }
658
659    /// Returns `true` if the [`WIDGET`] was not hovered, but now is.
660    ///
661    /// [`WIDGET`]: zng_app::widget::WIDGET
662    pub fn is_drag_enter(&self) -> bool {
663        !self.was_over() && self.is_over()
664    }
665
666    /// Returns `true` if the [`WIDGET`] was hovered, but now isn't.
667    ///
668    /// [`WIDGET`]: zng_app::widget::WIDGET
669    pub fn is_drag_leave(&self) -> bool {
670        self.was_over() && !self.is_over()
671    }
672
673    /// Returns `true` if the [`WIDGET`] is in [`prev_target`].
674    ///
675    /// [`prev_target`]: Self::prev_target
676    /// [`WIDGET`]: zng_app::widget::WIDGET
677    pub fn was_over(&self) -> bool {
678        if let Some(t) = &self.prev_target {
679            return t.contains(WIDGET.id());
680        }
681
682        false
683    }
684
685    /// Returns `true` if the [`WIDGET`] is in [`target`].
686    ///
687    /// [`target`]: Self::target
688    /// [`WIDGET`]: zng_app::widget::WIDGET
689    pub fn is_over(&self) -> bool {
690        if let Some(t) = &self.target {
691            return t.contains(WIDGET.id());
692        }
693
694        false
695    }
696
697    /// Returns `true` if the widget was enabled in [`prev_target`].
698    ///
699    /// [`prev_target`]: Self::prev_target
700    pub fn was_enabled(&self, widget_id: WidgetId) -> bool {
701        self.prev_target
702            .as_ref()
703            .and_then(|t| t.interactivity_of(widget_id))
704            .map(|itr| itr.is_enabled())
705            .unwrap_or(false)
706    }
707
708    /// Returns `true` if the widget was disabled in [`prev_target`].
709    ///
710    /// [`prev_target`]: Self::prev_target
711    pub fn was_disabled(&self, widget_id: WidgetId) -> bool {
712        self.prev_target
713            .as_ref()
714            .and_then(|t| t.interactivity_of(widget_id))
715            .map(|itr| itr.is_disabled())
716            .unwrap_or(false)
717    }
718
719    /// Returns `true` if the widget is enabled in [`target`].
720    ///
721    /// [`target`]: Self::target
722    pub fn is_enabled(&self, widget_id: WidgetId) -> bool {
723        self.target
724            .as_ref()
725            .and_then(|t| t.interactivity_of(widget_id))
726            .map(|itr| itr.is_enabled())
727            .unwrap_or(false)
728    }
729
730    /// Returns `true` if the widget is disabled in [`target`].
731    ///
732    /// [`target`]: Self::target
733    pub fn is_disabled(&self, widget_id: WidgetId) -> bool {
734        self.target
735            .as_ref()
736            .and_then(|t| t.interactivity_of(widget_id))
737            .map(|itr| itr.is_disabled())
738            .unwrap_or(false)
739    }
740
741    /// Returns `true` if the [`WIDGET`] was not hovered or was disabled, but now is hovered and enabled.
742    ///
743    /// [`WIDGET`]: zng_app::widget::WIDGET
744    pub fn is_drag_enter_enabled(&self) -> bool {
745        (!self.was_over() || self.was_disabled(WIDGET.id())) && self.is_over() && self.is_enabled(WIDGET.id())
746    }
747
748    /// Returns `true` if the [`WIDGET`] was hovered and enabled, but now is not hovered or is disabled.
749    ///
750    /// [`WIDGET`]: zng_app::widget::WIDGET
751    pub fn is_drag_leave_enabled(&self) -> bool {
752        self.was_over() && self.was_enabled(WIDGET.id()) && (!self.is_over() || self.is_disabled(WIDGET.id()))
753    }
754
755    /// Returns `true` if the [`WIDGET`] was not hovered or was enabled, but now is hovered and disabled.
756    ///
757    /// [`WIDGET`]: zng_app::widget::WIDGET
758    pub fn is_drag_enter_disabled(&self) -> bool {
759        (!self.was_over() || self.was_enabled(WIDGET.id())) && self.is_over() && self.is_disabled(WIDGET.id())
760    }
761
762    /// Returns `true` if the [`WIDGET`] was hovered and disabled, but now is not hovered or is enabled.
763    ///
764    /// [`WIDGET`]: zng_app::widget::WIDGET
765    pub fn is_drag_leave_disabled(&self) -> bool {
766        self.was_over() && self.was_disabled(WIDGET.id()) && (!self.is_over() || self.is_enabled(WIDGET.id()))
767    }
768}
769
770impl DragEndArgs {
771    /// Data was dropped on a valid target.
772    pub fn was_dropped(&self) -> bool {
773        !self.applied.is_empty()
774    }
775
776    /// Stopped dragging without dropping on a valid drop target.
777    pub fn was_canceled(&self) -> bool {
778        self.applied.is_empty()
779    }
780}
781
782/// Encode an widget ID for drag&drop data.
783pub fn encode_widget_id(id: WidgetId) -> DragDropData {
784    DragDropData::Text {
785        format: formatx!("zng/{}", APP_GUID.read().simple()),
786        data: formatx!("wgt-{}", id.get()),
787    }
788}
789
790/// Decode an widget ID from drag&drop data.
791///
792/// The ID will only decode if it was encoded by the same app instance.
793pub fn decode_widget_id(data: &DragDropData) -> Option<WidgetId> {
794    if let DragDropData::Text { format, data } = data {
795        if let Some(guid) = format.strip_prefix("zng/") {
796            if let Some(id) = data.strip_prefix("wgt-") {
797                if guid == APP_GUID.read().simple().to_string() {
798                    if let Ok(id) = id.parse::<u64>() {
799                        return Some(WidgetId::from_raw(id));
800                    }
801                }
802            }
803        }
804    }
805    None
806}
807
808app_local! {
809    static APP_GUID: uuid::Uuid = uuid::Uuid::new_v4();
810}