1use alloc::collections::{BTreeMap, BTreeSet};
39use alloc::string::String;
40use alloc::vec::Vec;
41
42use crate::consts::*;
43use crate::keys::*;
44use crate::list::{into_view, SCR_F_LIST};
45use crate::{Sp, Ui};
46
47#[derive(Clone, Copy, Debug)]
49pub struct GridCol<'a> {
50 pub title: &'a str,
51 pub width: i32,
52 pub min: i32,
53 pub flags: u32,
54}
55
56impl<'a> GridCol<'a> {
57 pub const fn new(title: &'a str, width: i32) -> GridCol<'a> {
59 GridCol { title, width, min: 48, flags: 0 }
60 }
61 pub const fn flags(mut self, flags: u32) -> GridCol<'a> {
63 self.flags = flags;
64 self
65 }
66 pub const fn min(mut self, min: i32) -> GridCol<'a> {
68 self.min = min;
69 self
70 }
71}
72
73#[derive(Clone, Default, Debug)]
75struct GridFrame {
76 id: u32,
77 keys: Vec<u64>,
78 cols: Vec<(i32, i32, u32)>,
80 x0: i32,
81 row_h: i32,
82 pad: i32,
83 next: i32,
84 vev: u32,
85 ev: u32,
86 row: i32,
87 row_rect: [i32; 4],
88 cur_row: i32,
89 focus_visible: bool,
90}
91
92#[derive(Clone, Default, Debug)]
94pub struct GridState {
95 pub sel: BTreeSet<u64>,
97 pub cur: Option<u64>,
99 anchor: Option<u64>,
100 pub sort: Option<(usize, bool)>,
102 pub widths: Vec<i32>,
104 pub striped: bool,
106 f: GridFrame,
107}
108
109impl GridState {
110 pub fn selected_in_order(&self) -> Vec<u64> {
112 self.f.keys.iter().copied().filter(|k| self.sel.contains(k)).collect()
113 }
114 pub fn select_only(&mut self, key: Option<u64>) {
116 self.sel.clear();
117 if let Some(k) = key {
118 self.sel.insert(k);
119 }
120 self.cur = key;
121 self.anchor = key;
122 }
123 pub fn retain(&mut self, keys: &[u64]) {
125 let set: BTreeSet<u64> = keys.iter().copied().collect();
126 self.sel.retain(|k| set.contains(k));
127 if self.cur.is_some_and(|k| !set.contains(&k)) {
128 self.cur = None;
129 }
130 if self.anchor.is_some_and(|k| !set.contains(&k)) {
131 self.anchor = None;
132 }
133 }
134}
135
136#[derive(Clone, Copy, Default, Debug)]
138pub struct GridEv {
139 pub ev: u32,
141 pub row: i32,
143 pub sort_changed: bool,
145}
146
147#[derive(Clone, Copy, Debug)]
149pub struct TreeRow<'a> {
150 pub key: u64,
151 pub depth: u16,
152 pub has_children: bool,
153 pub check: Option<u8>,
155 pub icon: u32,
156 pub label: &'a str,
157 pub detail: Option<&'a str>,
159}
160
161#[derive(Clone, Default, Debug)]
163pub struct TreeState {
164 pub expanded: BTreeSet<u64>,
165 pub cur: Option<u64>,
166 pub marks: BTreeMap<u64, u32>,
168}
169
170#[derive(Clone, Copy, Default, Debug)]
172pub struct TreeEv {
173 pub expanded: Option<u64>,
175 pub collapsed: Option<u64>,
177 pub check: Option<(u64, u8)>,
179 pub activated: Option<u64>,
181 pub sel_changed: bool,
183}
184
185fn part(id: u32, k: u32) -> u32 {
186 id.wrapping_mul(8).wrapping_add(k) | 0x8000_0000
187}
188
189fn head(id: u32, column: u32) -> u32 {
192 part(id ^ 0xA5A5, 1 + column)
193}
194
195impl Ui {
196 #[cfg_attr(jfgui_dev, track_caller)]
200 pub fn grid_begin(&mut self, id: u32, st: &mut GridState, cols: &[GridCol], keys: &[u64]) -> (i32, i32) {
201 let _src = self.src_scope();
202 let sd = self.style.data();
203 let (hh, rh, pad) = sd.grid_metrics(self);
204 let (hh, rh, pad) = (self.dp(hh), self.dp(rh).max(1), self.dp(pad));
205 if self.wp.lay.next_w == 0 && self.wp.lay.next_h == 0 && self.wp.lay.next_rect_set == 0 {
207 self.next_size(-1, -1);
208 }
209 self.box_begin(part(id, 0), BOX_V, 0);
210 let outer = self.box_rect();
211 if st.widths.len() != cols.len() {
213 st.widths.resize(cols.len(), 0);
214 }
215 let mut ws: Vec<i32> = cols
216 .iter()
217 .zip(&st.widths)
218 .map(|(c, &u)| self.dp(if u > 0 { u.max(c.min) } else { c.width.max(c.min) }))
219 .collect();
220 let fill = cols.iter().position(|c| c.flags & GC_FILL != 0).unwrap_or(cols.len().saturating_sub(1));
221 let total: i32 = ws.iter().sum();
222 if !ws.is_empty() && total < outer[2] {
223 ws[fill] += outer[2] - total;
224 } else if !ws.is_empty() && total > outer[2] && st.widths[fill] == 0 {
225 let floor = self.dp(cols[fill].min.max(48));
227 ws[fill] = (ws[fill] - (total - outer[2])).max(floor.min(ws[fill]));
228 }
229 let mut colv = Vec::with_capacity(cols.len());
230 let mut x = 0;
231 for (c, &w) in cols.iter().zip(&ws) {
232 colv.push((x, w, c.flags));
233 x += w;
234 }
235 let hr = self.place(0, hh);
237 let mut sp = Sp::default();
238 sp.place(hr);
239 sd.grid_header_bg(self, &sp);
240 self.clip_push(hr[0], hr[1], hr[2], hr[3]);
241 let mut sort_changed = false;
242 for (i, c) in cols.iter().enumerate() {
243 let (cx, cw, cf) = colv[i];
244 let r = [hr[0] + cx, hr[1], cw, hh];
245 self.wp.ctx.last_rect = r;
246 let sortable = cf & GC_NOSORT == 0;
247 let ev = self.behave(head(id, i as u32), if sortable { 0 } else { HB_DISABLED });
248 if ev & EV_ACT != 0 && sortable {
249 st.sort = match st.sort {
250 Some((s, desc)) if s == i => Some((i, !desc)),
251 _ => Some((i, false)),
252 };
253 sort_changed = true;
254 self.repass();
255 }
256 let mut f = 0;
257 match st.sort {
258 Some((s, false)) if s == i => f |= GH_SORT_ASC,
259 Some((s, true)) if s == i => f |= GH_SORT_DESC,
260 _ => {}
261 }
262 if i + 1 == cols.len() {
263 f |= GH_LAST;
264 }
265 if cf & GC_RIGHT != 0 {
266 f |= GH_RIGHT;
267 }
268 let mut sp = Sp { ev, flags: f, label: Some(c.title), ..Sp::default() };
269 sp.place(r);
270 sp.s[0] = pad;
271 sp.state = if self.wp.ctx.last_state == SS_DISABLED as u32 { 0 } else { sp.state };
272 sd.grid_header(self, &sp);
273 self.wp.ctx.last_rect = r;
274 self.wp.ctx.last_state = 0;
275 self.sem(K_BUTTON, head(id, i as u32), Some(c.title), f as i32);
276 }
277 for (i, c) in cols.iter().enumerate() {
279 if i + 1 == cols.len() {
280 break;
281 }
282 let (cx, cw, _) = colv[i];
283 let gw = self.dp(8);
284 let gid = (id ^ 0x5A5A).wrapping_mul(8).wrapping_add(1 + i as u32) | 0x8000_0000;
285 let ev = self.hit_rect(gid, hr[0] + cx + cw - (gw >> 1), hr[1], gw, hh, CUR_SIZE_WE << HB_CURSOR_SHIFT);
286 if ev & EV_DOUBLE != 0 {
287 st.widths[i] = 0;
288 self.repass();
289 } else if ev & EV_HELD != 0 {
290 let w = self.px2dip(self.wp.ctx.mx - (hr[0] + cx)).max(c.min);
291 if w != st.widths[i] {
292 st.widths[i] = w;
293 self.repass();
294 }
295 }
296 }
297 self.clip_pop();
298 self.next_size(-1, -1);
301 if let Some(c) = st.cur.and_then(|k| keys.iter().position(|&x| x == k)) {
302 self.kb_ctx_redirect(id, id.wrapping_add(1).wrapping_add(c as u32));
303 }
304 let vev = self.scr_open(id, SC_V | SCR_F_LIST, HB_FOCUS | HB_OWNKEYS);
305 let count = keys.len() as i32;
306 st.f = GridFrame {
307 id,
308 keys: keys.to_vec(),
309 cols: colv,
310 x0: hr[0],
311 row_h: rh,
312 pad,
313 next: 0,
314 vev,
315 ev: if sort_changed { 0x8000_0000 } else { 0 },
316 row: -1,
317 row_rect: [0; 4],
318 cur_row: st.cur.and_then(|k| keys.iter().position(|&x| x == k)).map_or(-1, |p| p as i32),
319 focus_visible: false,
320 };
321 if vev & EV_FOCUS != 0 {
322 self.grid_keys(st);
323 }
324 self.scr_offset();
325 let y0 = self.box_rect()[1];
326 let k = self.wp.ctx.uiclip;
327 let first = (k[1].wrapping_sub(y0).max(0) / rh).min(count);
328 let end = ((k[3].wrapping_sub(y0).max(0) + rh - 1) / rh).min(count).max(first);
329 st.f.next = first;
330 if first != 0 {
331 self.place(0, first * rh);
332 }
333 st.f.focus_visible = vev & EV_FOCUS != 0 && self.wp.ctx.focus_visible;
334 (first, end)
335 }
336
337 fn grid_keys(&mut self, st: &mut GridState) {
339 let n = st.f.keys.len() as i32;
340 if n == 0 {
341 return;
342 }
343 let (_, view) = self.scr_view();
344 let page = (view[3] / st.f.row_h).max(1);
345 let cur = st.f.cur_row;
346 let shift = self.mods & MOD_SHIFT != 0;
347 let ctrl = self.mods & MOD_CTRL != 0;
348 let mut new = cur;
349 if self.key(VK_UP) {
350 new = if cur > 0 { cur - 1 } else { 0 };
351 }
352 if self.key(VK_DOWN) {
353 new = if cur < 0 { 0 } else { cur + 1 };
354 }
355 if self.key(VK_HOME) {
356 new = 0;
357 }
358 if self.key(VK_END) {
359 new = n - 1;
360 }
361 if self.key(VK_PRIOR) {
362 new = cur - page;
363 }
364 if self.key(VK_NEXT) {
365 new = if cur < 0 { page } else { cur + page };
366 }
367 new = new.clamp(0, n - 1);
368 if ctrl && self.key(b'A' as u32) {
369 st.sel = st.f.keys.iter().copied().collect();
370 st.f.ev |= EV_ACT;
371 self.repass();
372 }
373 if self.key(VK_SPACE) && cur >= 0 {
374 let k = st.f.keys[cur as usize];
375 if !st.sel.remove(&k) {
376 st.sel.insert(k);
377 }
378 st.anchor = Some(k);
379 st.f.ev |= EV_ACT;
380 self.repass();
381 }
382 if self.key(VK_RETURN) && cur >= 0 {
383 st.f.ev |= EV_DOUBLE;
384 st.f.row = cur;
385 }
386 if new == cur {
387 return;
388 }
389 let k = st.f.keys[new as usize];
390 st.cur = Some(k);
391 st.f.cur_row = new;
392 if shift {
393 let a = st.anchor.and_then(|a| st.f.keys.iter().position(|&x| x == a)).map_or(new, |p| p as i32);
394 let (lo, hi) = (a.min(new) as usize, a.max(new) as usize);
395 st.sel = st.f.keys[lo..=hi].iter().copied().collect();
396 } else {
397 st.sel.clear();
398 st.sel.insert(k);
399 st.anchor = Some(k);
400 }
401 st.f.ev |= EV_ACT;
402 self.wp.ctx.focus_visible = true;
403 self.repass();
404 let (vid, view) = self.scr_view();
406 let rh = st.f.row_h;
407 let s = self.slot(vid);
408 let d = into_view(new * rh, rh, s.scrolly, view[3]);
409 s.scrolly += d;
410 s.adur = s.scrolly as u32;
411 }
412
413 #[cfg_attr(jfgui_dev, track_caller)]
415 pub fn grid_row(&mut self, st: &mut GridState, i: i32) -> u32 {
416 let _src = self.src_scope();
417 let f = &st.f;
418 let (id, rh) = (f.id, f.row_h);
419 let d = i - f.next;
420 if d > 0 {
421 self.place(0, d * rh);
422 }
423 st.f.next = i + 1;
424 let r = self.place(0, rh);
425 let iid = id.wrapping_add(1).wrapping_add(i as u32);
426 let ev = self.behave(iid, 0);
427 let key = st.f.keys.get(i as usize).copied().unwrap_or(0);
428 let c = &mut self.wp.ctx;
430 if c.active == iid && c.pressed & (1 << BTN_LEFT) != 0 {
431 c.focus = id;
432 c.focus_visible = false;
433 }
434 if ev & EV_ACT != 0 {
435 let m = self.mods;
436 if m & MOD_SHIFT != 0 {
437 let a = st.anchor.and_then(|a| st.f.keys.iter().position(|&x| x == a)).map_or(i, |p| p as i32);
438 let (lo, hi) = (a.min(i) as usize, a.max(i) as usize);
439 st.sel = st.f.keys[lo..=hi.min(st.f.keys.len() - 1)].iter().copied().collect();
440 } else if m & MOD_CTRL != 0 {
441 if !st.sel.remove(&key) {
442 st.sel.insert(key);
443 }
444 st.anchor = Some(key);
445 } else {
446 st.sel.clear();
447 st.sel.insert(key);
448 st.anchor = Some(key);
449 }
450 st.cur = Some(key);
451 st.f.cur_row = i;
452 st.f.ev |= EV_ACT;
453 self.repass();
454 }
455 if ev & EV_RCLICK != 0 {
456 if !st.sel.contains(&key) {
457 st.sel.clear();
458 st.sel.insert(key);
459 st.anchor = Some(key);
460 st.f.ev |= EV_ACT;
461 }
462 st.cur = Some(key);
463 st.f.cur_row = i;
464 st.f.ev |= EV_RCLICK;
465 st.f.row = i;
466 self.repass();
467 }
468 if ev & EV_DOUBLE != 0 {
469 st.f.ev |= EV_DOUBLE;
470 st.f.row = i;
471 }
472 let selected = st.sel.contains(&key);
473 let mut fl = 0;
474 if st.f.cur_row == i {
475 fl |= if st.f.focus_visible { GR_FOCUS } else { GR_CURRENT };
476 }
477 if st.striped && i & 1 != 0 {
478 fl |= GR_STRIPE;
479 }
480 let mut sp = Sp { ev, value: selected as i32, value2: i, flags: fl, ..Sp::default() };
481 sp.place(r);
482 let sd = self.style.data();
483 sd.grid_row(self, &sp);
484 self.wp.ctx.last_rect = r;
485 if selected {
486 self.wp.ctx.last_state |= SS_SELECTED as u32;
487 }
488 self.sem(K_LISTITEM, iid, None, i);
489 st.f.row_rect = r;
490 ev
491 }
492
493 #[cfg_attr(jfgui_dev, track_caller)]
496 pub fn grid_cell(&mut self, st: &GridState, c: usize) {
497 let _src = self.src_scope();
498 let r = st.f.row_rect;
499 let Some(&(cx, cw, _)) = st.f.cols.get(c) else { return };
500 let pad = st.f.pad;
501 let x = st.f.x0 + cx + pad;
502 let w = (cw - pad * 2).max(1);
503 self.next_rect_px(x, r[1], w, r[3]);
504 }
505
506 #[cfg_attr(jfgui_dev, track_caller)]
508 pub fn grid_text(&mut self, st: &GridState, c: usize, s: &str) {
509 let _src = self.src_scope();
510 self.grid_text_ts(st, c, TS_BODY, s);
511 }
512
513 #[cfg_attr(jfgui_dev, track_caller)]
515 pub fn grid_text_ts(&mut self, st: &GridState, c: usize, ts: u32, s: &str) {
516 let _src = self.src_scope();
517 let f = st.f.cols.get(c).map_or(0, |c| c.2);
518 let mut style = ts | TX_ELLIPSIS;
519 if f & GC_RIGHT != 0 {
520 style |= TX_RIGHT;
521 } else if f & GC_CENTER != 0 {
522 style |= TX_CENTER;
523 }
524 self.grid_cell(st, c);
525 self.text(style, s);
526 }
527
528 #[cfg_attr(jfgui_dev, track_caller)]
530 pub fn grid_end(&mut self, st: &mut GridState) -> GridEv {
531 let _src = self.src_scope();
532 let n = st.f.keys.len() as i32;
533 let d = n - st.f.next;
534 if d > 0 {
535 self.place(0, d * st.f.row_h);
536 }
537 self.scr_close();
538 if st.f.vev & EV_FOCUS != 0 && self.wp.ctx.focus_visible && st.f.cur_row < 0 {
540 let r = self.wp.ctx.last_rect;
541 let s = self.style;
542 s.focus_rect(self, r[0], r[1], r[2], r[3]);
543 }
544 let mut stt = 0;
545 if st.f.vev & EV_HOVER != 0 {
546 stt |= SS_HOVER as u32;
547 }
548 if st.f.vev & EV_FOCUS != 0 {
549 stt |= SS_FOCUSED as u32;
550 }
551 self.wp.ctx.last_state = stt;
552 self.sem(K_LIST, st.f.id, None, st.f.cur_row).max = n;
553 self.box_end(); let sort_changed = st.f.ev & 0x8000_0000 != 0;
555 GridEv { ev: st.f.ev & 0x7FFF_FFFF, row: st.f.row, sort_changed }
556 }
557
558 #[cfg_attr(jfgui_dev, track_caller)]
563 pub fn tree(&mut self, id: u32, st: &mut TreeState, rows: &[TreeRow], row_h: i32) -> TreeEv {
564 let _src = self.src_scope();
565 let sd = self.style.data();
566 let rh = self.dp(if row_h > 0 { row_h } else { sd.grid_metrics(self).1 }).max(1);
567 let mut out = TreeEv::default();
568 let n = rows.len() as i32;
569 if self.wp.lay.next_w == 0 && self.wp.lay.next_h == 0 && self.wp.lay.next_rect_set == 0 {
570 self.next_size(-1, -1);
571 }
572 let mut cur = st.cur.and_then(|k| rows.iter().position(|r| r.key == k)).map_or(-1, |p| p as i32);
573 if cur >= 0 {
574 self.kb_ctx_redirect(id, id.wrapping_add(1).wrapping_add(cur as u32)); }
576 let vev = self.scr_open(id, SC_V | SCR_F_LIST, HB_FOCUS | HB_OWNKEYS);
577 if vev & EV_FOCUS != 0 && n > 0 {
578 let mut new = cur;
579 if self.key(VK_UP) {
580 new = if cur > 0 { cur - 1 } else { 0 };
581 }
582 if self.key(VK_DOWN) {
583 new = if cur < 0 { 0 } else { (cur + 1).min(n - 1) };
584 }
585 if self.key(VK_HOME) {
586 new = 0;
587 }
588 if self.key(VK_END) {
589 new = n - 1;
590 }
591 if cur >= 0 {
592 let r = rows[cur as usize];
593 if self.key(VK_RIGHT) {
594 if r.has_children && !st.expanded.contains(&r.key) {
595 st.expanded.insert(r.key);
596 out.expanded = Some(r.key);
597 } else if r.has_children && cur + 1 < n {
598 new = cur + 1;
599 }
600 }
601 if self.key(VK_LEFT) {
602 if st.expanded.contains(&r.key) {
603 st.expanded.remove(&r.key);
604 out.collapsed = Some(r.key);
605 } else if r.depth > 0 {
606 if let Some(p) = rows[..cur as usize].iter().rposition(|x| x.depth < r.depth) {
608 new = p as i32;
609 }
610 }
611 }
612 if self.key(VK_SPACE) {
613 if let Some(c) = r.check {
614 out.check = Some((r.key, if c == 1 { 0 } else { 1 }));
615 }
616 }
617 if self.key(VK_RETURN) {
618 out.activated = Some(r.key);
619 }
620 }
621 if new != cur && new >= 0 {
622 cur = new;
623 st.cur = Some(rows[new as usize].key);
624 out.sel_changed = true;
625 self.wp.ctx.focus_visible = true;
626 let (vid, view) = self.scr_view();
627 let s = self.slot(vid);
628 let d = into_view(new * rh, rh, s.scrolly, view[3]);
629 s.scrolly += d;
630 s.adur = s.scrolly as u32;
631 }
632 if out.expanded.is_some() || out.collapsed.is_some() || out.check.is_some() || out.sel_changed {
633 self.repass();
634 }
635 }
636 self.scr_offset();
637 let y0 = self.box_rect()[1];
638 let k = self.wp.ctx.uiclip;
639 let first = (k[1].wrapping_sub(y0).max(0) / rh).min(n);
640 let end = ((k[3].wrapping_sub(y0).max(0) + rh - 1) / rh).min(n).max(first);
641 if first != 0 {
642 self.place(0, first * rh);
643 }
644 let focus_visible = vev & EV_FOCUS != 0 && self.wp.ctx.focus_visible;
645 let indent = self.dp(18);
646 let tw = self.dp(20);
647 for i in first..end {
648 let row = rows[i as usize];
649 let r = self.place(0, rh);
650 let iid = id.wrapping_add(1).wrapping_add(i as u32);
651 let ev = self.behave(iid, 0);
652 let c = &mut self.wp.ctx;
653 if c.active == iid && c.pressed & (1 << BTN_LEFT) != 0 {
654 c.focus = id;
655 c.focus_visible = false;
656 }
657 if ev & (EV_ACT | EV_RCLICK) != 0 && cur != i {
658 cur = i;
659 st.cur = Some(row.key);
660 out.sel_changed = true;
661 self.repass();
662 }
663 if ev & EV_DOUBLE != 0 {
664 if row.has_children {
665 if !st.expanded.remove(&row.key) {
666 st.expanded.insert(row.key);
667 out.expanded = Some(row.key);
668 } else {
669 out.collapsed = Some(row.key);
670 }
671 self.repass();
672 }
673 out.activated = Some(row.key);
674 }
675 let sel = cur == i;
676 let mut sp = Sp { ev, value: sel as i32, value2: i, flags: if sel && focus_visible { GR_FOCUS } else { 0 }, ..Sp::default() };
677 sp.place(r);
678 sd.grid_row(self, &sp);
679 if sel {
680 self.wp.ctx.last_state |= SS_SELECTED as u32;
681 }
682 self.sem(K_LISTITEM, iid, Some(row.label), i);
683 let mut x = r[0] + self.dp(8) + indent * row.depth as i32;
685 if row.has_children {
686 let cr = [x, r[1] + ((r[3] - tw) >> 1), tw, tw];
687 let tid = iid | 0x4000_0000;
688 let tev = self.hit_rect(tid, cr[0], cr[1], cr[2], cr[3], 0);
689 let open = st.expanded.contains(&row.key);
690 if tev & EV_ACT != 0 {
691 if open {
692 st.expanded.remove(&row.key);
693 out.collapsed = Some(row.key);
694 } else {
695 st.expanded.insert(row.key);
696 out.expanded = Some(row.key);
697 }
698 self.repass();
699 }
700 let mut tsp = Sp { ev: tev, value: open as i32, ..Sp::default() };
701 tsp.place(cr);
702 sd.tree_twisty(self, &tsp);
703 self.wp.ctx.last_rect = cr;
704 self.sem(K_BUTTON, tid, Some(crate::i18n::tr(if open { "Zuklappen" } else { "Aufklappen" })), open as i32);
705 }
706 x += tw + self.dp(4);
707 if let Some(cv) = row.check {
708 let st_ = self.style;
709 let mut csp = Sp::default();
710 let (cw, ch) = st_.checkbox_size(self, &mut csp);
711 let cw = cw.min(self.dp(20)).max(1);
712 let cr = [x, r[1] + ((r[3] - ch) >> 1), cw, ch];
713 let cid = iid | 0x2000_0000;
714 let cev = self.hit_rect(cid, cr[0], cr[1], cr[2], cr[3], 0);
715 if cev & EV_ACT != 0 {
716 out.check = Some((row.key, if cv == 1 { 0 } else { 1 }));
717 self.repass();
718 }
719 self.sp_placed(&mut csp, cev);
720 csp.value = cv as i32;
721 csp.label = None;
722 st_.checkbox(self, &csp);
723 self.wp.ctx.last_rect = cr;
724 if cv != 0 {
725 self.wp.ctx.last_state |= SS_CHECKED as u32;
726 }
727 self.sem(K_CHECKBOX, cid, Some(row.label), cv as i32);
728 x += cw + self.dp(8);
729 }
730 if row.icon != 0 {
731 let px = self.dp(16);
732 let c = self.color(T_TEXT2);
733 crate::stock::icon_in(self, row.icon, [x, r[1], px, r[3]], px, c);
734 x += px + self.dp(8);
735 }
736 let end_x = r[0] + r[2] - self.dp(12);
737 let mut label_end = end_x;
738 if let Some(dt) = row.detail {
739 self.ts_font(TS_CAPTION);
740 let dw = self.text.width(dt);
741 let dx = end_x - dw;
742 let c = self.color(T_TEXT2);
743 crate::stock::text_at(self, TS_CAPTION, dx, r, dt, c, 0);
744 label_end = dx - self.dp(12);
745 }
746 let c = self.color(T_TEXT);
747 self.ts_font(TS_BODY);
748 let full = self.text.width(row.label);
749 let maxw = (label_end - x).max(1);
750 if full > maxw {
751 let (e, _) = self.ellipsize(row.label.as_bytes(), maxw);
752 crate::stock::text_at(self, TS_BODY, x, r, &e, c, 0);
753 } else {
754 crate::stock::text_at(self, TS_BODY, x, r, row.label, c, 0);
755 }
756 }
757 if n - end > 0 {
758 self.place(0, (n - end) * rh);
759 }
760 self.scr_close();
761 let mut stt = 0;
762 if vev & EV_HOVER != 0 {
763 stt |= SS_HOVER as u32;
764 }
765 if vev & EV_FOCUS != 0 {
766 stt |= SS_FOCUSED as u32;
767 }
768 self.wp.ctx.last_state = stt;
769 self.sem(K_LIST, id, None, cur).max = n;
770 out
771 }
772
773 #[cfg_attr(jfgui_dev, track_caller)]
778 pub fn badge(&mut self, kind: u32, label: &str) {
779 let _src = self.src_scope();
780 let label = crate::basic::cstr(label);
781 let icon = self.take_icon();
782 self.take_flags();
783 let sd = self.style.data();
784 let mut short = String::new();
785 let mut sp = Sp { flags: kind & 7, icon, label: Some(label), ..Sp::default() };
786 let (mut w, h) = sd.badge_size(self, &mut sp);
787 let r = self.place(w, h);
788 if w > r[2] {
791 self.ts_font(TS_CAPTION);
792 let mut room = r[2] - (w - self.text.width(label));
793 for _ in 0..4 {
794 self.ts_font(TS_CAPTION);
795 short = self.ellipsize(label.as_bytes(), room).0;
796 let mut t = Sp { label: Some(&short), ..sp };
797 let (sw, _) = sd.badge_size(self, &mut t);
798 w = sw;
799 if sw <= r[2] || short == "…" {
800 break;
801 }
802 room -= sw - r[2];
803 }
804 sp.label = Some(&short);
805 sd.badge_size(self, &mut sp);
806 }
807 let (bw, bh) = (w.min(r[2]), h.min(r[3]));
809 sp.place([r[0], r[1] + ((r[3] - bh) >> 1), bw, bh]);
810 sd.badge(self, &sp);
811 self.wp.ctx.last_state = 0;
812 self.sem(K_TEXT, 0, Some(label), kind as i32);
813 }
814
815 #[cfg_attr(jfgui_dev, track_caller)]
817 pub fn status_dot(&mut self, kind: u32) {
818 let _src = self.src_scope();
819 let s = self.dp(10);
820 let r = self.place(s, s);
821 let sd = self.style.data();
822 let mut sp = Sp { flags: kind & 7, ..Sp::default() };
823 sp.place([r[0] + ((r[2] - s) >> 1), r[1] + ((r[3] - s) >> 1), s, s]);
824 sd.status_dot(self, &sp);
825 }
826
827 #[cfg_attr(jfgui_dev, track_caller)]
829 pub fn meter(&mut self, id: u32, v: i64, max: i64, kind: u32) {
830 let _src = self.src_scope();
831 let r = self.place(self.dp(120), self.dp(8));
832 let (mut a, mut b) = (v.max(0), max.max(0));
834 while b > i32::MAX as i64 {
835 a >>= 4;
836 b >>= 4;
837 }
838 let sd = self.style.data();
839 let mut sp = Sp { flags: kind & 7, value: a as i32, value2: b as i32, ..Sp::default() };
840 sp.place(r);
841 sd.meter(self, &sp);
842 self.wp.ctx.last_state = 0;
843 self.sem(K_PROGRESS, id, None, a as i32).max = b as i32;
844 }
845}