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    APP, 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::{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                && let Some(hovered) = &self.hovered
80            {
81                match &mut sv.pending_drop {
82                    Some((id, target, data, allowed)) => {
83                        if target != hovered {
84                            tracing::error!("drop sequence across different hovered")
85                        } else if *id != args.drop_id {
86                            tracing::error!("drop_id changed mid sequence")
87                        } else if *allowed != args.allowed {
88                            tracing::error!("allowed effects changed mid sequence")
89                        } else {
90                            data.extend(args.data.iter().cloned());
91                        }
92                    }
93                    None => sv.pending_drop = Some((args.drop_id, hovered.clone(), args.data.clone(), args.allowed)),
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                && let Some(wgt) = WINDOWS.widget_info(args.target.widget_id())
223                && let Some(wgt) = wgt.self_and_ancestors().find(|w| w.is_draggable())
224            {
225                // unhandled mouse press on draggable
226                args.propagation().stop();
227                let target = wgt.interaction_path();
228                let args = DragStartArgs::now(target.clone());
229                DRAG_START_EVENT.notify(args);
230                DRAG_DROP_SV.write().app_drag = Some(AppDragging {
231                    target,
232                    data: vec![],
233                    handles: vec![],
234                    allowed: DragDropEffect::empty(),
235                    view_id: DragDropId(0),
236                }); // calls to DRAG_DROP.drag are now valid
237            }
238        } else if let Some(args) = TOUCH_INPUT_EVENT.on_unhandled(update) {
239            if matches!(args.phase, TouchPhase::Start)
240                && let Some(wgt) = WINDOWS.widget_info(args.target.widget_id())
241                && let Some(wgt) = wgt.self_and_ancestors().find(|w| w.is_draggable())
242            {
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        } else if let Some(args) = DRAG_START_EVENT.on(update) {
257            // finished notifying draggable drag start
258            let mut sv = DRAG_DROP_SV.write();
259            let mut data = sv.app_drag.take();
260            let mut cancel = args.propagation_handle.is_stopped();
261            if !cancel {
262                if let Some(d) = &mut data {
263                    if d.data.is_empty() {
264                        d.data.push(encode_widget_id(args.target.widget_id()));
265                        d.allowed = DragDropEffect::all();
266                    }
267                    match WINDOWS_DRAG_DROP.start_drag_drop(d.target.window_id(), mem::take(&mut d.data), d.allowed) {
268                        Ok(id) => {
269                            d.view_id = id;
270                            sv.app_dragging.push(data.take().unwrap());
271                        }
272                        Err(e) => {
273                            tracing::error!("cannot start drag&drop, {e}");
274                            cancel = true;
275                        }
276                    }
277                } else {
278                    tracing::warn!("external notification of DRAG_START_EVENT ignored")
279                }
280            }
281            if cancel && let Some(d) = data {
282                DRAG_END_EVENT.notify(DragEndArgs::now(d.target, DragDropEffect::empty()));
283            }
284        } else if let Some(args) = DROP_EVENT.on(update) {
285            let _ = WINDOWS_DRAG_DROP.drag_dropped(args.target.window_id(), args.drop_id, *args.applied.lock());
286        }
287    }
288
289    fn update_preview(&mut self) {
290        let mut sv = DRAG_DROP_SV.write();
291
292        // fulfill drop requests
293        if let Some((id, target, data, allowed)) = sv.pending_drop.take() {
294            let window_id = self.pos_window.take().unwrap();
295            let hits = self.hits.take().unwrap_or_else(|| HitTestInfo::no_hits(window_id));
296            DRAG_HOVERED_EVENT.notify(DragHoveredArgs::now(Some(target.clone()), None, self.pos, hits.clone()));
297            DROP_EVENT.notify(DropArgs::now(
298                target,
299                data,
300                allowed,
301                self.pos,
302                hits,
303                id,
304                Arc::new(Mutex::new(DragDropEffect::empty())),
305            ));
306        }
307    }
308}
309
310/// Drag & drop service.
311///
312/// # Provider
313///
314/// This service is provided by the [`DragDropManager`] extension, it will panic if used in an app not extended.
315#[allow(non_camel_case_types)]
316pub struct DRAG_DROP;
317impl DRAG_DROP {
318    /// All data current dragging.
319    pub fn dragging_data(&self) -> Var<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 = {
368        APP.extensions().require::<DragDropManager>();
369        DragDropService {
370            data: var(vec![]),
371            system_dragging: vec![],
372            app_drag: None,
373            app_dragging: vec![],
374            pending_drop: None,
375        }
376    };
377}
378struct DragDropService {
379    data: Var<Vec<DragDropData>>,
380
381    system_dragging: Vec<DragDropData>,
382
383    app_drag: Option<AppDragging>,
384    app_dragging: Vec<AppDragging>,
385
386    pending_drop: Option<(DragDropId, InteractionPath, Vec<DragDropData>, DragDropEffect)>,
387}
388struct AppDragging {
389    target: InteractionPath,
390    data: Vec<DragDropData>,
391    handles: Vec<HandleOwner<()>>,
392    allowed: DragDropEffect,
393    view_id: DragDropId,
394}
395
396/// Represents dragging data.
397///
398/// Drop all clones of this handle to cancel the drag operation.
399#[derive(Clone, PartialEq, Eq, Hash, Debug)]
400#[repr(transparent)]
401#[must_use = "dropping the handle cancels the drag operation"]
402pub struct DragHandle(Handle<()>);
403impl DragHandle {
404    fn new() -> (HandleOwner<()>, Self) {
405        let (owner, handle) = Handle::new(());
406        (owner, Self(handle))
407    }
408
409    /// New handle to nothing.
410    pub fn dummy() -> Self {
411        Self(Handle::dummy(()))
412    }
413
414    /// Drops the handle but does **not** cancel the drag operation.
415    ///
416    /// The drag data stays alive until the user completes or cancels the operation.
417    pub fn perm(self) {
418        self.0.perm();
419    }
420
421    /// If another handle has called [`perm`](Self::perm).
422    ///
423    /// If `true` operation will run to completion.
424    pub fn is_permanent(&self) -> bool {
425        self.0.is_permanent()
426    }
427
428    /// Drops the handle and forces operation the cancel.
429    pub fn cancel(self) {
430        self.0.force_drop()
431    }
432
433    /// If another handle has called [`cancel`](Self::cancel).
434    pub fn is_canceled(&self) -> bool {
435        self.0.is_dropped()
436    }
437
438    /// Create a weak handle.
439    pub fn downgrade(&self) -> WeakDragHandle {
440        WeakDragHandle(self.0.downgrade())
441    }
442}
443/// Weak [`DragHandle`].
444#[derive(Clone, PartialEq, Eq, Hash, Default, Debug)]
445pub struct WeakDragHandle(WeakHandle<()>);
446impl WeakDragHandle {
447    /// New weak handle that does not upgrade.
448    pub fn new() -> Self {
449        Self(WeakHandle::new())
450    }
451
452    /// Gets the strong handle if it is still subscribed.
453    pub fn upgrade(&self) -> Option<DragHandle> {
454        self.0.upgrade().map(DragHandle)
455    }
456}
457
458/// [`WidgetInfo`] extensions for drag & drop service.
459pub trait WidgetInfoDragDropExt {
460    /// If this widget can be dragged and dropped.
461    fn is_draggable(&self) -> bool;
462}
463impl WidgetInfoDragDropExt for WidgetInfo {
464    fn is_draggable(&self) -> bool {
465        self.meta().flagged(*IS_DRAGGABLE_ID)
466    }
467}
468
469/// [`WidgetInfoBuilder`] extensions for drag & drop service.
470pub trait WidgetInfoBuilderDragDropExt {
471    /// Flag the widget as draggable.
472    fn draggable(&mut self);
473}
474impl WidgetInfoBuilderDragDropExt for WidgetInfoBuilder {
475    fn draggable(&mut self) {
476        self.flag_meta(*IS_DRAGGABLE_ID);
477    }
478}
479
480static_id! {
481    static ref IS_DRAGGABLE_ID: StateId<()>;
482}
483
484event_args! {
485    /// Arguments for [`DROP_EVENT`].
486    pub struct DropArgs {
487        /// Hovered target of the drag&drop gesture.
488        pub target: InteractionPath,
489        /// Drag&drop data payload.
490        pub data: Vec<DragDropData>,
491        /// Drop effects that the drag source allows.
492        pub allowed: DragDropEffect,
493        /// Position of the cursor in the window's content area.
494        pub position: DipPoint,
495        /// Hit-test result for the cursor point in the window.
496        pub hits: HitTestInfo,
497
498        drop_id: DragDropId,
499        applied: Arc<Mutex<DragDropEffect>>,
500
501        ..
502
503        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
504            list.insert_wgt(&self.target);
505        }
506    }
507
508    /// Arguments for [`DRAG_HOVERED_EVENT`].
509    pub struct DragHoveredArgs {
510        /// Previous hovered target.
511        pub prev_target: Option<InteractionPath>,
512        /// New hovered target.
513        pub target: Option<InteractionPath>,
514        /// Position of the cursor in the window's content area.
515        pub position: DipPoint,
516        /// Hit-test result for the cursor point in the window.
517        pub hits: HitTestInfo,
518
519        ..
520
521        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
522            if let Some(p) = &self.prev_target {
523                list.insert_wgt(p);
524            }
525            if let Some(p) = &self.target {
526                list.insert_wgt(p);
527            }
528        }
529    }
530
531    /// [`DRAG_MOVE_EVENT`] arguments.
532    pub struct DragMoveArgs {
533        /// Id of window that received the event.
534        pub window_id: WindowId,
535
536        /// Positions of the cursor in between the previous event and this one.
537        ///
538        /// Drag move events can be coalesced, i.e. multiple moves packed into a single event.
539        pub coalesced_pos: Vec<DipPoint>,
540
541        /// Position of the cursor in the window's content area.
542        pub position: DipPoint,
543
544        /// Hit-test result for the cursor point in the window.
545        pub hits: HitTestInfo,
546
547        /// Full path to the top-most hit in [`hits`](DragMoveArgs::hits).
548        pub target: InteractionPath,
549
550        ..
551
552        /// The [`target`].
553        ///
554        /// [`target`]: Self::target
555        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
556            list.insert_wgt(&self.target);
557        }
558    }
559
560    /// Arguments for [`DRAG_START_EVENT`].
561    pub struct DragStartArgs {
562        /// Draggable widget that has started dragging.
563        pub target: InteractionPath,
564
565        ..
566
567        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
568            list.insert_wgt(&self.target);
569        }
570    }
571
572    /// Arguments for [`DRAG_END_EVENT`].
573    pub struct DragEndArgs {
574        /// Draggable widget that was dragging.
575        pub target: InteractionPath,
576
577        /// Effect applied by the drop target on the data.
578        ///
579        /// Is empty or a single flag.
580        pub applied: DragDropEffect,
581
582        ..
583
584        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
585            list.insert_wgt(&self.target);
586        }
587
588        /// The `applied` field can only be empty or only have a single flag set.
589        fn validate(&self) -> Result<(), Txt> {
590            if self.applied.is_empty() && self.applied.len() > 1 {
591                return Err("only one or none `DragDropEffect` can be applied".into());
592            }
593            Ok(())
594        }
595    }
596}
597event! {
598    /// Drag&drop action finished over some drop target widget.
599    pub static DROP_EVENT: DropArgs;
600    /// Drag&drop enter or exit a drop target widget.
601    pub static DRAG_HOVERED_EVENT: DragHoveredArgs;
602    /// Drag&drop is dragging over the target widget.
603    pub static DRAG_MOVE_EVENT: DragMoveArgs;
604    /// Drag&drop started dragging a draggable widget.
605    ///
606    /// If propagation is stopped the drag operation is cancelled. Handlers can use
607    /// [`DRAG_DROP.drag`] to set the data, otherwise the widget ID will be dragged.
608    ///
609    /// [`DRAG_DROP.drag`]: DRAG_DROP::drag
610    pub static DRAG_START_EVENT: DragStartArgs;
611
612    /// Drag&drop gesture started from the draggable widget has ended.
613    pub static DRAG_END_EVENT: DragEndArgs;
614}
615
616impl DropArgs {
617    /// Stop propagation and set the `effect` that was applied to the data.
618    ///
619    /// Logs an error if propagation is already stopped.
620    ///
621    /// # Panics
622    ///
623    /// Panics if `effect` sets more then one flag or is not [`allowed`].
624    ///
625    /// [`allowed`]: Self::allowed
626    pub fn applied(&self, effect: DragDropEffect) {
627        assert!(effect.len() > 1, "can only apply one effect");
628        assert!(self.allowed.contains(effect), "source does not allow this effect");
629
630        let mut e = self.applied.lock();
631        if !self.propagation().is_stopped() {
632            self.propagation().stop();
633            *e = effect;
634        } else {
635            tracing::error!("drop already handled");
636        }
637    }
638}
639
640impl DragHoveredArgs {
641    /// Gets the [`DRAG_DROP.dragging_data`].
642    ///
643    /// [`DRAG_DROP.dragging_data`]: DRAG_DROP::dragging_data
644    pub fn data(&self) -> Var<Vec<DragDropData>> {
645        DRAG_DROP.dragging_data()
646    }
647
648    /// Returns `true` if the [`WIDGET`] was not hovered, but now is.
649    ///
650    /// [`WIDGET`]: zng_app::widget::WIDGET
651    pub fn is_drag_enter(&self) -> bool {
652        !self.was_over() && self.is_over()
653    }
654
655    /// Returns `true` if the [`WIDGET`] was hovered, but now isn't.
656    ///
657    /// [`WIDGET`]: zng_app::widget::WIDGET
658    pub fn is_drag_leave(&self) -> bool {
659        self.was_over() && !self.is_over()
660    }
661
662    /// Returns `true` if the [`WIDGET`] is in [`prev_target`].
663    ///
664    /// [`prev_target`]: Self::prev_target
665    /// [`WIDGET`]: zng_app::widget::WIDGET
666    pub fn was_over(&self) -> bool {
667        if let Some(t) = &self.prev_target {
668            return t.contains(WIDGET.id());
669        }
670
671        false
672    }
673
674    /// Returns `true` if the [`WIDGET`] is in [`target`].
675    ///
676    /// [`target`]: Self::target
677    /// [`WIDGET`]: zng_app::widget::WIDGET
678    pub fn is_over(&self) -> bool {
679        if let Some(t) = &self.target {
680            return t.contains(WIDGET.id());
681        }
682
683        false
684    }
685
686    /// Returns `true` if the widget was enabled in [`prev_target`].
687    ///
688    /// [`prev_target`]: Self::prev_target
689    pub fn was_enabled(&self, widget_id: WidgetId) -> bool {
690        match &self.prev_target {
691            Some(t) => t.contains_enabled(widget_id),
692            None => false,
693        }
694    }
695
696    /// Returns `true` if the widget was disabled in [`prev_target`].
697    ///
698    /// [`prev_target`]: Self::prev_target
699    pub fn was_disabled(&self, widget_id: WidgetId) -> bool {
700        match &self.prev_target {
701            Some(t) => t.contains_disabled(widget_id),
702            None => false,
703        }
704    }
705
706    /// Returns `true` if the widget is enabled in [`target`].
707    ///
708    /// [`target`]: Self::target
709    pub fn is_enabled(&self, widget_id: WidgetId) -> bool {
710        match &self.target {
711            Some(t) => t.contains_enabled(widget_id),
712            None => false,
713        }
714    }
715
716    /// Returns `true` if the widget is disabled in [`target`].
717    ///
718    /// [`target`]: Self::target
719    pub fn is_disabled(&self, widget_id: WidgetId) -> bool {
720        match &self.target {
721            Some(t) => t.contains_disabled(widget_id),
722            None => false,
723        }
724    }
725
726    /// Returns `true` if the [`WIDGET`] was not hovered or was disabled, but now is hovered and enabled.
727    ///
728    /// [`WIDGET`]: zng_app::widget::WIDGET
729    pub fn is_drag_enter_enabled(&self) -> bool {
730        (!self.was_over() || self.was_disabled(WIDGET.id())) && self.is_over() && self.is_enabled(WIDGET.id())
731    }
732
733    /// Returns `true` if the [`WIDGET`] was hovered and enabled, but now is not hovered or is disabled.
734    ///
735    /// [`WIDGET`]: zng_app::widget::WIDGET
736    pub fn is_drag_leave_enabled(&self) -> bool {
737        self.was_over() && self.was_enabled(WIDGET.id()) && (!self.is_over() || self.is_disabled(WIDGET.id()))
738    }
739
740    /// Returns `true` if the [`WIDGET`] was not hovered or was enabled, but now is hovered and disabled.
741    ///
742    /// [`WIDGET`]: zng_app::widget::WIDGET
743    pub fn is_drag_enter_disabled(&self) -> bool {
744        (!self.was_over() || self.was_enabled(WIDGET.id())) && self.is_over() && self.is_disabled(WIDGET.id())
745    }
746
747    /// Returns `true` if the [`WIDGET`] was hovered and disabled, but now is not hovered or is enabled.
748    ///
749    /// [`WIDGET`]: zng_app::widget::WIDGET
750    pub fn is_drag_leave_disabled(&self) -> bool {
751        self.was_over() && self.was_disabled(WIDGET.id()) && (!self.is_over() || self.is_enabled(WIDGET.id()))
752    }
753}
754
755impl DragEndArgs {
756    /// Data was dropped on a valid target.
757    pub fn was_dropped(&self) -> bool {
758        !self.applied.is_empty()
759    }
760
761    /// Stopped dragging without dropping on a valid drop target.
762    pub fn was_canceled(&self) -> bool {
763        self.applied.is_empty()
764    }
765}
766
767/// Encode an widget ID for drag&drop data.
768pub fn encode_widget_id(id: WidgetId) -> DragDropData {
769    DragDropData::Text {
770        format: formatx!("zng/{}", APP_GUID.read().simple()),
771        data: formatx!("wgt-{}", id.get()),
772    }
773}
774
775/// Decode an widget ID from drag&drop data.
776///
777/// The ID will only decode if it was encoded by the same app instance.
778pub fn decode_widget_id(data: &DragDropData) -> Option<WidgetId> {
779    if let DragDropData::Text { format, data } = data
780        && let Some(guid) = format.strip_prefix("zng/")
781        && let Some(id) = data.strip_prefix("wgt-")
782        && guid == APP_GUID.read().simple().to_string()
783        && let Ok(id) = id.parse::<u64>()
784    {
785        return Some(WidgetId::from_raw(id));
786    }
787    None
788}
789
790app_local! {
791    static APP_GUID: uuid::Uuid = uuid::Uuid::new_v4();
792}