1use alloc::vec::Vec;
54
55use super::consts::*;
56use super::keys::*;
57use super::{Sp, Ui};
58
59const SCROLL_MAX: usize = 8;
62pub(crate) const SCR_F_LIST: u32 = 0x4000_0000; const LIST_MAX: usize = 4;
64const EXP_MAX: usize = 16;
65const TABS_INLINE: usize = 32;
67const WHEEL_DIP: i32 = 48;
68
69#[derive(Default)]
71struct Tw {
72 inline: [i32; TABS_INLINE],
73 heap: Vec<i32>,
74}
75
76impl Tw {
77 fn get(&mut self, n: usize) -> &mut [i32] {
79 if n <= TABS_INLINE {
80 &mut self.inline[..n]
81 } else {
82 self.heap.resize(n, 0);
83 &mut self.heap[..]
84 }
85 }
86}
87
88fn tabs_fit(ws: &mut [i32], room: i32, min: i32) {
91 let mut sorted = ws.to_vec();
92 sorted.sort_unstable();
93 let (mut rest, mut cap) = (room, i32::MAX);
94 for (k, &w) in sorted.iter().enumerate() {
95 let left = (sorted.len() - k) as i32;
96 if w.saturating_mul(left) > rest {
97 cap = rest / left;
98 break;
99 }
100 rest -= w;
101 }
102 let cap = cap.max(min);
103 for w in ws.iter_mut() {
104 *w = (*w).min(cap);
105 }
106}
107
108#[derive(Clone, Copy, Default)]
117pub(crate) struct Scr {
118 id: u32,
119 flags: u32,
120 rect: [i32; 4],
122 hit0: usize,
124 sem0: usize,
126 layer: u32,
127 ev: u32,
129 copy: bool,
131}
132
133#[derive(Clone, Copy, Default)]
135pub(crate) struct Ls {
136 id: u32,
137 count: i32,
138 sel: i32,
140 ih: i32,
142 next: i32,
144 old: i32,
146 ev: u32,
147 vev: u32,
149 y0: i32,
151 flags: u32,
153 drop: i32,
155 kdbl: bool,
157 ktog: bool,
158 notab: bool,
160}
161
162#[derive(Default)]
164pub(crate) struct S {}
165
166#[derive(Default)]
169pub(crate) struct W {
170 scr: [Scr; SCROLL_MAX + 1],
172 list: [Ls; LIST_MAX + 1],
173 exp: [u32; EXP_MAX],
174 scr_sp: u32,
175 list_sp: u32,
176 exp_sp: u32,
177 ldrag_list: u32,
179 ldrag_from: i32,
180 ldrag_to: i32,
182 ldrag_t: u32,
184}
185
186fn clamp_mem(v: i32, max: i32) -> (i32, bool) {
188 let mut x = if v < 0 { 0 } else { v };
189 if x > max {
190 x = max;
191 }
192 (x, x != v)
193}
194
195pub(crate) fn anim_decay(cur: i32, target: i32, steps: u32) -> i32 {
198 if steps == 0 {
199 return cur;
200 }
201 let d = target.wrapping_sub(cur);
202 let sign = d >> 31;
203 let mut rem = ((d ^ sign).wrapping_sub(sign)) as u32 as u64; for _ in 0..steps {
205 rem = rem * 150 >> 8;
206 }
207 let mut r = rem as u32;
208 if r <= 1 {
209 r = 0;
210 }
211 let r = ((r as i32) ^ sign).wrapping_sub(sign);
212 target.wrapping_sub(r)
213}
214
215fn thumb_pos(pos: i32, free: i32, range: i32) -> i32 {
217 if range <= 0 || free <= 0 {
218 return 0;
219 }
220 ((pos as u32 as u64 * free as u32 as u64) / range as u32 as u64) as u32 as i32
221}
222
223pub(crate) fn into_view(pos: i32, size: i32, vpos: i32, vsize: i32) -> i32 {
226 let start = pos.wrapping_sub(vpos);
227 if start < 0 {
228 return start;
229 }
230 let end = pos.wrapping_add(size).wrapping_sub(vpos).wrapping_sub(vsize);
231 if end <= 0 {
232 return 0;
233 }
234 if end <= start { end } else { start }
235}
236
237fn scr_axes(flags: u32) -> u32 {
239 if flags & SC_H != 0 {
240 if flags & SC_V != 0 { 3 } else { 2 }
241 } else {
242 1
243 }
244}
245
246pub(crate) fn anim_steps(now: u32, t0: &mut i32) -> u32 {
248 let d = (now as i32).wrapping_sub(*t0);
249 if d <= 0 {
250 return 0;
251 }
252 *t0 = now as i32;
253 let mut n = (d as u32).wrapping_add(8) >> 4;
254 if n == 0 {
255 n = 1;
256 }
257 n.min(24)
258}
259
260impl Ui {
261 pub(crate) fn scr_top(&self) -> usize {
265 let i = self.wp.list.scr_sp.wrapping_sub(1) as usize;
266 if i < SCROLL_MAX { i } else { SCROLL_MAX }
267 }
268
269 fn scr_max(&mut self, e: &Scr) -> (i32, i32) {
271 let s = *self.slot(e.id);
272 let mut my = 0;
273 let mut mx = 0;
274 if e.flags & SC_H == 0 || e.flags & SC_V != 0 {
275 my = s.meash.wrapping_sub(e.rect[3]).max(0);
276 }
277 if e.flags & SC_H != 0 {
278 mx = s.measw.wrapping_sub(e.rect[2]).max(0);
279 }
280 (mx, my)
281 }
282
283 pub(crate) fn scr_open(&mut self, id: u32, flags: u32, hbflags: u32) -> u32 {
287 let axes = scr_axes(flags);
288 let mut bf = (flags & 0x00FF_FFFF & !BOX_V) | BOX_CLIP;
289 if axes != 2 {
290 bf |= BOX_V; }
292 self.box_begin(id, bf, 0);
293 let st = self.style;
297 let room = st.scrollbar_room(self);
298 if room > 0 && axes == 1 {
299 if let Some(b) = self.wp.lay.boxes.last_mut().filter(|b| b.id == id) {
300 b.len[0] = b.len[0].wrapping_sub(room).max(0);
301 b.gutter = room;
302 }
303 }
304 let i = (self.wp.list.scr_sp as usize).min(SCROLL_MAX);
306 if i == SCROLL_MAX {
307 self.limit_hit(super::ctx::LIM_SCROLLS);
308 }
309 self.wp.list.scr_sp = self.wp.list.scr_sp.wrapping_add(1);
310 let c = &self.wp.ctx;
311 let mut e = Scr {
312 id,
313 flags,
314 rect: c.last_rect,
315 layer: c.cmd_layer_ui,
316 sem0: self.sems.len(),
317 hit0: c.hits[c.hit_cur].len(),
318 ev: 0,
319 copy: false,
320 };
321 self.slot(id);
322 if hbflags & HB_FOCUS != 0 {
324 e.ev = self.behave(id, hbflags);
325 } else {
326 self.hit(id, hbflags);
327 }
328 self.wp.list.scr[i] = e;
329 e.ev
330 }
331
332 fn scr_settle(&mut self, e: &Scr) -> bool {
335 let (mx, my) = self.scr_max(e);
336 let now = self.now_ms;
337 let anim_off = self.anim_off;
338 let s = self.slot(e.id);
339 if s.measw | s.meash == 0 {
341 return false;
342 }
343 s.at0 = clamp_mem(s.at0 as i32, mx).0 as u32;
344 s.adur = clamp_mem(s.adur as i32, my).0 as u32;
345 let (tx, ty) = (s.at0 as i32, s.adur as i32);
346 let mut animating = false;
347 if s.scrollx != tx || s.scrolly != ty {
348 if anim_off {
349 s.scrollx = tx;
350 s.scrolly = ty;
351 } else {
352 let steps = anim_steps(now, &mut s.aux);
353 s.scrollx = anim_decay(s.scrollx, tx, steps);
354 s.scrolly = anim_decay(s.scrolly, ty, steps);
355 if s.scrollx != tx || s.scrolly != ty {
356 animating = true;
357 }
358 }
359 }
360 let (x, cx) = clamp_mem(s.scrollx, mx);
361 let (y, cy) = clamp_mem(s.scrolly, my);
362 s.scrollx = x;
363 s.scrolly = y;
364 if animating {
365 self.animating = true;
366 }
367 cx || cy
368 }
369
370 pub(crate) fn scr_offset(&mut self) {
373 let top = self.scr_top();
374 let e = self.wp.list.scr[top];
375 self.scr_settle(&e);
376 let s = *self.slot(e.id);
377 self.box_offset(s.scrollx, s.scrolly);
378 let (mx, my) = self.scr_max(&e);
379 if mx | my != 0 {
380 self.wp.list.scr[top].copy = true;
381 self.scroll_copy_begin(e.id, e.rect[0].wrapping_sub(s.scrollx) as i64, e.rect[1].wrapping_sub(s.scrolly) as i64);
382 }
383 }
384
385 fn scr_follow(&mut self, e: &Scr) {
387 let f = self.wp.ctx.focus;
388 let s = self.slot(e.id);
389 if f == (s.afrom as u32 | (s.ato as u32) << 16) {
390 return;
391 }
392 s.afrom = f as u16;
393 s.ato = (f >> 16) as u16;
394 if f == 0 || !self.wp.ctx.focus_visible || f == e.id {
395 return;
396 }
397 self.scr_show(e, f);
398 }
399
400 fn scr_show(&mut self, e: &Scr, id: u32) {
402 let Some(r) = self.sems.get(e.sem0..).and_then(|v| v.iter().rev().find(|r| r.id == id)).map(|r| self.phys_rect(r.rect)) else {
404 return;
405 };
406 let axes = scr_axes(e.flags);
407 let m = self.dp(8); let (mx, my) = self.scr_max(e);
411 let measured = {
412 let s = self.slot(e.id);
413 s.measw | s.meash != 0
414 };
415 if axes & 1 != 0 {
416 let d = into_view(r[1].wrapping_sub(m), r[3].wrapping_add(m * 2), e.rect[1], e.rect[3]);
417 let s = self.slot(e.id);
418 let to = s.scrolly.wrapping_add(d);
419 let to = if measured { to.clamp(0, my) } else { to };
420 if to != s.scrolly {
421 s.scrolly = to;
422 s.adur = to as u32;
423 self.repass();
424 }
425 }
426 if axes & 2 != 0 {
427 let d = into_view(r[0].wrapping_sub(m), r[2].wrapping_add(m * 2), e.rect[0], e.rect[2]);
428 let s = self.slot(e.id);
429 let to = s.scrollx.wrapping_add(d);
430 let to = if measured { to.clamp(0, mx) } else { to };
431 if to != s.scrollx {
432 s.scrollx = to;
433 s.at0 = to as u32;
434 self.repass();
435 }
436 }
437 }
438
439 fn scr_bars(&mut self, e: &Scr) {
441 let st = self.style;
442 let t = st.scrollbar_thickness(self); let axes = scr_axes(e.flags);
444 let s = *self.slot(e.id);
445 let vneed = axes & 1 != 0 && s.meash > e.rect[3];
446 let hneed = axes & 2 != 0 && s.measw > e.rect[2];
447 let r = e.rect;
448 if vneed {
449 let h = if hneed { r[3].wrapping_sub(t) } else { r[3] };
450 self.wp.ctx.last_rect = [r[0].wrapping_add(r[2]).wrapping_sub(t), r[1], t, h];
451 let id = e.id.wrapping_mul(8).wrapping_add(1) | 0x8000_0000;
452 let (p, _) = self.sbar_core(id, s.scrolly, s.meash, r[3], true);
453 if p != s.scrolly {
454 let sl = self.slot(e.id);
455 sl.scrolly = p;
456 sl.adur = p as u32;
457 self.repass();
458 }
459 }
460 if hneed {
461 let s = *self.slot(e.id);
462 let w = if vneed { r[2].wrapping_sub(t) } else { r[2] };
463 self.wp.ctx.last_rect = [r[0], r[1].wrapping_add(r[3]).wrapping_sub(t), w, t];
464 let id = e.id.wrapping_mul(8).wrapping_add(2) | 0x8000_0000;
465 let (p, _) = self.sbar_core(id, s.scrollx, s.measw, r[2], false);
466 if p != s.scrollx {
467 let sl = self.slot(e.id);
468 sl.scrollx = p;
469 sl.at0 = p as u32;
470 self.repass();
471 }
472 }
473 }
474
475 fn scr_wheel(&mut self, e: &Scr) {
477 if e.flags & SC_NOWHEEL != 0 {
478 return;
479 }
480 let c = &self.wp.ctx;
481 if c.wheel_y | c.wheel_x == 0 {
482 return;
483 }
484 let hot = c.hot;
486 if hot == 0 {
487 return;
488 }
489 let list = &c.hits[c.hit_cur];
490 let found = list.get(e.hit0..).is_some_and(|v| v.iter().any(|h| h.id == hot && h.layer as u32 == e.layer));
491 if !found {
492 return;
493 }
494 let step = self.dp(WHEEL_DIP);
495 let c = &self.wp.ctx;
496 let mut dy = c.wheel_y.wrapping_neg().wrapping_mul(step) / 120; let mut dx = c.wheel_x.wrapping_mul(step) / 120;
498 let axes = scr_axes(e.flags);
499 if axes & 1 == 0 || (axes & 2 != 0 && self.mods & MOD_SHIFT != 0) {
501 dx = dx.wrapping_add(dy);
502 dy = 0;
503 }
504 if axes & 1 == 0 {
505 dy = 0;
506 }
507 if axes & 2 == 0 {
508 dx = 0;
509 }
510 let (mx, my) = self.scr_max(e);
511 let now = self.now_ms;
512 let anim_off = self.anim_off;
513 let s = self.slot(e.id);
514 let ny = clamp_mem((s.adur as i32).wrapping_add(dy), my).0;
515 let nx = clamp_mem((s.at0 as i32).wrapping_add(dx), mx).0;
516 if ny == s.adur as i32 && nx == s.at0 as i32 {
517 return; }
519 if s.scrolly == s.adur as i32 && s.scrollx == s.at0 as i32 {
521 s.aux = now as i32;
522 }
523 s.adur = ny as u32;
524 s.at0 = nx as u32;
525 if anim_off {
526 s.scrolly = ny;
527 s.scrollx = nx;
528 }
529 self.wp.ctx.wheel_y = 0;
530 self.wp.ctx.wheel_x = 0;
531 if anim_off {
532 self.repass();
533 } else {
534 self.animating = true;
535 }
536 }
537
538 pub(crate) fn scr_close(&mut self) {
540 if self.wp.list.scr_sp == 0 {
541 return;
542 }
543 let e = self.wp.list.scr[self.scr_top()];
544 if e.copy {
545 self.scroll_copy_end();
546 }
547 self.box_end();
548 self.scr_follow(&e);
549 if let Some(id) = self.agent_reveal() {
550 self.scr_show(&e, id); }
552 if self.scr_settle(&e) {
553 self.repass(); }
555 if e.flags & SC_NOBAR == 0 {
556 self.scr_bars(&e);
557 }
558 self.scr_wheel(&e);
559 if e.flags & SC_NOWHEEL == 0 {
560 let (mx, my) = self.scr_max(&e);
562 let s = *self.slot(e.id);
563 let axes = scr_axes(e.flags);
564 let ax = if axes & 1 != 0 { super::touch::AX_V } else { 0 } | if axes & 2 != 0 { super::touch::AX_H } else { 0 };
565 self.pan_push(e.id, e.hit0, e.layer as u8, false, ax, [s.scrollx, s.scrolly], [mx, my]);
566 }
567 self.wp.ctx.last_rect = e.rect;
568 if e.flags & SCR_F_LIST == 0 {
569 self.wp.ctx.last_state = 0;
571 let s = *self.slot(e.id);
572 let p = if scr_axes(e.flags) & 1 != 0 { s.scrolly } else { s.scrollx };
573 let v = self.px2dip(p);
574 self.sem(K_BOX, e.id, None, v);
575 }
576 self.wp.list.scr_sp -= 1;
577 }
578
579 pub(crate) fn scr_view(&self) -> (u32, [i32; 4]) {
581 let e = self.wp.list.scr[self.scr_top()];
582 (e.id, e.rect)
583 }
584
585 #[cfg_attr(jfgui_dev, track_caller)]
587 pub fn scroll_begin(&mut self, id: u32, flags: u32) {
588 let _src = self.src_scope();
589 self.scr_open(id, flags & !SCR_F_LIST, 0);
590 if flags & SC_H == 0 && self.mobile() {
594 let top = self.scr_top();
595 let e = self.wp.list.scr[top];
596 if self.slot(id).measw > e.rect[2] {
597 self.wp.list.scr[top].flags |= SC_H | SC_V;
598 }
599 }
600 self.scr_offset();
601 }
602
603 #[cfg_attr(jfgui_dev, track_caller)]
605 pub fn scroll_end(&mut self) {
606 let _src = self.src_scope();
607 self.scr_close();
608 }
609
610 pub fn scroll_to(&mut self, id: u32, x: i32, y: i32) {
612 let (px, py) = (self.dp(x), self.dp(y));
613 let s = self.slot(id);
614 if x >= 0 {
615 s.scrollx = px;
616 s.at0 = px as u32;
617 }
618 if y >= 0 {
619 s.scrolly = py;
620 s.adur = py as u32;
621 }
622 self.repass();
623 }
624
625 pub fn scroll_pos(&mut self, id: u32) -> (i32, i32) {
627 let s = *self.slot(id);
628 (self.px2dip(s.scrollx), self.px2dip(s.scrolly))
629 }
630
631 pub fn scroll_extent(&mut self, id: u32) -> (i32, i32) {
635 let s = *self.slot(id);
636 (self.px2dip(s.measw), self.px2dip(s.meash))
637 }
638
639 fn sbar_core(&mut self, id: u32, pos: i32, content: i32, view: i32, vertical: bool) -> (i32, u32) {
644 let rect = self.wp.ctx.last_rect;
645 let mut pos = pos;
646 let mut ev = 0;
647 let range = content.wrapping_sub(view);
648 if range <= 0 {
649 pos = 0;
650 } else if view <= 0 || self.m.touch.on {
651 pos = pos.max(0).min(range);
653 } else {
654 ev = self.behave(id, 0);
655 self.slot(id);
656 let exp = self.anim(id, if ev & (EV_HOVER | EV_HELD) != 0 { 256 } else { 0 }, 120);
657 let (mut start, mut len) = if vertical { (rect[1], rect[3]) } else { (rect[0], rect[2]) };
659 let st = self.style;
660 let m = st.scrollbar_margin(self);
661 start = start.wrapping_add(m);
662 len = len.wrapping_sub(m).wrapping_sub(m).max(1);
663 let mut thumb = ((len as u32 as u64 * view as u32 as u64) / content as u32 as u64) as u32 as i32;
665 let mn = st.scrollbar_min_thumb(self);
666 if thumb < mn {
667 thumb = mn;
668 }
669 if thumb > len {
670 thumb = len;
671 }
672 let free = len - thumb;
673 let c = &self.wp.ctx;
675 let (active, pressed, held) = (c.active == id, c.pressed & (1 << BTN_LEFT) != 0, ev & EV_HELD != 0);
676 let (press, cur) = if vertical { (c.press_y, c.my) } else { (c.press_x, c.mx) };
677 if active && pressed {
678 let tp = thumb_pos(pos, free, range);
679 let p = press.wrapping_sub(start);
680 let s = self.slot(id);
681 if p < tp {
682 pos = pos.wrapping_sub(view);
683 s.scrollx = 2;
684 } else if p >= tp.wrapping_add(thumb) {
685 pos = pos.wrapping_add(view);
686 s.scrollx = 2;
687 } else {
688 s.aux = p - tp;
689 s.scrollx = 1;
690 }
691 } else if held {
692 let s = *self.slot(id);
693 if s.scrollx == 1 {
694 let t = cur.wrapping_sub(start).wrapping_sub(s.aux).max(0); if free > 0 {
696 let t = t.min(free);
697 let f = free as u32 as u64;
699 pos = ((t as u32 as u64 * range as u32 as u64 + (f >> 1)) / f) as u32 as i32;
700 }
701 }
702 } else {
703 self.slot(id).scrollx = 0;
704 }
705 if pos < 0 {
706 pos = 0;
707 }
708 if pos > range {
709 pos = range;
710 }
711 let mut sp = Sp { value: exp as i32, flags: vertical as u32, ..Sp::default() };
713 sp.place(rect);
714 sp.s[0] = start; sp.s[1] = thumb; sp.value2 = thumb_pos(pos, free, range); st.scrollbar(self, &sp);
718 }
719 self.wp.ctx.last_rect = rect;
720 (pos, ev)
721 }
722
723 #[cfg_attr(jfgui_dev, track_caller)]
725 pub fn scrollbar(&mut self, id: u32, pos: &mut i32, content: i32, view: i32, vertical: bool) -> u32 {
726 let _src = self.src_scope();
727 let st = self.style;
728 let t = st.scrollbar_thickness(self);
729 if vertical {
730 self.place(t, 0);
731 } else {
732 self.place(0, t);
733 }
734 let (p, mut ev) = self.sbar_core(id, *pos, content, view, vertical);
735 if p != *pos {
736 *pos = p;
737 ev |= EV_ACT;
738 self.repass();
739 }
740 let mut s = 0;
742 if ev & EV_HOVER != 0 {
743 s |= SS_HOVER as u32;
744 }
745 if ev & EV_HELD != 0 {
746 s |= SS_PRESSED as u32;
747 }
748 self.wp.ctx.last_state = s;
749 self.sem(K_SLIDER, id, None, *pos).max = content.wrapping_sub(view).max(0);
750 ev
751 }
752
753 fn list_top(&self) -> usize {
757 let i = self.wp.list.list_sp.wrapping_sub(1) as usize;
758 if i < LIST_MAX { i } else { LIST_MAX }
759 }
760
761 fn list_keys(&mut self, li: usize) {
764 let e = self.wp.list.list[li];
765 let count = e.count;
766 if count <= 0 {
767 return;
768 }
769 let cur = e.sel;
770 let mut new = None;
771 let top = self.wp.list.scr[self.scr_top()];
772 let page = (top.rect[3] / e.ih).max(1);
774 if e.flags & LF_REORDER != 0 && cur >= 0 && cur < count {
776 let mut to = cur;
777 if self.kb_chord(VK_UP, MOD_CTRL) {
778 to = cur - 1;
779 } else if self.kb_chord(VK_DOWN, MOD_CTRL) {
780 to = cur + 1;
781 } else if self.kb_chord(VK_HOME, MOD_CTRL) {
782 to = 0;
783 } else if self.kb_chord(VK_END, MOD_CTRL) {
784 to = count - 1;
785 }
786 let to = to.clamp(0, count - 1);
787 if to != cur {
788 let l = &mut self.wp.list.list[li];
789 l.drop = (to << 16) | cur;
790 l.sel = to;
791 self.wp.ctx.focus_visible = true;
792 self.repass();
793 let s = self.slot(top.id);
794 let d = into_view(to.wrapping_mul(e.ih), e.ih, s.scrolly, top.rect[3]);
795 s.scrolly = s.scrolly.wrapping_add(d);
796 s.adur = s.scrolly as u32;
797 return;
798 }
799 }
800 if cur >= 0 && self.key(VK_RETURN) {
801 self.wp.list.list[li].kdbl = true;
802 self.repass();
803 }
804 if cur >= 0 && self.key(VK_SPACE) {
805 self.wp.list.list[li].ktog = true;
806 self.wp.ctx.focus_visible = true;
807 self.repass();
808 }
809 if self.key(VK_UP) {
810 new = Some(if cur > 0 { cur - 1 } else { 0 });
811 }
812 if self.key(VK_DOWN) {
813 new = Some(if cur < 0 { 0 } else { cur.wrapping_add(1) });
814 }
815 if self.key(VK_HOME) {
816 new = Some(0);
817 }
818 if self.key(VK_END) {
819 new = Some(count - 1);
820 }
821 if self.key(VK_PRIOR) {
822 new = Some(cur.wrapping_sub(page));
823 }
824 if self.key(VK_NEXT) {
825 new = Some(cur.wrapping_add(page));
826 }
827 let Some(mut new) = new else {
828 return;
829 };
830 if new < 0 {
831 new = 0;
832 }
833 if new >= count {
834 new = count - 1;
835 }
836 if new == cur {
837 return;
838 }
839 self.wp.list.list[li].sel = new;
840 self.wp.ctx.focus_visible = true;
841 self.repass();
842 let s = self.slot(top.id);
844 let d = into_view(new.wrapping_mul(e.ih), e.ih, s.scrolly, top.rect[3]);
845 s.scrolly = s.scrolly.wrapping_add(d);
846 s.adur = s.scrolly as u32;
847 }
848
849 fn list_drag(&mut self, li: usize) {
852 let e = self.wp.list.list[li];
853 if e.vev & EV_HELD != 0 {
854 if self.key(VK_ESCAPE) {
855 self.wp.ctx.active = 0;
856 self.wp.list.ldrag_list = 0;
857 self.repass();
858 return;
859 }
860 let top = self.wp.list.scr[self.scr_top()];
862 let zone = self.dp(24);
863 let my = self.wp.ctx.my;
864 let mut depth = zone.wrapping_sub(my.wrapping_sub(top.rect[1])); if depth <= 0 {
866 let b = top.rect[1].wrapping_add(top.rect[3]).wrapping_sub(my);
867 depth = zone.wrapping_sub(b);
868 if depth <= 0 {
869 self.wp.list.ldrag_t = self.now_ms;
870 return;
871 }
872 depth = depth.wrapping_neg(); }
874 self.animating = true;
876 let now = self.now_ms;
877 let mut t0 = self.wp.list.ldrag_t as i32;
878 let steps = anim_steps(now, &mut t0);
879 self.wp.list.ldrag_t = t0 as u32;
880 if steps == 0 {
881 return;
882 }
883 let sign = depth >> 31;
884 let mut sp = ((((depth ^ sign).wrapping_sub(sign)) as u32 >> 2) + 2) as i32;
885 if sp > e.ih {
886 sp = e.ih;
887 }
888 sp = sp.wrapping_mul(steps as i32);
889 if depth <= 0 {
890 sp = sp.wrapping_neg();
891 }
892 let s = self.slot(top.id);
893 let mut y = s.scrolly.wrapping_sub(sp);
894 if y < 0 {
895 y = 0;
896 }
897 s.scrolly = y;
898 s.adur = y as u32;
899 self.repass();
900 } else if e.vev & EV_COMMIT != 0 {
901 let from = self.wp.list.ldrag_from;
903 let mut to = self.wp.list.ldrag_to;
904 if to > from {
905 to -= 1; }
907 self.wp.list.ldrag_list = 0;
908 if to == from || from as u32 >= e.count as u32 || to as u32 >= e.count as u32 {
909 return;
910 }
911 let l = &mut self.wp.list.list[li];
912 l.drop = (to << 16) | from;
913 l.sel = to; self.repass();
915 } else {
916 self.wp.list.ldrag_list = 0;
917 self.repass();
918 }
919 }
920
921 fn list_drag_slot(&mut self, li: usize) {
923 let e = self.wp.list.list[li];
924 let half = ((e.ih as u32) >> 1) as i32;
925 let p = self.wp.ctx.my.wrapping_sub(e.y0).wrapping_add(half).max(0);
926 let mut to = p / e.ih;
927 if to > e.count {
928 to = e.count;
929 }
930 self.wp.list.ldrag_to = to;
931 }
932
933 fn list_drag_line(&mut self, li: usize) {
935 let e = self.wp.list.list[li];
936 let r = self.box_rect();
937 let mut sp = Sp { x: r[0], w: r[2], ..Sp::default() };
938 sp.y = self.wp.list.ldrag_to.wrapping_mul(e.ih).wrapping_add(e.y0); sp.h = 0;
940 let st = self.style;
941 st.list_drop_line(self, &sp);
942 }
943
944 #[cfg_attr(jfgui_dev, track_caller)]
946 pub fn list_begin(&mut self, id: u32, sel: &mut i32, count: i32, item_h: u32) -> (i32, i32) {
947 let _src = self.src_scope();
948 let flags = item_h & 0xFFFF_0000; let count = count.max(0);
950 let ihd = (item_h & 0xFFFF) as i32;
951 let ih = self.dp(if ihd > 0 { ihd } else { 40 }).max(1);
952 let li = (self.wp.list.list_sp as usize).min(LIST_MAX);
954 if li == LIST_MAX {
955 self.limit_hit(super::ctx::LIM_LISTS);
956 }
957 self.wp.list.list_sp = self.wp.list.list_sp.wrapping_add(1);
958 self.wp.list.list[li] = Ls { id, count, sel: *sel, ih, next: 0, old: *sel, ev: 0, vev: 0, y0: 0, flags, drop: -1, kdbl: false, ktog: false, notab: false };
959 if *sel >= 0 && *sel < count {
962 self.kb_ctx_redirect(id, id.wrapping_add(1).wrapping_add(*sel as u32));
963 }
964 let vev = self.scr_open(id, SC_V | SCR_F_LIST, HB_FOCUS | HB_OWNKEYS);
965 self.wp.list.list[li].vev = vev;
966 if vev & EV_FOCUS != 0 {
967 self.list_keys(li);
968 }
969 if self.wp.list.ldrag_list == id {
970 self.list_drag(li); }
972 self.scr_offset();
973 let y0 = self.box_rect()[1]; self.wp.list.list[li].y0 = y0;
976 if self.wp.list.ldrag_list == id {
977 self.list_drag_slot(li);
978 }
979 let k = self.wp.ctx.uiclip;
980 let first = (k[1].wrapping_sub(y0).max(0) / ih).min(count);
981 let end = ((k[3].wrapping_sub(y0).max(0).wrapping_add(ih).wrapping_sub(1)) / ih).min(count).max(first);
982 self.wp.list.list[li].next = first;
983 if first != 0 {
984 self.place(0, first.wrapping_mul(ih)); }
986 *sel = self.wp.list.list[li].sel;
987 (first, end)
988 }
989
990 fn li_row(&mut self, i: i32, label: Option<&str>) -> u32 {
992 let li = self.list_top();
993 let e = self.wp.list.list[li];
994 let d = i.wrapping_sub(e.next);
996 if d > 0 {
997 self.place(0, d.wrapping_mul(e.ih));
998 }
999 self.wp.list.list[li].next = i.wrapping_add(1);
1000 self.place(0, e.ih);
1001 let iid = e.id.wrapping_add(i as u32).wrapping_add(1);
1002 let ev0 = self.behave(iid, 0);
1003 let mut ev = ev0;
1004 let disabled = self.wp.ctx.last_state == SS_DISABLED as u32;
1005 let c = &mut self.wp.ctx;
1007 if c.active == iid && c.pressed & (1 << BTN_LEFT) != 0 {
1008 c.focus = e.id;
1009 c.focus_visible = false;
1010 }
1011 if e.flags & LF_REORDER != 0 && ev & EV_DRAG != 0 && self.wp.list.ldrag_list == 0 && self.wp.ctx.active == iid {
1013 let l = &mut self.wp.list;
1014 l.ldrag_list = e.id;
1015 l.ldrag_from = i;
1016 l.ldrag_to = i;
1017 l.ldrag_t = self.now_ms;
1018 self.wp.ctx.active = e.id;
1019 self.wp.ctx.focus = e.id;
1020 ev &= !(EV_ACT | EV_DRAG);
1021 self.repass();
1022 }
1023 if ev & EV_ACT != 0 && self.wp.list.list[li].sel != i {
1024 self.wp.list.list[li].sel = i;
1025 self.wp.list.list[li].ev |= EV_ACT;
1026 self.repass();
1027 }
1028 let selected = self.wp.list.list[li].sel == i;
1029 if selected && core::mem::take(&mut self.wp.list.list[li].kdbl) {
1031 ev |= EV_DOUBLE;
1032 }
1033 self.m.kbnav.toggle = selected && core::mem::take(&mut self.wp.list.list[li].ktog);
1034 if ev & EV_DOUBLE != 0 {
1035 self.wp.list.list[li].ev |= EV_DOUBLE;
1036 }
1037 let mut fl = 0;
1039 if self.wp.list.ldrag_list == e.id && self.wp.list.ldrag_from == i {
1040 fl |= 1; }
1042 if e.vev & EV_FOCUS != 0 && self.wp.ctx.focus_visible {
1043 fl |= 2;
1044 }
1045 let mut sp = Sp { ev, value: selected as i32, value2: disabled as i32, label, flags: fl, ..Sp::default() };
1046 sp.place(self.wp.ctx.last_rect);
1047 let st = self.style;
1048 st.list_row(self, &sp);
1049 if selected {
1050 self.wp.ctx.last_state |= SS_SELECTED as u32;
1051 }
1052 self.sem(K_LISTITEM, iid, label, i);
1053 ev0 | (ev & EV_DOUBLE)
1054 }
1055
1056 #[cfg_attr(jfgui_dev, track_caller)]
1058 pub fn list_item(&mut self, i: i32, label: &str) -> u32 {
1059 let _src = self.src_scope();
1060 let ev = self.li_row(i, Some(super::basic::cstr(label)));
1061 self.m.kbnav.toggle = false; ev
1063 }
1064
1065 #[cfg_attr(jfgui_dev, track_caller)]
1067 pub fn list_item_begin(&mut self, i: i32) -> u32 {
1068 let _src = self.src_scope();
1069 let ev = self.li_row(i, None);
1070 let li = self.list_top();
1071 let id = self.wp.list.list[li].id;
1072 let notab = self.wp.list.list[li].sel != i;
1074 self.wp.list.list[li].notab = notab;
1075 if notab {
1076 self.kb_notab(true);
1077 }
1078 let cid = id.wrapping_add(i as u32).wrapping_add(1) | 0xC000_0000; let x0 = self.dp(16);
1080 let wm = self.dp(28);
1081 let r = self.wp.ctx.last_rect;
1082 let mut w = r[2].wrapping_sub(wm);
1083 if w <= 0 {
1084 w = 1;
1085 }
1086 self.next_rect_px(r[0].wrapping_add(x0), r[1], w, r[3]);
1087 self.box_begin(cid, BOX_H | BOX_XCENTER | BOX_FIXED, 8);
1090 ev
1091 }
1092
1093 pub fn list_sel(&self) -> i32 {
1097 if self.wp.list.list_sp == 0 {
1098 return -1;
1099 }
1100 self.wp.list.list[self.list_top()].sel
1101 }
1102
1103 pub(crate) fn list_row_focused(&self, id: u32) -> bool {
1106 if self.wp.list.list_sp == 0 {
1107 return false;
1108 }
1109 let e = &self.wp.list.list[self.list_top()];
1110 e.sel >= 0 && self.wp.ctx.focus == e.id && id == e.id.wrapping_add(1).wrapping_add(e.sel as u32)
1111 }
1112
1113 #[cfg_attr(jfgui_dev, track_caller)]
1115 pub fn list_item_end(&mut self) {
1116 let _src = self.src_scope();
1117 self.box_end();
1118 let li = self.list_top();
1119 if core::mem::take(&mut self.wp.list.list[li].notab) {
1120 self.kb_notab(false);
1121 }
1122 self.m.kbnav.toggle = false;
1123 }
1124
1125 #[cfg_attr(jfgui_dev, track_caller)]
1128 pub fn list_end(&mut self, sel: &mut i32) -> (u32, i32) {
1129 let _src = self.src_scope();
1130 if self.wp.list.list_sp == 0 {
1131 return (0, -1);
1132 }
1133 let li = self.list_top();
1134 let e = self.wp.list.list[li];
1135 let d = e.count.wrapping_sub(e.next);
1137 if d > 0 {
1138 self.place(0, d.wrapping_mul(e.ih));
1139 }
1140 if self.wp.list.ldrag_list == e.id {
1141 self.list_drag_line(li); }
1143 self.scr_close();
1144 let e = self.wp.list.list[li];
1145 let s = e.sel;
1146 *sel = s;
1147 if e.vev & EV_FOCUS != 0 && self.wp.ctx.focus_visible && s < 0 {
1149 let r = self.wp.ctx.last_rect;
1150 let st = self.style;
1151 st.focus_rect(self, r[0], r[1], r[2], r[3]);
1152 }
1153 let mut stt = 0;
1154 if e.vev & EV_HOVER != 0 {
1155 stt |= SS_HOVER as u32;
1156 }
1157 if e.vev & EV_FOCUS != 0 {
1158 stt |= SS_FOCUSED as u32;
1159 }
1160 self.wp.ctx.last_state = stt;
1161 self.sem(K_LIST, e.id, None, s).max = e.count;
1162 let mut ev = e.ev;
1163 if s != e.old {
1164 ev |= EV_ACT;
1165 }
1166 if e.kdbl {
1167 ev |= EV_DOUBLE; }
1169 self.m.kbnav.toggle = false;
1170 self.wp.list.list_sp -= 1;
1171 if e.drop != -1 {
1172 return (ev | EV_DRAG, e.drop); }
1174 (ev, s)
1175 }
1176
1177 fn tab_sizes<'a>(&mut self, flags: u32, names: &[&'a str], widths: &mut Tw) -> (Sp<'a>, usize, i32) {
1182 let st = self.style;
1183 let mut sp = Sp { flags, ..Sp::default() };
1184 st.tab_metrics(self, &mut sp); let pad = sp.s[0];
1186 st.tab_font(self, false);
1188 let n = names.iter().position(|s| super::basic::cstr(s).is_empty()).unwrap_or(names.len());
1189 let w = widths.get(n);
1190 let mut total = 0i32;
1191 for (k, name) in names[..n].iter().enumerate() {
1192 w[k] = self.text.width(super::basic::cstr(name)).wrapping_add(pad).wrapping_add(pad);
1193 total = total.wrapping_add(w[k]);
1194 }
1195 (sp, n, total)
1196 }
1197
1198 #[cfg_attr(jfgui_dev, track_caller)]
1203 pub fn tabs_scrolling(&mut self, id: u32, sel: &mut i32, names: &[&str]) -> u32 {
1204 let _src = self.src_scope();
1205 let flags = self.take_flags();
1206 let mut tw = Tw::default();
1207 let (sp, count, total) = self.tab_sizes(flags, names, &mut tw);
1208 let widths = tw.get(count);
1209 let rect = self.place(total, sp.s[1]);
1210 let base = id.wrapping_mul(8) | 0x8000_0000;
1211 let state = self.slot(base + 7);
1212 let changed = state.measw != rect[2] || state.meash != total || state.aux != *sel;
1213 state.measw = rect[2];
1214 state.meash = total;
1215 state.aux = *sel;
1216 if total <= rect[2] {
1217 self.next_rect_px(rect[0], rect[1], rect[2], rect[3]);
1218 self.next_flags(flags);
1219 return self.tabs(id, sel, names);
1220 }
1221 let arrow = self.dp(22).min((rect[2] / 3).max(0));
1222 let view = (rect[2] - arrow * 2).max(0);
1223 let sid = base + 1;
1224 let old = *sel;
1225 self.next_rect_px(rect[0] + arrow, rect[1], view, rect[3]);
1226 self.scroll_begin(sid, SC_H | SC_NOBAR | BOX_H);
1227 let width_dip = ((total as i64 * 96 + self.dpi() as i64 - 1) / self.dpi() as i64) as i32;
1228 self.next_size(width_dip, self.px2dip(rect[3]));
1229 self.next_flags(flags);
1230 let ev = self.tabs(id, sel, names);
1231 self.scroll_end();
1232 let (offset, _) = self.scroll_pos(sid);
1233 let viewport = self.px2dip(view);
1234 if (changed || old != *sel) && *sel >= 0 && (*sel as usize) < count {
1235 let left: i32 = widths[..*sel as usize].iter().sum();
1236 let right = self.px2dip(left + widths[*sel as usize]);
1237 let left = self.px2dip(left);
1238 let next = if left < offset { left }
1239 else if right > offset + viewport { (right - viewport).min(left) }
1240 else { offset };
1241 if next != offset { self.scroll_to(sid, next, 0); }
1242 }
1243 self.slot(base + 7).aux = *sel;
1244 let maximum = (width_dip - viewport).max(0);
1245 for (part, x, icon, label, next, enabled) in [
1246 (2, rect[0], IC_CHEV_LEFT, crate::i18n::tr("Vorherige Register"), (offset - viewport / 2).max(0), offset > 0),
1247 (3, rect[0] + rect[2] - arrow, IC_CHEV_RIGHT, crate::i18n::tr("Weitere Register"), (offset + viewport / 2).min(maximum), offset < maximum),
1248 ] {
1249 self.next_rect_px(x, rect[1], arrow, rect[3]);
1250 self.next_icon(icon);
1251 self.next_flags(flags | ST_SUBTLE | ST_COMPACT | ST_ICON_ONLY | if enabled { 0 } else { ST_DISABLED });
1252 if self.button(base + part, label) & EV_ACT != 0 { self.scroll_to(sid, next, 0); }
1253 self.tooltip(label);
1254 }
1255 self.wp.ctx.last_rect = rect;
1256 ev
1257 }
1258
1259 #[cfg_attr(jfgui_dev, track_caller)]
1261 pub fn tabs(&mut self, id: u32, sel: &mut i32, names: &[&str]) -> u32 {
1262 let _src = self.src_scope();
1263 let old = *sel;
1264 let st = self.style;
1265 let flags = self.take_flags();
1266 let (mut tw, mut tfull) = (Tw::default(), Tw::default());
1267 let (mut sp, n, total) = self.tab_sizes(flags, names, &mut tw);
1268 let widths = tw.get(n);
1269 let (h, pillw) = (sp.s[1], sp.s[2]);
1270 let count = n as i32;
1271 self.place(total, h);
1272 let strip = self.wp.ctx.last_rect;
1273 let full = tfull.get(n);
1275 full.copy_from_slice(widths);
1276 if total > strip[2] && n > 0 {
1277 let pad = sp.s[0];
1278 st.tab_font(self, false);
1279 let min = pad * 2 + self.text.width("…");
1280 tabs_fit(widths, strip[2], min);
1281 }
1282 let disabled = if flags & ST_DISABLED != 0 { HB_DISABLED } else { 0 };
1283 let ev = self.behave(id, HB_FOCUS | disabled);
1284 let state = self.wp.ctx.last_state;
1285 if ev & EV_FOCUS != 0 && count != 0 {
1287 let mut s = *sel;
1288 if self.key(VK_LEFT) {
1289 s = s.wrapping_sub(1);
1290 }
1291 if self.key(VK_RIGHT) {
1292 s = s.wrapping_add(1);
1293 }
1294 if self.key(VK_HOME) {
1295 s = 0;
1296 }
1297 if self.key(VK_END) {
1298 s = count - 1;
1299 }
1300 if s < 0 {
1301 s = 0;
1302 }
1303 if s >= count {
1304 s = count - 1;
1305 }
1306 if s != *sel {
1307 *sel = s;
1308 self.wp.ctx.focus_visible = true;
1309 self.repass();
1310 }
1311 }
1312 let mut x = strip[0];
1314 for i in 0..n {
1315 let label = super::basic::cstr(names[i]);
1316 self.wp.ctx.last_rect = [x, strip[1], widths[i], h];
1317 let tid = id.wrapping_add(i as u32).wrapping_add(1);
1318 let tev = self.behave(tid, disabled);
1319 if tev & EV_ACT != 0 {
1320 self.wp.ctx.focus = id;
1321 self.wp.ctx.focus_visible = false;
1322 if *sel != i as i32 {
1323 *sel = i as i32;
1324 self.repass();
1325 }
1326 }
1327 sp.place(self.wp.ctx.last_rect);
1329 sp.ev = tev;
1330 sp.value = (*sel == i as i32) as i32;
1331 sp.value2 = (self.wp.ctx.last_state == SS_DISABLED as u32) as i32;
1332 sp.label = Some(label);
1333 if widths[i] < full[i] {
1334 st.tab_font(self, *sel == i as i32);
1335 let short = self.ellipsize(label.as_bytes(), widths[i] - sp.s[0] * 2).0;
1336 st.tab(self, &Sp { label: Some(&short), ..sp });
1337 } else {
1338 st.tab(self, &sp);
1339 }
1340 if *sel == i as i32 {
1341 self.wp.ctx.last_state |= SS_SELECTED as u32;
1342 }
1343 self.sem(K_TAB, tid, Some(label), i as i32);
1344 x = x.wrapping_add(widths[i]);
1345 }
1346 let s = *sel;
1348 if s >= 0 && (s as u32) < count as u32 {
1349 let s = s as usize;
1350 let tx: i32 = widths[..s].iter().fold(0i32, |a, &w| a.wrapping_add(w)); let target = tx.wrapping_add(widths[s].wrapping_sub(pillw) >> 1);
1352 let now = self.now_ms;
1353 let anim_off = self.anim_off;
1354 let sl = self.slot(id);
1355 if sl.at0 == 0 {
1356 sl.at0 = 1;
1357 sl.scrollx = target;
1358 sl.scrolly = target;
1359 }
1360 if target != sl.scrolly {
1361 if sl.scrollx == sl.scrolly {
1362 sl.aux = now as i32;
1363 }
1364 sl.scrolly = target;
1365 }
1366 let mut animating = false;
1367 if sl.scrollx != target {
1368 if anim_off {
1369 sl.scrollx = target;
1370 } else {
1371 let steps = anim_steps(now, &mut sl.aux);
1372 sl.scrollx = anim_decay(sl.scrollx, target, steps);
1373 if sl.scrollx != target {
1374 animating = true;
1375 }
1376 }
1377 }
1378 let px = sl.scrollx;
1379 if animating {
1380 self.animating = true;
1381 }
1382 let mut pp = sp;
1384 pp.place(strip);
1385 pp.value = px; pp.value2 = (state == SS_DISABLED as u32) as i32;
1387 st.tab_pill(self, &pp);
1388 if ev & EV_FOCUS != 0 && self.wp.ctx.focus_visible {
1390 let fx = strip[0].wrapping_add(tx);
1391 st.focus_rect(self, fx, strip[1], widths[s], h);
1392 }
1393 }
1394 self.wp.ctx.last_rect = strip;
1396 self.wp.ctx.last_state = state;
1397 self.sem(K_BOX, id, None, *sel).max = count;
1398 let mut r = ev & !EV_ACT;
1399 if *sel != old {
1400 r |= EV_ACT;
1401 }
1402 r
1403 }
1404
1405 #[cfg_attr(jfgui_dev, track_caller)]
1411 pub fn expander_begin(&mut self, id: u32, header: &str, open: &mut i32) -> i32 {
1412 let _src = self.src_scope();
1413 let header = super::basic::cstr(header);
1414 self.box_begin(id.wrapping_mul(8).wrapping_add(1) | 0x8000_0000, BOX_V, 0);
1416 let st = self.style;
1418 let mut sp = Sp { label: Some(header), ..Sp::default() };
1419 let (w, h) = st.expander_size(self, &mut sp);
1420 self.place(w, h);
1421 let hdr = self.wp.ctx.last_rect;
1422 let ev = self.behave(id, HB_FOCUS);
1423 let state = self.wp.ctx.last_state;
1424 if ev & EV_ACT != 0 {
1425 *open = (*open ^ 1) & 1;
1426 self.repass();
1427 }
1428 let o = *open;
1429 sp.place(hdr);
1431 sp.ev = ev;
1432 sp.value = o;
1433 sp.value2 = (state == SS_DISABLED as u32) as i32;
1434 st.expander_header(self, &sp);
1435 self.wp.ctx.last_rect = hdr;
1437 if ev & EV_FOCUS != 0 && self.wp.ctx.focus_visible {
1438 st.focus_rect(self, hdr[0], hdr[1], hdr[2], hdr[3]);
1439 }
1440 let mut s = state;
1441 if o != 0 {
1442 s |= SS_OPEN as u32;
1443 }
1444 self.wp.ctx.last_state = s;
1445 self.sem(K_BUTTON, id, Some(header), o);
1446 if self.wp.list.exp_sp as usize >= EXP_MAX {
1448 self.limit_hit(super::ctx::LIM_EXPANDERS);
1449 }
1450 let l = &mut self.wp.list;
1451 if (l.exp_sp as usize) < EXP_MAX {
1452 l.exp[l.exp_sp as usize] = o as u32;
1453 }
1454 l.exp_sp = l.exp_sp.wrapping_add(1);
1455 if o != 0 {
1456 self.box_begin(id.wrapping_mul(8).wrapping_add(2) | 0x8000_0000, BOX_V | PAD_16, 8);
1458 sp.place(self.wp.ctx.last_rect);
1459 st.expander_body(self, &sp);
1460 }
1461 o
1462 }
1463
1464 #[cfg_attr(jfgui_dev, track_caller)]
1466 pub fn expander_end(&mut self) {
1467 let _src = self.src_scope();
1468 let l = &mut self.wp.list;
1469 if l.exp_sp == 0 {
1470 return;
1471 }
1472 l.exp_sp -= 1;
1473 let i = l.exp_sp as usize;
1474 if i < EXP_MAX && l.exp[i] != 0 {
1475 self.box_end(); }
1477 self.box_end(); }
1479}