zng_ext_input/
drag_drop.rs

1#![cfg(feature = "drag_drop")]
2
3//! Drag & drop gesture events and service.
4//!
5//! # Events
6//!
7//! Events this extension provides.
8//!
9//! * [`DROP_EVENT`]
10//! * [`DRAG_HOVERED_EVENT`]
11//! * [`DRAG_MOVE_EVENT`]
12//! * [`DRAG_START_EVENT`]
13//! * [`DRAG_END_EVENT`]
14//! * [`DROP_EVENT`]
15//!
16//! # Services
17//!
18//! Services this extension provides.
19//!
20//! * [`DRAG_DROP`]
21
22use 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/// Drag & drop service.
52#[allow(non_camel_case_types)]
53pub struct DRAG_DROP;
54impl DRAG_DROP {
55    /// All data current dragging.
56    pub fn dragging_data(&self) -> Var<Vec<DragDropData>> {
57        DRAG_DROP_SV.read().data.read_only()
58    }
59
60    /// Start dragging `data`.
61    ///
62    /// This method will only work if a [`DRAG_START_EVENT`] is notifying. Handlers of draggable widgets
63    /// can provide custom drag data using this method.
64    ///
65    /// Returns a handle that can be dropped to cancel the drag operation. A [`DRAG_END_EVENT`] notifies
66    /// the draggable widget on cancel or drop. Logs an error message and returns a dummy handle on error.
67    ///
68    /// Note that the `allowed_effects` apply to all data, if a previous handler already set data with an incompatible
69    /// effect the call is an error and the data ignored.
70    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    // last cursor move position.
123    pos: DipPoint,
124    // last cursor move over window.
125    pos_window: Option<WindowId>,
126    // last cursor move hit-test (on the pos_window or a nested window).
127    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            // system drop
142            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                // mouse_move data
192                let mut frame_info = match WINDOWS.widget_tree(args.window_id) {
193                    Some(f) => f,
194                    None => {
195                        // window not found
196                        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                            // nested window hit
215                            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                // drag_enter/leave.
241                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                // mouse_move
250                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    // system drag cancelled of dragged out of all app windows
264    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                    // unhandled mouse press on draggable
320                    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                    }); // calls to DRAG_DROP.drag are now valid
331                }
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                    // unhandled touch start on draggable
345                    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                    }); // calls to DRAG_DROP.drag are now valid
356                }
357            }),
358        )
359        .perm();
360
361    DRAG_START_EVENT
362        .hook(|args| {
363            // finished notifying draggable drag start
364            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/// Represents dragging data.
404///
405/// Drop all clones of this handle to cancel the drag operation.
406#[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    /// New handle to nothing.
417    pub fn dummy() -> Self {
418        Self(Handle::dummy(()))
419    }
420
421    /// Drops the handle but does **not** cancel the drag operation.
422    ///
423    /// The drag data stays alive until the user completes or cancels the operation.
424    pub fn perm(self) {
425        self.0.perm();
426    }
427
428    /// If another handle has called [`perm`](Self::perm).
429    ///
430    /// If `true` operation will run to completion.
431    pub fn is_permanent(&self) -> bool {
432        self.0.is_permanent()
433    }
434
435    /// Drops the handle and forces operation the cancel.
436    pub fn cancel(self) {
437        self.0.force_drop()
438    }
439
440    /// If another handle has called [`cancel`](Self::cancel).
441    pub fn is_canceled(&self) -> bool {
442        self.0.is_dropped()
443    }
444
445    /// Create a weak handle.
446    pub fn downgrade(&self) -> WeakDragHandle {
447        WeakDragHandle(self.0.downgrade())
448    }
449}
450/// Weak [`DragHandle`].
451#[derive(Clone, PartialEq, Eq, Hash, Default, Debug)]
452pub struct WeakDragHandle(WeakHandle<()>);
453impl WeakDragHandle {
454    /// New weak handle that does not upgrade.
455    pub fn new() -> Self {
456        Self(WeakHandle::new())
457    }
458
459    /// Gets the strong handle if it is still subscribed.
460    pub fn upgrade(&self) -> Option<DragHandle> {
461        self.0.upgrade().map(DragHandle)
462    }
463}
464
465/// [`WidgetInfo`] extensions for drag & drop service.
466pub trait WidgetInfoDragDropExt {
467    /// If this widget can be dragged and dropped.
468    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
476/// [`WidgetInfoBuilder`] extensions for drag & drop service.
477pub trait WidgetInfoBuilderDragDropExt {
478    /// Flag the widget as draggable.
479    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    /// Arguments for [`DROP_EVENT`].
493    pub struct DropArgs {
494        /// Hovered target of the drag&drop gesture.
495        pub target: InteractionPath,
496        /// Drag&drop data payload.
497        pub data: Vec<DragDropData>,
498        /// Drop effects that the drag source allows.
499        pub allowed: DragDropEffect,
500        /// Position of the cursor in the window's content area.
501        pub position: DipPoint,
502        /// Hit-test result for the cursor point in the window.
503        pub hits: HitTestInfo,
504
505        drop_id: DragDropId,
506        applied: ArcEq<Mutex<DragDropEffect>>,
507
508        ..
509
510        /// If is in [`target`].
511        ///
512        /// [`target`]: DropArgs::target
513        fn is_in_target(&self, id: WidgetId) -> bool {
514            self.target.contains(id)
515        }
516    }
517
518    /// Arguments for [`DRAG_HOVERED_EVENT`].
519    pub struct DragHoveredArgs {
520        /// Previous hovered target.
521        pub prev_target: Option<InteractionPath>,
522        /// New hovered target.
523        pub target: Option<InteractionPath>,
524        /// Position of the cursor in the window's content area.
525        pub position: DipPoint,
526        /// Hit-test result for the cursor point in the window.
527        pub hits: HitTestInfo,
528
529        ..
530
531        /// If is in [`prev_target`] or [`target`].
532        ///
533        /// [`prev_target`]: DropArgs::prev_target
534        /// [`target`]: DropArgs::target
535        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    /// [`DRAG_MOVE_EVENT`] arguments.
551    pub struct DragMoveArgs {
552        /// Id of window that received the event.
553        pub window_id: WindowId,
554
555        /// Positions of the cursor in between the previous event and this one.
556        ///
557        /// Drag move events can be coalesced, i.e. multiple moves packed into a single event.
558        pub coalesced_pos: Vec<DipPoint>,
559
560        /// Position of the cursor in the window's content area.
561        pub position: DipPoint,
562
563        /// Hit-test result for the cursor point in the window.
564        pub hits: HitTestInfo,
565
566        /// Full path to the top-most hit in [`hits`](DragMoveArgs::hits).
567        pub target: InteractionPath,
568
569        ..
570
571        /// If is in [`target`].
572        ///
573        /// [`target`]: Self::target
574        fn is_in_target(&self, id: WidgetId) -> bool {
575            self.target.contains(id)
576        }
577    }
578
579    /// Arguments for [`DRAG_START_EVENT`].
580    pub struct DragStartArgs {
581        /// Draggable widget that has started dragging.
582        pub target: InteractionPath,
583
584        ..
585
586        /// If is in [`target`].
587        ///
588        /// [`target`]: Self::target
589        fn is_in_target(&self, id: WidgetId) -> bool {
590            self.target.contains(id)
591        }
592    }
593
594    /// Arguments for [`DRAG_END_EVENT`].
595    pub struct DragEndArgs {
596        /// Draggable widget that was dragging.
597        pub target: InteractionPath,
598
599        /// Effect applied by the drop target on the data.
600        ///
601        /// Is empty or a single flag.
602        pub applied: DragDropEffect,
603
604        ..
605
606        /// If is in [`target`].
607        ///
608        /// [`target`]: Self::target
609        fn is_in_target(&self, id: WidgetId) -> bool {
610            self.target.contains(id)
611        }
612
613        /// The `applied` field can only be empty or only have a single flag set.
614        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    /// Drag&drop action finished over some drop target widget.
624    pub static DROP_EVENT: DropArgs {
625        let _ = DRAG_DROP_SV.read();
626    };
627    /// Drag&drop enter or exit a drop target widget.
628    pub static DRAG_HOVERED_EVENT: DragHoveredArgs {
629        let _ = DRAG_DROP_SV.read();
630    };
631    /// Drag&drop is dragging over the target widget.
632    pub static DRAG_MOVE_EVENT: DragMoveArgs {
633        let _ = DRAG_DROP_SV.read();
634    };
635    /// Drag&drop started dragging a draggable widget.
636    ///
637    /// If propagation is stopped the drag operation is cancelled. Handlers can use
638    /// [`DRAG_DROP.drag`] to set the data, otherwise the widget ID will be dragged.
639    ///
640    /// [`DRAG_DROP.drag`]: DRAG_DROP::drag
641    pub static DRAG_START_EVENT: DragStartArgs {
642        let _ = DRAG_DROP_SV.read();
643    };
644
645    /// Drag&drop gesture started from the draggable widget has ended.
646    pub static DRAG_END_EVENT: DragEndArgs {
647        let _ = DRAG_DROP_SV.read();
648    };
649}
650
651impl DropArgs {
652    /// Stop propagation and set the `effect` that was applied to the data.
653    ///
654    /// Logs an error if propagation is already stopped.
655    ///
656    /// # Panics
657    ///
658    /// Panics if `effect` sets more then one flag or is not [`allowed`].
659    ///
660    /// [`allowed`]: Self::allowed
661    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    /// Gets the [`DRAG_DROP.dragging_data`].
677    ///
678    /// [`DRAG_DROP.dragging_data`]: DRAG_DROP::dragging_data
679    pub fn data(&self) -> Var<Vec<DragDropData>> {
680        DRAG_DROP.dragging_data()
681    }
682
683    /// Returns `true` if the `wgt` was not hovered, but now is.
684    pub fn is_drag_enter(&self, wgt: WidgetId) -> bool {
685        !self.was_over(wgt) && self.is_over(wgt)
686    }
687
688    /// Returns `true` if the `wgt` was hovered, but now isn't.
689    pub fn is_drag_leave(&self, wgt: WidgetId) -> bool {
690        self.was_over(wgt) && !self.is_over(wgt)
691    }
692
693    /// Returns `true` if the `wgt` is in [`prev_target`].
694    ///
695    /// [`prev_target`]: Self::prev_target
696    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    /// Returns `true` if the `wgt` is in [`target`].
704    ///
705    /// [`target`]: Self::target
706    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    /// Returns `true` if the widget was enabled in [`prev_target`].
714    ///
715    /// [`prev_target`]: Self::prev_target
716    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    /// Returns `true` if the widget was disabled in [`prev_target`].
724    ///
725    /// [`prev_target`]: Self::prev_target
726    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    /// Returns `true` if the widget is enabled in [`target`].
734    ///
735    /// [`target`]: Self::target
736    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    /// Returns `true` if the widget is disabled in [`target`].
744    ///
745    /// [`target`]: Self::target
746    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    /// Returns `true` if the `wgt` was not hovered or was disabled, but now is hovered and enabled.
754    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    /// Returns `true` if the `wgt` was hovered and enabled, but now is not hovered or is disabled.
759    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    /// Returns `true` if the `wgt` was not hovered or was enabled, but now is hovered and disabled.
764    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    /// Returns `true` if the `wgt` was hovered and disabled, but now is not hovered or is enabled.
769    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    /// Data was dropped on a valid target.
776    pub fn was_dropped(&self) -> bool {
777        !self.applied.is_empty()
778    }
779
780    /// Stopped dragging without dropping on a valid drop target.
781    pub fn was_canceled(&self) -> bool {
782        self.applied.is_empty()
783    }
784}
785
786/// Encode an widget ID for drag&drop data.
787pub 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
794/// Decode an widget ID from drag&drop data.
795///
796/// The ID will only decode if it was encoded by the same app instance.
797pub 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}