veridian_kernel/desktop/desktop_ext/
dnd.rs1#[cfg(feature = "alloc")]
6use alloc::vec::Vec;
7
8use super::clipboard::ClipboardMime;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum DndError {
13 NotDragging,
15 AlreadyDragging,
17 DropRejected,
19 NoMimeMatch,
21 InvalidSurface,
23 Cancelled,
25}
26
27impl core::fmt::Display for DndError {
28 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
29 match self {
30 Self::NotDragging => write!(f, "not dragging"),
31 Self::AlreadyDragging => write!(f, "already dragging"),
32 Self::DropRejected => write!(f, "drop rejected"),
33 Self::NoMimeMatch => write!(f, "no MIME match"),
34 Self::InvalidSurface => write!(f, "invalid surface"),
35 Self::Cancelled => write!(f, "drag cancelled"),
36 }
37 }
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
42pub enum DndState {
43 #[default]
45 Idle,
46 Dragging,
48 DropPending,
50 Transferring,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum DndEvent {
57 Enter { surface_id: u32, x: i32, y: i32 },
59 Leave { surface_id: u32 },
61 Motion { surface_id: u32, x: i32, y: i32 },
63 Drop { surface_id: u32, x: i32, y: i32 },
65 Cancelled,
67}
68
69#[derive(Debug, Clone, PartialEq, Eq)]
71#[cfg(feature = "alloc")]
72pub struct DragSource {
73 pub source_surface: u32,
75 pub offered_mimes: Vec<ClipboardMime>,
77 pub origin_x: i32,
79 pub origin_y: i32,
80 pub ghost_width: u32,
82 pub ghost_height: u32,
83}
84
85#[derive(Debug, Clone, PartialEq, Eq)]
87#[cfg(feature = "alloc")]
88pub struct DropTarget {
89 pub surface_id: u32,
91 pub accepted_mimes: Vec<ClipboardMime>,
93 pub x: i32,
95 pub y: i32,
96 pub width: u32,
97 pub height: u32,
98}
99
100#[cfg(feature = "alloc")]
101impl DropTarget {
102 pub fn contains(&self, px: i32, py: i32) -> bool {
104 px >= self.x
105 && px < self.x.saturating_add(self.width as i32)
106 && py >= self.y
107 && py < self.y.saturating_add(self.height as i32)
108 }
109}
110
111#[derive(Debug)]
113#[cfg(feature = "alloc")]
114pub struct DndManager {
115 state: DndState,
117 source: Option<DragSource>,
119 hover_surface: Option<u32>,
121 cursor_x: i32,
123 cursor_y: i32,
124 targets: Vec<DropTarget>,
126 events: Vec<DndEvent>,
128}
129
130#[cfg(feature = "alloc")]
131impl Default for DndManager {
132 fn default() -> Self {
133 Self::new()
134 }
135}
136
137#[cfg(feature = "alloc")]
138impl DndManager {
139 pub fn new() -> Self {
141 Self {
142 state: DndState::Idle,
143 source: None,
144 hover_surface: None,
145 cursor_x: 0,
146 cursor_y: 0,
147 targets: Vec::new(),
148 events: Vec::new(),
149 }
150 }
151
152 pub fn start_drag(
154 &mut self,
155 source_surface: u32,
156 offered_mimes: Vec<ClipboardMime>,
157 origin_x: i32,
158 origin_y: i32,
159 ghost_width: u32,
160 ghost_height: u32,
161 ) -> Result<(), DndError> {
162 if self.state != DndState::Idle {
163 return Err(DndError::AlreadyDragging);
164 }
165
166 self.source = Some(DragSource {
167 source_surface,
168 offered_mimes,
169 origin_x,
170 origin_y,
171 ghost_width,
172 ghost_height,
173 });
174 self.state = DndState::Dragging;
175 self.cursor_x = origin_x;
176 self.cursor_y = origin_y;
177
178 Ok(())
179 }
180
181 pub fn motion(&mut self, x: i32, y: i32) -> Result<(), DndError> {
184 if self.state != DndState::Dragging && self.state != DndState::DropPending {
185 return Err(DndError::NotDragging);
186 }
187
188 self.cursor_x = x;
189 self.cursor_y = y;
190
191 let hit = self.targets.iter().find(|t| t.contains(x, y));
193
194 let new_surface = hit.map(|t| t.surface_id);
195 let old_surface = self.hover_surface;
196
197 if new_surface != old_surface {
199 if let Some(old_id) = old_surface {
200 self.events.push(DndEvent::Leave { surface_id: old_id });
201 }
202 if let Some(new_id) = new_surface {
203 self.events.push(DndEvent::Enter {
204 surface_id: new_id,
205 x,
206 y,
207 });
208 self.state = DndState::DropPending;
209 } else {
210 self.state = DndState::Dragging;
211 }
212 self.hover_surface = new_surface;
213 } else if let Some(sid) = new_surface {
214 self.events.push(DndEvent::Motion {
215 surface_id: sid,
216 x,
217 y,
218 });
219 }
220
221 Ok(())
222 }
223
224 pub fn drop_action(&mut self) -> Result<DndEvent, DndError> {
226 if self.state != DndState::DropPending {
227 return Err(DndError::NotDragging);
228 }
229
230 let surface_id = self.hover_surface.ok_or(DndError::InvalidSurface)?;
231 let source = self.source.as_ref().ok_or(DndError::NotDragging)?;
232
233 let target = self
235 .targets
236 .iter()
237 .find(|t| t.surface_id == surface_id)
238 .ok_or(DndError::InvalidSurface)?;
239
240 let has_match = source
241 .offered_mimes
242 .iter()
243 .any(|m| target.accepted_mimes.contains(m));
244
245 if !has_match {
246 self.cancel();
247 return Err(DndError::NoMimeMatch);
248 }
249
250 let event = DndEvent::Drop {
251 surface_id,
252 x: self.cursor_x,
253 y: self.cursor_y,
254 };
255 self.events.push(event);
256
257 self.state = DndState::Transferring;
258
259 Ok(event)
260 }
261
262 pub fn cancel(&mut self) {
264 if let Some(sid) = self.hover_surface.take() {
265 self.events.push(DndEvent::Leave { surface_id: sid });
266 }
267 self.events.push(DndEvent::Cancelled);
268 self.source = None;
269 self.state = DndState::Idle;
270 }
271
272 pub fn finish_transfer(&mut self) {
274 self.source = None;
275 self.hover_surface = None;
276 self.state = DndState::Idle;
277 }
278
279 pub fn register_target(&mut self, target: DropTarget) {
281 self.targets.retain(|t| t.surface_id != target.surface_id);
283 self.targets.push(target);
284 }
285
286 pub fn unregister_target(&mut self, surface_id: u32) {
288 self.targets.retain(|t| t.surface_id != surface_id);
289 }
290
291 pub fn state(&self) -> DndState {
293 self.state
294 }
295
296 pub fn cursor_position(&self) -> (i32, i32) {
298 (self.cursor_x, self.cursor_y)
299 }
300
301 pub fn source(&self) -> Option<&DragSource> {
303 self.source.as_ref()
304 }
305
306 pub fn ghost_position(&self) -> Option<(i32, i32, u32, u32)> {
308 self.source.as_ref().map(|s| {
309 (
310 self.cursor_x - (s.ghost_width as i32 / 2),
311 self.cursor_y - (s.ghost_height as i32 / 2),
312 s.ghost_width,
313 s.ghost_height,
314 )
315 })
316 }
317
318 pub fn drain_events(&mut self) -> Vec<DndEvent> {
320 core::mem::take(&mut self.events)
321 }
322
323 pub fn negotiate_mime(&self, surface_id: u32) -> Option<ClipboardMime> {
325 let source = self.source.as_ref()?;
326 let target = self.targets.iter().find(|t| t.surface_id == surface_id)?;
327 source
328 .offered_mimes
329 .iter()
330 .find(|m| target.accepted_mimes.contains(m))
331 .copied()
332 }
333}