ailloli_ui_core/event/file.rs
1//! Ordered, provider-neutral file hover and drop payloads.
2
3use crate::{Point, UploadFile};
4
5/// Platform-neutral file hover/drop event.
6///
7/// Current variants are `Entered`, `Moved`, `Left`, and `Dropped`; `Hover`,
8/// `HoverCancelled`, and `Drop` remain compatibility variants.
9///
10/// # Examples
11///
12/// ```
13/// use ailloli_ui_core::{event::FileEvent, Point, UploadFile};
14/// let event = FileEvent::dropped(Some(Point::new(2.0, 3.0)), [UploadFile::named("a.txt")]);
15/// assert_eq!(event.files()[0].name, "a.txt");
16/// ```
17#[non_exhaustive]
18#[derive(Debug, Clone, PartialEq)]
19pub enum FileEvent {
20 /// A batch of files entered the logical window.
21 Entered {
22 /// Logical pointer position when supplied by the provider.
23 pos: Option<Point>,
24 /// Files in provider order; the batch may be empty.
25 files: Vec<UploadFile>,
26 },
27 /// A hovering batch moved within the logical window.
28 Moved {
29 /// Logical pointer position when supplied by the provider.
30 pos: Option<Point>,
31 /// Current files in provider order; the batch may be empty.
32 files: Vec<UploadFile>,
33 },
34 /// The current file batch left the logical window.
35 Left,
36 /// A batch of files was dropped in the logical window.
37 Dropped {
38 /// Logical pointer position when supplied by the provider.
39 pos: Option<Point>,
40 /// Dropped files in provider order; the batch may be empty.
41 files: Vec<UploadFile>,
42 },
43 /// One or more files are hovering over the window at logical `pos`.
44 ///
45 /// New adapters should use [`FileEvent::Entered`] and [`FileEvent::Moved`].
46 Hover {
47 /// Logical pointer position reported by the legacy adapter.
48 pos: Point,
49 /// Hovering files in provider order.
50 files: Vec<UploadFile>,
51 },
52 /// File hover was cancelled by the platform.
53 ///
54 /// New adapters should use [`FileEvent::Left`].
55 HoverCancelled,
56 /// One or more files were dropped at logical `pos`.
57 ///
58 /// New adapters should use [`FileEvent::Dropped`].
59 Drop {
60 /// Logical pointer position reported by the legacy adapter.
61 pos: Point,
62 /// Dropped files in provider order.
63 files: Vec<UploadFile>,
64 },
65}
66
67impl FileEvent {
68 /// Creates an ordered batch that entered the logical window.
69 ///
70 /// `None` preserves that the provider did not supply a pointer position;
71 /// it is not replaced with an origin or stale coordinate.
72 ///
73 /// # Examples
74 ///
75 /// ```
76 /// use ailloli_ui_core::{event::FileEvent, UploadFile};
77 /// let event = FileEvent::entered(None, [UploadFile::named("a.txt")]);
78 /// assert_eq!(event.pos(), None);
79 /// ```
80 pub fn entered(pos: Option<Point>, files: impl IntoIterator<Item = UploadFile>) -> Self {
81 Self::Entered {
82 pos,
83 files: files.into_iter().collect(),
84 }
85 }
86
87 /// Creates an ordered hovering batch movement.
88 ///
89 /// # Examples
90 ///
91 /// ```
92 /// use ailloli_ui_core::{event::FileEvent, Point, UploadFile};
93 /// let event = FileEvent::moved(Some(Point::new(1.0, 2.0)), [UploadFile::named("a.txt")]);
94 /// assert_eq!(event.pos(), Some(Point::new(1.0, 2.0)));
95 /// ```
96 pub fn moved(pos: Option<Point>, files: impl IntoIterator<Item = UploadFile>) -> Self {
97 Self::Moved {
98 pos,
99 files: files.into_iter().collect(),
100 }
101 }
102
103 /// Creates a notification that the current batch left the window.
104 ///
105 /// Leave events carry neither files nor a position.
106 ///
107 /// # Examples
108 ///
109 /// ```
110 /// use ailloli_ui_core::event::FileEvent;
111 /// assert!(FileEvent::left().is_left());
112 /// ```
113 pub const fn left() -> Self {
114 Self::Left
115 }
116
117 /// Creates an ordered batch dropped in the logical window.
118 ///
119 /// # Examples
120 ///
121 /// ```
122 /// use ailloli_ui_core::{event::FileEvent, UploadFile};
123 /// assert_eq!(FileEvent::dropped(None, [UploadFile::named("a.txt")]).files().len(), 1);
124 /// ```
125 pub fn dropped(pos: Option<Point>, files: impl IntoIterator<Item = UploadFile>) -> Self {
126 Self::Dropped {
127 pos,
128 files: files.into_iter().collect(),
129 }
130 }
131
132 /// Logical pointer position supplied by the provider, when known.
133 ///
134 /// # Examples
135 ///
136 /// ```
137 /// use ailloli_ui_core::{event::FileEvent, Point, UploadFile};
138 /// let event = FileEvent::entered(Some(Point::new(3.0, 4.0)), [UploadFile::named("a")]);
139 /// assert_eq!(event.pos(), Some(Point::new(3.0, 4.0)));
140 /// ```
141 pub fn pos(&self) -> Option<Point> {
142 match self {
143 Self::Entered { pos, .. } | Self::Moved { pos, .. } | Self::Dropped { pos, .. } => *pos,
144 Self::Hover { pos, .. } | Self::Drop { pos, .. } => Some(*pos),
145 Self::Left | Self::HoverCancelled => None,
146 }
147 }
148
149 /// Ordered files carried by the event, or an empty slice for leave events.
150 ///
151 /// # Examples
152 ///
153 /// ```
154 /// use ailloli_ui_core::event::FileEvent;
155 /// assert!(FileEvent::left().files().is_empty());
156 /// ```
157 pub fn files(&self) -> &[UploadFile] {
158 match self {
159 Self::Entered { files, .. }
160 | Self::Moved { files, .. }
161 | Self::Dropped { files, .. }
162 | Self::Hover { files, .. }
163 | Self::Drop { files, .. } => files,
164 Self::Left | Self::HoverCancelled => &[],
165 }
166 }
167
168 /// Returns `true` for both current and legacy leave variants.
169 ///
170 /// # Examples
171 ///
172 /// ```
173 /// use ailloli_ui_core::event::FileEvent;
174 /// assert!(FileEvent::left().is_left());
175 /// ```
176 pub const fn is_left(&self) -> bool {
177 matches!(self, Self::Left | Self::HoverCancelled)
178 }
179}