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