1use alloc::string::String;
40
41use super::consts::*;
42use super::keys::*;
43use super::{Sp, Ui};
44use plat::{DE_ALL, DE_COPY, DE_MOVE, DT_FILES, DT_STD, DragData, Event};
45
46const PH_NONE: u8 = 0;
47const PH_START: u8 = 1; const PH_IN: u8 = 2; const PH_OUT: u8 = 3; const LABEL_MAX: usize = 48; #[derive(Clone, Copy, Default)]
54pub(crate) struct Zone {
55 pub(crate) id: u32,
56 pub(crate) types: u32,
57 pub(crate) layer: u8,
58 pub(crate) x0: i32,
59 pub(crate) y0: i32,
60 pub(crate) x1: i32,
61 pub(crate) y1: i32,
62}
63
64pub(crate) fn zone_find(zones: &[Zone], hits: &[super::ctx::Hit], modal: u32, x: i32, y: i32, types: u32) -> Option<Zone> {
67 if types == 0 || (x == -1 && y == -1) {
68 return None;
69 }
70 let inside = |x0: i32, y0: i32, x1: i32, y1: i32| x >= x0 && x < x1 && y >= y0 && y < y1;
71 let top = hits.iter().filter(|h| inside(h.x0, h.y0, h.x1, h.y1)).map(|h| h.layer as u32).max().unwrap_or(0).max(modal);
73 let area = |z: &Zone| (z.x1 - z.x0) as i64 * (z.y1 - z.y0) as i64;
74 let mut best: Option<Zone> = None;
75 for z in zones.iter().filter(|z| (z.layer as u32) >= top && z.types & types != 0 && inside(z.x0, z.y0, z.x1, z.y1)) {
76 let better = match &best {
77 None => true,
78 Some(b) => z.layer > b.layer || (z.layer == b.layer && area(z) <= area(b)),
79 };
80 if better {
81 best = Some(*z);
82 }
83 }
84 best
85}
86
87pub(crate) struct S {
89 phase: u8,
90 src: u32,
91 src_win: u32,
92 data: DragData,
94 end_src: u32,
96 end_win: u32,
97 end_live: bool,
98 last_effect: u32,
99 ext: bool,
101 ext_win: u32,
102 ext_x: i32,
103 ext_y: i32,
104 ext_types: u32,
105 over: u32,
108 over_win: u32,
109 hl: Option<(u32, u8, [i32; 4])>,
110 drop_id: u32,
112 drop_win: u32,
113 drop_live: bool,
114 pub(crate) drop_effect: u32,
115 pub(crate) dropped: DragData,
117 pub(crate) accept: u32,
119 pub(crate) out: Option<DragData>,
121}
122
123impl Default for S {
124 fn default() -> S {
125 S {
126 phase: PH_NONE,
127 src: 0,
128 src_win: 0,
129 data: DragData::default(),
130 end_src: 0,
131 end_win: 0,
132 end_live: false,
133 last_effect: 0,
134 ext: false,
135 ext_win: 0,
136 ext_x: 0,
137 ext_y: 0,
138 ext_types: 0,
139 over: 0,
140 over_win: 0,
141 hl: None,
142 drop_id: 0,
143 drop_win: 0,
144 drop_live: false,
145 drop_effect: 0,
146 dropped: DragData::default(),
147 accept: DT_FILES,
148 out: None,
149 }
150 }
151}
152
153fn label_of(d: &DragData) -> String {
155 let cut = |s: &str| -> String {
156 let line = s.lines().find(|l| !l.trim().is_empty()).unwrap_or("").trim();
157 let mut out: String = line.chars().take(LABEL_MAX).collect();
158 if line.chars().count() > LABEL_MAX {
159 out.push('…');
160 }
161 out
162 };
163 if !d.label.is_empty() {
164 return cut(&d.label);
165 }
166 if let Some(t) = &d.text {
167 return cut(t);
168 }
169 if let Some(f) = d.files.first() {
170 let name = f.rsplit(['/', '\\']).find(|s| !s.is_empty()).unwrap_or(f);
171 let mut s = cut(name);
172 if d.files.len() > 1 {
173 s.push_str(&alloc::format!(" +{}", d.files.len() - 1));
174 }
175 return s;
176 }
177 if let Some(u) = d.urls.first() {
178 return cut(u);
179 }
180 String::new()
181}
182
183impl Ui {
184 pub fn drag_source(&mut self, id: u32, ev: u32) -> u32 {
190 let cur = self.uiw_cur;
191 let d = &self.dnd;
192 if id == 0 {
193 return 0;
194 }
195 if d.end_live && d.end_src == id && d.end_win == cur {
196 return DS_END;
197 }
198 if d.phase != PH_NONE && d.src == id && d.src_win == cur {
199 return if d.phase == PH_START { DS_START } else { DS_ACTIVE };
200 }
201 if d.phase == PH_NONE && ev & EV_DRAG != 0 && self.wp.ctx.active == id && self.wp.ctx.mbtn & (1 << BTN_LEFT) != 0 {
202 let d = &mut self.dnd;
203 d.phase = PH_START;
204 d.src = id;
205 d.src_win = cur;
206 d.data = DragData::default();
207 self.wp.ctx.active = 0; return DS_START;
209 }
210 0
211 }
212
213 pub fn drag_set(&mut self, data: DragData) {
215 let d = &mut self.dnd;
216 if d.phase == PH_START && d.src_win == self.uiw_cur && !data.is_empty() {
217 d.data = data;
218 d.phase = PH_IN;
219 self.repass = true; }
221 }
222
223 pub fn drag_out(&mut self, data: DragData) -> bool {
226 if self.dnd.phase != PH_NONE || data.types() & DT_STD == 0 {
227 return false;
228 }
229 self.dnd.src = 0;
230 self.dnd.src_win = self.uiw_cur;
231 self.dnd.data = data;
232 self.dnd_go_out()
233 }
234
235 pub fn drag_effect(&self) -> u32 {
237 self.dnd.last_effect
238 }
239
240 pub fn dragging(&self) -> bool {
242 self.dnd.phase >= PH_IN
243 }
244
245 fn dnd_go_out(&mut self) -> bool {
247 let data = self.dnd.data.clone();
248 let ok = if self.headless || self.hr_outside("a drag out of the app") {
250 self.dnd.out = Some(data);
251 true
252 } else {
253 plat::drag_start(data)
254 };
255 if ok {
256 self.dnd.phase = PH_OUT;
257 self.dnd_publish();
258 }
259 ok
260 }
261
262 fn dnd_end(&mut self, effect: u32) {
264 let d = &mut self.dnd;
265 d.end_src = d.src;
266 d.end_win = d.src_win;
267 d.end_live = d.src != 0;
268 d.last_effect = effect;
269 d.phase = PH_NONE;
270 d.src = 0;
271 d.data = DragData::default();
272 if d.over_win == d.src_win {
273 d.over = 0;
274 }
275 self.dnd_publish();
276 self.cmd_run(UIC_DRAG_END, effect);
277 self.repass = true;
278 }
279
280 pub fn drop_target(&mut self, id: u32, types: u32) -> u32 {
286 if id == 0 || types == 0 {
287 return 0;
288 }
289 let c = &mut self.wp.ctx;
290 if c.disabled_depth != 0 {
291 return 0;
292 }
293 let r = c.last_rect;
294 let k = c.uiclip;
295 let cur = c.hit_cur;
296 let mut z = Zone {
297 id,
298 types,
299 layer: c.cmd_layer_ui as u8,
300 x0: r[0].max(k[0]),
301 y0: r[1].max(k[1]),
302 x1: r[0].wrapping_add(r[2]).min(k[2]),
303 y1: r[1].wrapping_add(r[3]).min(k[3]),
304 };
305 let full = c.zones[cur].len() >= super::ctx::HITS_MAX;
306 if z.x1 > z.x0 && z.y1 > z.y0 && !full {
307 c.zones[cur].push(z);
308 } else {
309 z.x1 = z.x0; if full {
311 self.limit_hit(super::ctx::LIM_ZONES);
312 }
313 }
314 let win = self.uiw_cur;
315 let d = &self.dnd;
316 if d.drop_live && d.drop_win == win && d.drop_id == id {
317 return DS_DROP;
318 }
319 if d.over == id && d.over_win == win && self.dnd_types_in(win) & types != 0 {
320 self.dnd.hl = Some((win, z.layer, [z.x0, z.y0, z.x1 - z.x0, z.y1 - z.y0]));
321 return DS_HOVER;
322 }
323 0
324 }
325
326 pub fn drop_accept(&mut self, types: u32) {
329 if self.dnd.accept != types {
330 self.dnd.accept = types;
331 self.dnd_publish();
332 }
333 }
334
335 pub fn drag_over(&self) -> (u32, i32, i32) {
337 let win = self.uiw_cur;
338 let d = &self.dnd;
339 if d.phase == PH_IN && d.src_win == win {
340 let c = &self.wp.ctx;
341 return (d.data.types(), self.px2dip(c.mx), self.px2dip(c.my));
342 }
343 if d.ext && d.ext_win == win {
344 return (d.ext_types, self.px2dip(d.ext_x), self.px2dip(d.ext_y));
345 }
346 (0, 0, 0)
347 }
348
349 pub fn drop_effect(&self) -> u32 {
351 self.dnd.drop_effect
352 }
353
354 fn dnd_types_in(&self, win: u32) -> u32 {
356 let d = &self.dnd;
357 if d.phase == PH_IN && d.src_win == win {
358 d.data.types()
359 } else if d.ext && d.ext_win == win {
360 d.ext_types
361 } else {
362 0
363 }
364 }
365
366 pub(crate) fn dnd_pass_begin(&mut self) {
370 let win = self.uiw_cur;
371 if self.dnd.hl.is_some_and(|h| h.0 == win) {
372 self.dnd.hl = None;
373 }
374 let d = &self.dnd;
375 let at = if d.phase == PH_IN && d.src_win == win {
376 Some((self.wp.ctx.mx, self.wp.ctx.my, d.data.types()))
377 } else if d.ext && d.ext_win == win {
378 Some((d.ext_x, d.ext_y, d.ext_types))
379 } else {
380 None
381 };
382 if let Some((x, y, types)) = at {
383 let c = &self.wp.ctx;
384 let z = if self.uiw_inert_now() { None } else { zone_find(&c.zones[c.hit_cur ^ 1], &c.hits[c.hit_cur ^ 1], c.modal_prev, x, y, types) };
385 self.dnd.over = z.map_or(0, |z| z.id);
386 self.dnd.over_win = win;
387 } else if self.dnd.over_win == win {
388 self.dnd.over = 0;
389 }
390 }
391
392 pub(crate) fn dnd_pass_end(&mut self) {
395 if let Some((w, layer, r)) = self.dnd.hl {
396 if w == self.uiw_cur && r[2] > 0 {
397 let prev = self.layer_set(layer as u32);
398 let st = self.style;
399 st.drop_highlight(self, r);
400 self.layer_set(prev);
401 }
402 }
403 let d = &self.dnd;
404 if d.phase != PH_IN || d.src_win != self.uiw_cur {
405 return;
406 }
407 let s = label_of(&d.data);
408 if s.is_empty() {
409 return;
410 }
411 let (mx, my) = (self.wp.ctx.mx, self.wp.ctx.my);
412 if mx == -1 && my == -1 {
413 return;
414 }
415 let lr = self.wp.ctx.last_rect;
416 let prev = self.layer_set(L_TIP);
417 let st = self.style;
418 let mut sp = Sp { label: Some(&s), ..Sp::default() };
419 let (w, h) = st.tooltip_size(self, &mut sp);
420 let (fw, fh) = (self.wp.rt.fb.w, self.wp.rt.fb.h);
422 let gap = self.dp(12);
423 let x = (mx + gap).min(fw - w).max(0);
424 let y = (my + gap).min(fh - h).max(0);
425 sp.place([x, y, w, h]);
426 st.tooltip(self, &sp);
427 self.layer_set(prev);
428 self.wp.ctx.last_rect = lr;
429 }
430
431 pub(crate) fn dnd_clear_edges(&mut self) {
433 self.dnd.drop_live = false;
434 self.dnd.end_live = false;
435 }
436
437 pub(crate) fn dnd_after_passes(&mut self) {
440 if self.dnd.phase == PH_START {
441 self.dnd.phase = PH_NONE;
442 self.dnd.src = 0;
443 }
444 if self.dnd.phase == PH_IN {
445 let (cur, w) = (self.uiw_cur, self.dnd.src_win);
446 self.uiw_use(w);
447 if self.wp.ctx.mbtn & (1 << BTN_LEFT) == 0 {
448 self.dnd_end(0); } else {
450 self.dnd_moved();
451 }
452 self.uiw_use(cur);
453 }
454 }
455
456 pub(crate) fn dnd_follows_pointer(&self) -> bool {
458 self.dnd.phase == PH_IN && self.dnd.src_win == self.uiw_cur
459 }
460
461 pub(crate) fn dnd_moved(&mut self) {
464 if !self.dnd_follows_pointer() || self.dnd.data.types() & DT_STD == 0 {
465 return;
466 }
467 let c = &self.wp.ctx;
468 let (x, y) = (c.mx, c.my);
469 let fb = &self.wp.rt.fb;
470 if x >= 0 && y >= 0 && x < fb.w && y < fb.h {
471 return;
472 }
473 if c.mbtn & (1 << BTN_LEFT) == 0 {
474 return;
475 }
476 if self.dnd_go_out() {
477 self.dnd.over = 0;
478 self.repass = true;
479 }
480 }
481
482 pub(crate) fn dnd_input(&mut self, ev: &Event) -> bool {
485 let d = &self.dnd;
486 if d.src_win != self.uiw_cur || !(d.phase == PH_START || d.phase == PH_IN) {
487 return false;
488 }
489 match ev.typ {
490 plat::E_KEY_DOWN if ev.data & 0xFFFF == VK_ESCAPE => {
491 self.dnd_end(0); true
493 }
494 plat::E_UP if ev.btn == BTN_LEFT as u8 => {
495 if self.dnd.phase == PH_START {
496 self.dnd_after_passes();
497 return false;
498 }
499 let (x, y) = if ev.x != -1 { (ev.x, ev.y) } else { (self.wp.ctx.mx, self.wp.ctx.my) };
501 let c = &self.wp.ctx;
502 let types = self.dnd.data.types();
503 let z = zone_find(&c.zones[c.hit_cur ^ 1], &c.hits[c.hit_cur ^ 1], c.modal_prev, x, y, types);
504 let mut effect = 0;
505 if let Some(z) = z {
506 let a = self.dnd.data.allowed();
508 effect = if self.mods & (MOD_CTRL | MOD_SHIFT) == 0 && a & DE_MOVE != 0 {
509 DE_MOVE
510 } else {
511 plat::drag_effect_pick(a, self.mods as u8)
512 };
513 let win = self.uiw_cur;
514 let mut data = self.dnd.data.clone();
515 data.effects = effect;
516 self.dnd_deliver(z.id, win, x, y, data);
517 }
518 self.dnd_end(effect);
519 false }
521 _ => false,
522 }
523 }
524
525 fn dnd_deliver(&mut self, id: u32, win: u32, x: i32, y: i32, data: DragData) {
527 let files = data.files.clone();
528 self.dr.list = files;
529 self.dr.win = self.uiw_rec_id(win);
530 self.dr.x = self.px2dip_win(win, x);
531 self.dr.y = self.px2dip_win(win, y);
532 let d = &mut self.dnd;
533 d.drop_effect = data.effects;
534 d.dropped = data;
535 d.drop_id = id;
536 d.drop_win = win;
537 d.drop_live = true;
538 self.repass = true;
539 }
540
541 fn px2dip_win(&mut self, win: u32, v: i32) -> i32 {
543 let cur = self.uiw_cur;
544 self.uiw_use(win);
545 let r = self.px2dip(v);
546 self.uiw_use(cur);
547 r
548 }
549
550 pub(crate) fn dnd_hover(&mut self, ev: &Event) {
553 let win = ev.win as u32;
554 if ev.data == 0 {
555 if self.dnd.ext_win == win {
556 self.dnd.ext = false;
557 }
558 return;
559 }
560 let mut types = ev.data & DT_STD;
561 if self.dnd.phase == PH_OUT {
562 types |= self.dnd.data.types(); }
564 let cur = self.uiw_cur;
565 self.uiw_use(win);
566 let x = if self.rtl { self.wp.rt.fb.w - 1 - ev.x } else { ev.x }; self.uiw_use(cur);
568 let d = &mut self.dnd;
569 d.ext = true;
570 d.ext_win = win;
571 d.ext_x = x;
572 d.ext_y = ev.y;
573 d.ext_types = types;
574 }
575
576 pub(crate) fn dnd_dropped(&mut self, win: u32, x: i32, y: i32, mut data: DragData) -> bool {
579 self.dnd.ext = false;
580 if self.dnd.phase == PH_OUT {
581 let effect = data.effects;
583 data = self.dnd.data.clone();
584 data.effects = effect;
585 }
586 if data.effects & DE_ALL == 0 || (data.effects == DE_MOVE && !data.files.is_empty()) {
587 data.effects = DE_COPY; }
589 let types = data.types();
590 let cur = self.uiw_cur;
591 self.uiw_use(win);
592 let c = &self.wp.ctx;
593 let z = if self.uiw_inert_now() { None } else { zone_find(&c.zones[c.hit_cur ^ 1], &c.hits[c.hit_cur ^ 1], c.modal_prev, x, y, types) };
594 self.uiw_use(cur);
595 self.dnd.drop_effect = data.effects;
596 match z {
597 Some(z) => {
598 self.dnd_deliver(z.id, win, x, y, data);
599 true
600 }
601 None => {
602 self.dnd.dropped = data;
603 false
604 }
605 }
606 }
607
608 pub(crate) fn dnd_out_end(&mut self, effect: u32) {
610 if self.dnd.phase == PH_OUT {
611 self.dnd.out = None;
612 self.dnd_end(effect & DE_ALL);
613 }
614 }
615
616 pub(crate) fn dnd_out_data(&self) -> Option<&DragData> {
618 if self.dnd.phase == PH_OUT { self.dnd.out.as_ref() } else { None }
619 }
620
621 fn dnd_publish(&self) {
623 let own = if self.dnd.phase == PH_OUT { self.dnd.data.types() } else { 0 };
624 super::win::uiw_publish_dnd(self.dnd.accept, own);
625 }
626}