1use alloc::collections::{BTreeMap, BTreeSet};
50use alloc::format;
51use alloc::string::String;
52use alloc::vec::Vec;
53
54use super::auto::{parse_qstring, skip_ws, word_eq, Sel};
55use super::cmd::{cmd_bbox, cmd_hash, render_layer, Cmd};
56use super::consts::*;
57use super::keys::*;
58use super::layout::px2dip_at;
59use super::{App, Sem, Shared, TreeRow, TreeState, Ui};
60use crate::i18n::tr;
61
62pub(crate) const INSPECT_WIN: u32 = 0x7FFF_1000;
65
66const ID_ROOT: u32 = 0x07FE_0000;
69const ID_PICK: u32 = 0x07FE_0001;
70const ID_OUTL: u32 = 0x07FE_0002;
71const ID_ORDER: u32 = 0x07FE_0003;
72const ID_DIRTY: u32 = 0x07FE_0004;
73const ID_COPY: u32 = 0x07FE_0005;
74const ID_TABS: u32 = 0x07FE_0010; const ID_TOKENS: u32 = 0x07FE_0020;
76const ID_TREE: u32 = 0x07FE_1000; const WIN_W: i32 = 400; const WIN_H: i32 = 660;
80const ROW_H: i32 = 22; const KEY_W: i32 = 140; const LABEL_CUT: usize = 48; const C_HOVER: u32 = 0xFFFF_8C00;
86const C_HOVER_FILL: u32 = 0x1CFF_8C00;
87const C_SEL: u32 = 0xFF1E_88E5;
88const C_SEL_FILL: u32 = 0x221E_88E5;
89const C_BOX: u32 = 0xB000_ACC1;
90const C_DIRTY: u32 = 0x00E5_3935; const C_TAG: u32 = 0xF021_2121;
92const C_TAG_TEXT: u32 = 0xFFFF_FFFF;
93const C_ORDER: u32 = 0xE06A_1B9A;
94const FLASH_MS: u32 = 700; const FLASH_MAX: usize = 96;
96
97use super::theme::TOKEN_NAMES;
99const LAYER_NAMES: [&str; L_COUNT] = ["base", "dialog", "popup", "tip"];
100
101pub(crate) type Src = &'static core::panic::Location<'static>;
104
105#[derive(Clone, Copy)]
109pub(crate) struct BoxEv {
110 win: u8,
111 begin: bool,
112 layer: u8,
113 at: u32,
114 id: u32,
115 rect: [i32; 4],
116 src: Option<Src>,
117}
118
119const N_WIDGET: u8 = 0;
121const N_BOX: u8 = 1;
122const N_WIN: u8 = 2;
123
124struct Node {
127 rec: Sem,
128 what: u8,
129 sem: usize,
131 depth: u16,
132 key: u64,
133 kids: bool,
134 src: Option<Src>,
135}
136
137#[derive(Clone, Copy, Default)]
139struct Stats {
140 passes: u32,
141 build_us: u64,
142 render_us: [u64; WIN_MAX],
143 dirty_px: [i64; WIN_MAX],
144 area_px: [i64; WIN_MAX],
145 cmds: [u32; WIN_MAX],
146 bytes: [u32; WIN_MAX],
147 cap: [u32; WIN_MAX],
148}
149
150pub(crate) struct S {
152 pub(crate) armed: bool,
154 pub(crate) on: bool,
156 pub(crate) gone: bool,
158 keys: Vec<u32>,
159 outlines: i32,
160 order: i32,
161 flash: i32,
162 tab: i32,
163 picking: bool,
164 pick_at: Option<(u32, i32, i32)>,
166 up_eat: u32,
168 sel: u64,
170 pub(crate) sel_rec: Option<Sem>,
171 pub(crate) sel_src: Option<Src>,
173 reveal: u8,
176 tree: TreeState,
177 collapsed: BTreeSet<u64>,
178 pub(crate) boxes: Vec<BoxEv>,
180 ptr: [(i32, i32); WIN_MAX],
182 hover_key: [u64; WIN_MAX],
183 ov: [Vec<Cmd>; WIN_MAX],
185 ov_box: [Vec<[i32; 4]>; WIN_MAX],
186 ov_hash: [u64; WIN_MAX],
187 ov_now: bool,
189 t_render: u64,
190 flashes: Vec<(u8, [i32; 4], u32)>,
192 st: Stats,
193 open: Shared<i32>,
194 rect: Shared<WinRect>,
195 work: u32,
197 work_mark: u32,
198 snap: Option<(i32, i32, Vec<u8>)>,
200}
201
202impl Default for S {
203 fn default() -> S {
204 S {
205 armed: false,
206 on: false,
207 gone: false,
208 keys: alloc::vec![VK_I | (MOD_CTRL | MOD_SHIFT) << 16, VK_F12],
209 outlines: 0,
210 order: 0,
211 flash: 0,
212 tab: 0,
213 picking: false,
214 pick_at: None,
215 up_eat: 0,
216 sel: 0,
217 sel_rec: None,
218 sel_src: None,
219 reveal: 0,
220 tree: TreeState::default(),
221 collapsed: BTreeSet::new(),
222 boxes: Vec::new(),
223 ptr: [(-1, -1); WIN_MAX],
224 hover_key: [0; WIN_MAX],
225 ov: Default::default(),
226 ov_box: Default::default(),
227 ov_hash: [0; WIN_MAX],
228 ov_now: false,
229 t_render: 0,
230 flashes: Vec::new(),
231 st: Stats::default(),
232 open: Shared::new(0),
233 rect: Shared::default(),
234 work: 0,
235 work_mark: 0,
236 snap: None,
237 }
238 }
239}
240
241fn mix(h: u64, v: u64) -> u64 {
245 let mut z = (h ^ v).wrapping_add(0x9E37_79B9_7F4A_7C15);
246 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
247 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
248 z ^ (z >> 31)
249}
250
251fn hash_str(s: &str) -> u64 {
253 s.bytes().fold(0xCBF2_9CE4_8422_2325, |h, b| (h ^ b as u64).wrapping_mul(0x0000_0100_0000_01B3))
254}
255
256fn ordinal(seen: &mut BTreeMap<u64, u32>, base: u64) -> u64 {
258 let n = seen.entry(base).or_insert(0);
259 let k = mix(base, *n as u64);
260 *n += 1;
261 k.max(1)
262}
263
264fn inside(r: &[i32; 4], x: i32, y: i32) -> bool {
266 r[2] > 0 && r[3] > 0 && x >= r[0] && y >= r[1] && x < r[0].wrapping_add(r[2]) && y < r[1].wrapping_add(r[3])
267}
268
269fn id_text(id: u32) -> String {
271 if id < 0x1_0000 { format!("#{}", id) } else { format!("#{:#010X}", id).replace("0X", "0x") }
272}
273
274fn cut(l: &str) -> String {
276 let mut s: String = l.chars().take(LABEL_CUT).map(|c| if c == '\n' || c == '\t' { ' ' } else { c }).collect();
277 if l.chars().count() > LABEL_CUT {
278 s.push('…');
279 }
280 s
281}
282
283fn quote(l: &str) -> String {
285 let mut s = String::from("\"");
286 for c in l.chars() {
287 match c {
288 '"' => s.push_str("\\\""),
289 '\\' => s.push_str("\\\\"),
290 '\n' => s.push_str("\\n"),
291 '\t' => s.push_str("\\t"),
292 c => s.push(c),
293 }
294 }
295 s.push('"');
296 s
297}
298
299fn kind_name(k: u16) -> &'static str {
300 KIND_NAMES[if k < K_KIND_COUNT { k as usize } else { 0 }]
301}
302
303fn state_text(st: u16) -> String {
304 let mut s = String::new();
305 for (k, n) in STATE_NAMES.iter().enumerate() {
306 if st & (1 << k) != 0 {
307 if !s.is_empty() {
308 s.push_str(", ");
309 }
310 s.push_str(n);
311 }
312 }
313 s
314}
315
316fn frame(ov: &mut Vec<Cmd>, r: [i32; 4], t: i32, c: u32) {
318 let [x, y, w, h] = r;
319 if w <= 0 || h <= 0 {
320 return;
321 }
322 let t = t.min(w).min(h).max(1);
323 ov.push(Cmd::Rect { x, y, w, h: t, c });
324 ov.push(Cmd::Rect { x, y: y + h - t, w, h: t, c });
325 if h > 2 * t {
326 ov.push(Cmd::Rect { x, y: y + t, w: t, h: h - 2 * t, c });
327 ov.push(Cmd::Rect { x: x + w - t, y: y + t, w: t, h: h - 2 * t, c });
328 }
329}
330
331fn disjoint(rs: &[[i32; 4]]) -> Vec<[i32; 4]> {
333 let mut out: Vec<[i32; 4]> = Vec::new();
334 for r in rs {
335 let mut pieces = alloc::vec![[r[0], r[1], r[0] + r[2], r[1] + r[3]]]; for o in &out {
337 let o = [o[0], o[1], o[0] + o[2], o[1] + o[3]];
338 let mut next = Vec::new();
339 for p in pieces {
340 if p[0] >= o[2] || p[2] <= o[0] || p[1] >= o[3] || p[3] <= o[1] {
341 next.push(p);
342 continue;
343 }
344 let (y0, y1) = (p[1].max(o[1]), p[3].min(o[3]));
346 for q in [[p[0], p[1], p[2], o[1]], [p[0], o[3], p[2], p[3]], [p[0], y0, o[0], y1], [o[2], y0, p[2], y1]] {
347 let q = [q[0].max(p[0]), q[1].max(p[1]), q[2].min(p[2]), q[3].min(p[3])];
348 if q[2] > q[0] && q[3] > q[1] {
349 next.push(q);
350 }
351 }
352 }
353 pieces = next;
354 }
355 out.extend(pieces.iter().map(|p| [p[0], p[1], p[2] - p[0], p[3] - p[1]]));
356 }
357 out
358}
359
360fn same(a: &Sem, b: &Sem) -> bool {
362 a.win == b.win && a.id == b.id && a.kind == b.kind && a.rect == b.rect && a.label == b.label && a.layer == b.layer
363}
364
365impl Ui {
366 pub fn inspect_enable(&mut self, on: bool) {
371 self.m.ins.armed = on;
372 if !on {
373 self.inspect_show(false);
374 }
375 }
376
377 pub fn inspect_show(&mut self, on: bool) {
380 let s = &mut self.m.ins;
381 if on {
382 s.armed = true;
383 }
384 if on == s.on {
385 return;
386 }
387 s.on = on;
388 s.gone = !on;
389 s.boxes.clear();
390 s.picking = false;
391 s.pick_at = None;
392 if on {
393 s.open.set(1);
394 } else {
395 s.open.set(0);
396 s.flashes.clear();
397 s.ptr = [(-1, -1); WIN_MAX];
398 s.hover_key = [0; WIN_MAX];
399 s.ov_hash = [0; WIN_MAX];
400 for v in s.ov.iter_mut() {
401 v.clear();
402 }
403 for v in s.ov_box.iter_mut() {
404 v.clear();
405 }
406 s.work_mark = s.work;
407 self.src_clear(); self.dirty_force_full();
410 for r in self.m.win.recs.iter_mut() {
411 if let Some(p) = r.save.as_deref_mut() {
412 p.dirty.full = true;
413 }
414 }
415 }
416 self.invalidate();
417 self.need_render = true;
418 }
419
420 pub fn inspect_open(&self) -> bool {
422 self.m.ins.on
423 }
424
425 pub fn inspect_keys(&mut self, keys: &[u32]) {
428 self.m.ins.keys = keys.to_vec();
429 }
430
431 pub(crate) fn inspect_args(&mut self, flag: bool) {
433 let env = plat::env("jfgui_INSPECT");
434 let env = env.as_deref().unwrap_or("");
435 if flag || env == "open" {
436 self.inspect_show(true);
437 } else if env.starts_with('1') {
438 self.inspect_enable(true);
439 }
440 }
441
442 fn ins_win(&self) -> i32 {
444 self.uiw_index(INSPECT_WIN)
445 }
446
447 pub(crate) fn ins_event(&mut self, ev: &plat::Event) -> u32 {
452 let w = ev.win as u32 & (WIN_MAX as u32 - 1);
453 let on = self.m.ins.on;
454 match ev.typ {
455 plat::E_KEY_DOWN => {
456 let chord = (ev.data & 0xFFFF) | (ev.mods as u32 & (MOD_SHIFT | MOD_CTRL | MOD_ALT)) << 16;
457 if self.m.ins.keys.contains(&chord) {
458 self.inspect_show(!on);
459 return 1;
460 }
461 if on && self.m.ins.picking && ev.data & 0xFFFF == VK_ESCAPE {
462 self.m.ins.picking = false;
463 self.invalidate();
464 return 1;
465 }
466 0
467 }
468 plat::E_DOWN if on && self.m.ins.picking && w as i32 != self.ins_win() => {
469 let (x, y) = if ev.x != -1 { (ev.x, ev.y) } else { self.m.ins.ptr[w as usize] };
470 self.m.ins.pick_at = Some((w, x, y));
471 self.m.ins.picking = false;
472 self.m.ins.up_eat |= 1 << (ev.btn & 31);
473 self.invalidate();
474 1
475 }
476 plat::E_UP if self.m.ins.up_eat & 1 << (ev.btn & 31) != 0 => {
477 self.m.ins.up_eat &= !(1 << (ev.btn & 31));
478 2
479 }
480 plat::E_MOVE | plat::E_DOWN | plat::E_UP if on && ev.x != -1 => {
481 self.ins_pointer(w, ev.x, ev.y);
482 0
483 }
484 plat::E_LEAVE if on => {
485 self.ins_pointer(w, -1, -1);
486 0
487 }
488 plat::E_FOCUS_OUT => {
489 self.m.ins.up_eat = 0; 0
491 }
492 _ => 0,
493 }
494 }
495
496 fn ins_pointer(&mut self, w: u32, x: i32, y: i32) {
498 for i in 0..WIN_MAX {
499 let p = if i as u32 == w { (x, y) } else { (-1, -1) };
500 if self.m.ins.ptr[i] == p {
501 continue;
502 }
503 self.m.ins.ptr[i] = p;
504 let k = self.ins_at(i as u32, p.0, p.1).map_or(0, |k| {
505 let r = &self.sems[k];
506 mix(mix(r.id as u64, r.kind as u64), (r.rect[0] as u64) << 32 ^ r.rect[1] as u32 as u64).max(1)
507 });
508 if k != self.m.ins.hover_key[i] {
509 self.m.ins.hover_key[i] = k;
510 self.need_render = true;
511 }
512 }
513 }
514
515 pub(crate) fn ins_cmd(&mut self, id: u32) -> bool {
517 match id {
518 UIC_WINCLOSE => {
519 self.inspect_show(false);
520 true
521 }
522 UIC_ESCAPE => {
523 self.m.ins.picking = false;
524 true
525 }
526 UIC_DROP => true,
527 _ => false,
528 }
529 }
530
531 pub(crate) fn ins_box(&mut self, id: u32, r: [i32; 4]) {
533 let rect = self.phys_rect(r);
534 let (layer, at, src) = (self.wp.ctx.cmd_layer_ui as u8, self.sems.len() as u32, self.src_cur());
535 let e = BoxEv { win: self.uiw_cur as u8, begin: true, layer, at, id, rect, src };
536 self.m.ins.boxes.push(e);
537 }
538
539 pub(crate) fn ins_box_end(&mut self) {
541 let e = BoxEv { win: self.uiw_cur as u8, begin: false, layer: 0, at: self.sems.len() as u32, id: 0, rect: [0; 4], src: None };
542 self.m.ins.boxes.push(e);
543 }
544
545 fn ins_at(&self, w: u32, x: i32, y: i32) -> Option<usize> {
550 if x == -1 && y == -1 {
551 return None;
552 }
553 let mut best: Option<(usize, u8, i64)> = None;
554 for (i, r) in self.sems.iter().enumerate() {
555 if r.win as u32 != w || !inside(&r.rect, x, y) {
556 continue;
557 }
558 let area = r.rect[2] as i64 * r.rect[3] as i64;
559 if best.is_none_or(|(_, l, a)| r.layer > l || (r.layer == l && area <= a)) {
560 best = Some((i, r.layer, area));
561 }
562 }
563 best.map(|b| b.0)
564 }
565
566 fn ins_tree(&self) -> Vec<Node> {
569 let iw = self.ins_win();
570 let mut wins: Vec<u32> = Vec::new();
571 for r in &self.sems {
572 let w = r.win as u32;
573 if w as i32 != iw && !wins.contains(&w) {
574 wins.push(w);
575 }
576 }
577 wins.sort_unstable();
578 let multi = wins.len() > 1;
579 let mut out: Vec<Node> = Vec::new();
580 let mut seen: BTreeMap<u64, u32> = BTreeMap::new();
581 for &w in &wins {
582 let base = multi as u16;
583 let wkey = mix(1, w as u64).max(1);
584 if multi {
585 let fb = self.win_fb(w);
586 let title = if w == 0 { String::from(tr("Hauptfenster")) } else { String::from(self.uiw_title(w)) };
587 let rec = Sem { kind: K_BOX, rect: [0, 0, fb.w, fb.h], label: Some(title), win: w as u8, id: self.uiw_rec_id(w), ..Sem::default() };
588 out.push(Node { rec, what: N_WIN, sem: usize::MAX, depth: 0, key: wkey, kids: false, src: None });
589 }
590 let evs: Vec<BoxEv> = self.m.ins.boxes.iter().filter(|e| e.win as u32 == w).copied().collect();
591 let mut ei = 0;
592 let mut stack: Vec<(usize, bool)> = Vec::new();
594 let n = self.sems.len();
595 for k in 0..=n {
596 while ei < evs.len() && (k == n || evs[ei].at as usize <= k) {
597 let e = evs[ei];
598 ei += 1;
599 if e.begin {
600 let depth = base + stack.len() as u16;
601 let key = ordinal(&mut seen, mix(mix(2, w as u64), e.id as u64));
602 let rec = Sem { id: e.id, kind: K_BOX, rect: e.rect, layer: e.layer, win: w as u8, ..Sem::default() };
603 out.push(Node { rec, what: N_BOX, sem: usize::MAX, depth, key, kids: false, src: e.src });
604 stack.push((out.len() - 1, false));
605 } else if let Some((i, used)) = stack.pop() {
606 if !used {
607 out.truncate(i); }
609 }
610 }
611 if k == n {
612 break;
613 }
614 let r = &self.sems[k];
615 if r.win as u32 != w {
616 continue;
617 }
618 for s in stack.iter_mut().rev() {
619 if s.1 {
620 break; }
622 s.1 = true;
623 }
624 let depth = base + stack.len() as u16;
625 let parent = stack.last().map_or(wkey, |s| out[s.0].key);
626 let b = if r.id != 0 {
627 mix(mix(3, w as u64), (r.id as u64) << 16 | r.kind as u64)
628 } else {
629 mix(mix(4, parent), hash_str(r.label.as_deref().unwrap_or("")) ^ r.kind as u64)
630 };
631 let key = ordinal(&mut seen, b);
632 out.push(Node { rec: r.clone(), what: N_WIDGET, sem: k, depth, key, kids: false, src: self.src_of(k) });
633 }
634 while let Some((i, used)) = stack.pop() {
635 if !used {
636 out.truncate(i);
637 }
638 }
639 }
640 for i in 0..out.len() {
641 out[i].kids = out.get(i + 1).is_some_and(|n| n.depth > out[i].depth);
642 }
643 out
644 }
645
646 fn ins_pick_node(nodes: &[Node], w: u32, x: i32, y: i32) -> Option<usize> {
649 let mut best: Option<(usize, u8, u8, i64)> = None; for (i, n) in nodes.iter().enumerate() {
651 if n.what == N_WIN || n.rec.win as u32 != w || !inside(&n.rec.rect, x, y) {
652 continue;
653 }
654 let wd = (n.what == N_WIDGET) as u8;
655 let area = n.rec.rect[2] as i64 * n.rec.rect[3] as i64;
656 if best.is_none_or(|(_, bw, l, a)| (wd, n.rec.layer) > (bw, l) || ((wd, n.rec.layer) == (bw, l) && area <= a)) {
657 best = Some((i, wd, n.rec.layer, area));
658 }
659 }
660 best.map(|b| b.0)
661 }
662
663 fn ins_select(&mut self, nodes: &[Node], i: usize) {
665 self.m.ins.sel = nodes[i].key;
666 self.m.ins.sel_rec = Some(nodes[i].rec.clone());
667 self.m.ins.sel_src = nodes[i].src;
668 let mut d = nodes[i].depth;
669 for n in nodes[..i].iter().rev() {
670 if d == 0 {
671 break;
672 }
673 if n.depth < d {
674 self.m.ins.collapsed.remove(&n.key);
675 d = n.depth;
676 }
677 }
678 self.m.ins.reveal = 3;
679 self.m.ins.tab = 0;
680 }
681
682 pub(crate) fn ins_selector(&self, r: &Sem) -> String {
685 let iw = self.ins_win();
686 if let Some(l) = r.label.as_deref().filter(|l| !l.is_empty() && l.len() < 250) {
687 let n = self.sems.iter().filter(|s| s.win as i32 != iw && s.kind == r.kind && s.label.as_deref() == Some(l)).count();
688 if n == 1 {
689 return format!("{}:{}", kind_name(r.kind), quote(l));
690 }
691 }
692 if r.id != 0 {
693 return id_text(r.id);
694 }
695 let dpi = self.win_dpi(r.win as u32);
696 format!("{},{}", px2dip_at(r.rect[0] + r.rect[2] / 2, dpi), px2dip_at(r.rect[1] + r.rect[3] / 2, dpi))
697 }
698
699 fn ins_copy(&mut self) {
701 if let Some(r) = self.m.ins.sel_rec.clone() {
702 let s = self.ins_selector(&r);
703 self.clip_set(CLIP_TEXT, s.as_bytes());
704 }
705 }
706
707 pub(crate) fn inspect_frame(&mut self) {
712 if self.m.ins.open.get() == 0 {
713 self.inspect_show(false); return;
715 }
716 self.m.ins.work = self.m.ins.work.wrapping_add(1);
717 let nodes = self.ins_tree();
718 if let Some((w, x, y)) = self.m.ins.pick_at.take() {
719 if let Some(i) = Ui::ins_pick_node(&nodes, w, x, y) {
720 self.ins_select(&nodes, i);
721 self.repass();
722 }
723 }
724 let sel = self.m.ins.sel;
725 let n = nodes.iter().find(|n| sel != 0 && n.key == sel);
726 self.m.ins.sel_rec = n.map(|n| n.rec.clone());
727 self.m.ins.sel_src = n.and_then(|n| n.src);
728 if self.m.ins.rect.get().flags & WRF_POS == 0 {
729 self.ins_place();
730 }
731 let open = self.m.ins.open.clone();
732 let rect = self.m.ins.rect.clone();
733 self.ied_plain(true); if self.window_begin(INSPECT_WIN, tr("Inspektor"), Some(&open), WF_TOOL | WF_RESIZABLE, Some(&rect)) {
735 self.ins_ui(&nodes);
736 }
737 self.window_end();
738 self.ied_plain(false);
739 self.ied_after(); }
741
742 fn ins_place(&mut self) {
745 let dpi = self.win_dpi(0);
746 let fb = self.win_fb(0);
747 let cw = px2dip_at(fb.w, dpi);
748 let outside = self.headless
749 || match (plat::win_get_rect(0), plat::win_work_area(0)) {
750 (Some(r), Some(a)) => r[0] + r[2] + super::layout::dp_at(WIN_W + 24, dpi) <= a[0] + a[2],
751 _ => false,
752 };
753 let rc = if outside { WinRect::new(cw + 12, 0, WIN_W, WIN_H, WRF_POS) } else { WinRect::new((cw - WIN_W - 16).max(0), 48, WIN_W, WIN_H, WRF_POS) };
754 self.m.ins.rect.set(rc);
755 }
756
757 fn ins_ui(&mut self, nodes: &[Node]) {
759 self.next_size(-1, -1);
760 self.box_begin(ID_ROOT, BOX_V | pad(8), 6);
761 self.box_begin(0, BOX_H | BOX_WRAP | BOX_XCENTER, 10);
763 let mut v = self.m.ins.picking as i32;
764 if self.toggle_button(ID_PICK, tr("Auswählen"), &mut v) & EV_ACT != 0 {
765 self.m.ins.picking = v != 0;
766 }
767 let mut v = self.m.ins.outlines;
768 self.checkbox(ID_OUTL, tr("Umrisse"), &mut v);
769 self.m.ins.outlines = v;
770 let mut v = self.m.ins.order;
771 self.checkbox(ID_ORDER, tr("Tab-Reihenfolge"), &mut v);
772 self.m.ins.order = v;
773 let mut v = self.m.ins.flash;
774 self.checkbox(ID_DIRTY, tr("Neu gezeichnet"), &mut v);
775 self.m.ins.flash = v;
776 self.box_end();
777 if self.m.ins.picking {
778 self.text(TS_CAPTION | TX_ACCENT, tr("Klicken Sie auf ein Widget (Esc beendet)"));
779 }
780 self.hr_ins_note(); let mut tab = self.m.ins.tab;
782 let mut tabs = alloc::vec![tr("Widgets"), tr("Bildaufbau"), tr("Farben")];
783 if super::inspect_edit::EDIT {
784 tabs.push(tr("Darstellung")); }
786 self.segmented(ID_TABS, &mut tab, &tabs);
787 self.m.ins.tab = tab;
788 match tab {
789 1 => self.ins_stats(nodes),
790 2 => {
791 if !self.ied_colors() {
792 self.ins_colors(); }
794 }
795 3 => self.ied_look(),
796 _ => {
797 self.ins_tree_ui(nodes);
798 self.ins_props();
799 }
800 }
801 if self.kb_chord(VK_C, MOD_CTRL) {
803 self.ins_copy();
804 }
805 self.ied_keys(); if self.key(VK_ESCAPE) {
807 self.m.ins.picking = false;
808 self.repass();
809 }
810 self.box_end();
811 }
812
813 fn ins_row_text(&self, n: &Node) -> (String, Option<String>) {
815 match n.what {
816 N_WIN => (n.rec.label.clone().unwrap_or_default(), (n.rec.id != 0).then(|| id_text(n.rec.id))),
817 N_BOX => (String::from("box"), Some(id_text(n.rec.id))),
818 _ => {
819 let mut s = String::from(kind_name(n.rec.kind));
820 if let Some(l) = n.rec.label.as_deref().filter(|l| !l.is_empty()) {
821 s.push(' ');
822 s.push_str("e(&cut(l)));
823 }
824 (s, (n.rec.id != 0).then(|| id_text(n.rec.id)))
825 }
826 }
827 }
828
829 fn ins_tree_ui(&mut self, nodes: &[Node]) {
831 let mut vis: Vec<usize> = Vec::new();
832 let mut hide: Option<u16> = None;
833 for (i, n) in nodes.iter().enumerate() {
834 if let Some(d) = hide {
835 if n.depth > d {
836 continue;
837 }
838 hide = None;
839 }
840 vis.push(i);
841 if n.kids && self.m.ins.collapsed.contains(&n.key) {
842 hide = Some(n.depth);
843 }
844 }
845 let texts: Vec<(String, Option<String>)> = vis.iter().map(|&i| self.ins_row_text(&nodes[i])).collect();
846 let rows: Vec<TreeRow> = vis
847 .iter()
848 .zip(texts.iter())
849 .map(|(&i, (l, d))| TreeRow {
850 key: nodes[i].key,
851 depth: nodes[i].depth,
852 has_children: nodes[i].kids,
853 check: None,
854 icon: 0,
855 label: l,
856 detail: d.as_deref(),
857 })
858 .collect();
859 let mut st = core::mem::take(&mut self.m.ins.tree);
860 st.expanded = nodes.iter().filter(|n| n.kids && !self.m.ins.collapsed.contains(&n.key)).map(|n| n.key).collect();
861 let sel = self.m.ins.sel;
862 st.cur = (sel != 0).then_some(sel);
863 self.next_size(-1, -1);
864 let ev = self.tree(ID_TREE, &mut st, &rows, ROW_H);
865 if self.m.ins.reveal > 0 {
868 self.m.ins.reveal -= 1;
869 let vh = self.last_rect()[3];
870 if let Some(p) = rows.iter().position(|r| r.key == sel) {
871 let rh = self.dp(ROW_H);
872 let y = p as i32 * rh;
873 let s = self.slot(ID_TREE);
874 if super::list::into_view(y, rh, s.scrolly, vh) != 0 {
875 s.scrolly = (y - (vh - rh) / 2).max(0); s.adur = s.scrolly as u32;
877 self.repass();
878 }
879 }
880 }
881 if let Some(k) = ev.collapsed {
882 self.m.ins.collapsed.insert(k);
883 }
884 if let Some(k) = ev.expanded {
885 self.m.ins.collapsed.remove(&k);
886 }
887 if ev.sel_changed {
888 if let Some(k) = st.cur {
889 self.m.ins.sel = k;
890 let n = nodes.iter().find(|n| n.key == k);
891 self.m.ins.sel_rec = n.map(|n| n.rec.clone());
892 self.m.ins.sel_src = n.and_then(|n| n.src);
893 }
894 }
895 self.m.ins.tree = st;
896 }
897
898 fn ins_props(&mut self) {
900 self.separator();
901 let Some(r) = self.m.ins.sel_rec.clone() else {
902 self.text(TS_BODY | TX_SECONDARY, tr("Nichts ausgewählt"));
903 return;
904 };
905 let w = r.win as u32;
906 let dpi = self.win_dpi(w);
907 let d = |v: i32| px2dip_at(v, dpi);
908 self.key_value(tr("Art"), kind_name(r.kind), KEY_W);
909 let mut id = if r.id == 0 { String::from("0") } else { format!("{:#010X} ({})", r.id, r.id).replace("0X", "0x") };
910 if let Some(n) = self.id_name(r.id) {
911 id.push_str(&format!(" \"{}\"", n));
912 }
913 self.key_value(tr("Kennung"), &id, KEY_W);
914 let label = r.label.clone().unwrap_or_default();
915 self.key_value(tr("Beschriftung"), &cut(&label), KEY_W);
916 let rc = r.rect;
917 self.key_value(tr("Rechteck"), &format!("{}, {} {} x {} DIP", d(rc[0]), d(rc[1]), d(rc[2]), d(rc[3])), KEY_W);
918 self.key_value("", &format!("{}, {} {} x {} px", rc[0], rc[1], rc[2], rc[3]), KEY_W);
919 let value = if r.min != 0 { format!("{} / {} .. {}", r.value, r.min, r.max) } else { format!("{} / {}", r.value, r.max) };
920 self.key_value(tr("Wert"), &value, KEY_W);
921 let st = state_text(r.state);
922 self.key_value(tr("Zustand"), if st.is_empty() { "-" } else { &st }, KEY_W);
923 let layer = format!("{} ({})", r.layer, LAYER_NAMES[(r.layer as usize) & (L_COUNT - 1)]);
924 self.key_value(tr("Ebene"), &layer, KEY_W);
925 let win = if w == 0 { String::from(tr("Hauptfenster")) } else { format!("{} \"{}\"", self.uiw_rec_id(w), self.uiw_title(w)) };
926 self.key_value(tr("Fenster"), &win, KEY_W);
927 let sel = self.ins_selector(&r);
929 self.box_begin(0, BOX_H | BOX_XCENTER, 12);
930 self.next_size(KEY_W, 0);
931 self.text(TS_BODY | TX_SECONDARY | TX_ELLIPSIS, tr("Selektor"));
932 self.next_size(-1, 0);
933 self.text(TS_BODY | TX_ELLIPSIS, &sel);
934 self.next_flags(ST_COMPACT);
935 if self.button(ID_COPY, tr("Kopieren")) & EV_ACT != 0 {
936 self.ins_copy();
937 }
938 self.box_end();
939 if super::inspect_src::SRC {
940 let src = self.m.ins.sel_src;
941 self.isrc_props(src); }
943 self.ied_operate(&r); }
945
946 fn ins_stats(&mut self, nodes: &[Node]) {
948 let iw = self.ins_win();
949 let s = self.m.ins.st;
950 let app = |i: usize| i as i32 != iw;
951 let sum = |v: &[u64; WIN_MAX]| (0..WIN_MAX).filter(|&i| app(i)).map(|i| v[i]).sum::<u64>();
952 let sumi = |v: &[i64; WIN_MAX]| (0..WIN_MAX).filter(|&i| app(i)).map(|i| v[i]).sum::<i64>();
953 let sumu = |v: &[u32; WIN_MAX]| (0..WIN_MAX).filter(|&i| app(i)).map(|i| v[i] as u64).sum::<u64>();
954 self.key_value(tr("Durchgänge"), &format!("{}", s.passes), KEY_W);
955 self.key_value(tr("Aufbau"), &format!("{} µs", s.build_us), KEY_W);
956 self.key_value(tr("Zeichnen"), &format!("{} µs", sum(&s.render_us)), KEY_W);
957 let (px, area) = (sumi(&s.dirty_px), sumi(&s.area_px).max(1));
958 let pm = (px * 1000 / area) as i32;
959 self.key_value(tr("Neu gezeichnete Fläche"), &format!("{} px ({}.{} %)", px, pm / 10, pm % 10), KEY_W);
960 let cmds = format!("{} ({} / {} KiB)", sumu(&s.cmds), sumu(&s.bytes) / 1024, sumu(&s.cap) / 1024);
961 self.key_value(tr("Zeichenbefehle"), &cmds, KEY_W);
962 let widgets = nodes.iter().filter(|n| n.what == N_WIDGET).count();
963 self.key_value(tr("Widgets"), &format!("{}", widgets), KEY_W);
964 let (n, nmax, b, bmax) = self.text.gc_usage();
965 self.key_value(tr("Glyphen-Cache"), &format!("{} / {} ({} / {} KiB)", n, nmax, b / 1024, bmax / 1024), KEY_W);
966 }
967
968 fn ins_colors(&mut self) {
970 self.next_size(-1, -1);
971 self.scroll_begin(ID_TOKENS, SC_V);
972 let stroke = self.color(T_CTRL_STROKE);
973 for (i, name) in TOKEN_NAMES.iter().enumerate() {
974 let c = self.theme[i];
975 self.box_begin(0, BOX_H | BOX_XCENTER, 10);
976 self.next_size(32, 18);
977 let r = self.place(0, 0);
978 let h2 = r[2] / 2;
980 self.draw_color(stroke);
981 self.draw_rect(r[0], r[1], r[2], r[3]);
982 self.draw_color(0xFFFF_FFFF);
983 self.draw_rect(r[0] + 1, r[1] + 1, h2 - 1, r[3] - 2);
984 self.draw_color(0xFF00_0000);
985 self.draw_rect(r[0] + h2, r[1] + 1, r[2] - h2 - 1, r[3] - 2);
986 self.draw_color(c);
987 self.draw_rect(r[0] + 1, r[1] + 1, r[2] - 2, r[3] - 2);
988 self.next_size(160, 0);
989 self.text(TS_CAPTION, &format!("T_{}", name));
990 self.text(TS_CAPTION | TX_SECONDARY, &format!("#{:08X}", c));
991 self.box_end();
992 }
993 self.scroll_end();
994 }
995
996 pub(crate) fn ins_render_begin(&mut self, n: i32) -> i32 {
1002 self.m.ins.ov_now = false;
1003 let w = self.uiw_cur;
1004 if w as i32 == self.ins_win() {
1005 return n; }
1007 let wi = w as usize & (WIN_MAX - 1);
1008 self.m.ins.work = self.m.ins.work.wrapping_add(1);
1009 self.m.ins.t_render = plat::time_us();
1010 let (fw, fh) = (self.wp.rt.fb.w, self.wp.rt.fb.h);
1011 if n != 0 {
1013 let area = fw as i64 * fh as i64;
1014 let px = if n < 0 { area } else { self.drects[..n as usize].iter().map(|r| r[2] as i64 * r[3] as i64).sum() };
1015 self.m.ins.st.dirty_px[wi] = px;
1016 self.m.ins.st.area_px[wi] = area;
1017 if self.m.ins.flash != 0 {
1018 let now = self.now_ms;
1019 let rs: Vec<[i32; 4]> = if n < 0 { alloc::vec![[0, 0, fw, fh]] } else { self.drects[..n as usize].to_vec() };
1020 let f = &mut self.m.ins.flashes;
1021 f.retain(|e| now.wrapping_sub(e.2) < FLASH_MS);
1022 for r in rs {
1023 f.push((w as u8, r, now));
1024 }
1025 if f.len() > FLASH_MAX {
1026 let k = f.len() - FLASH_MAX;
1027 f.drain(..k);
1028 }
1029 }
1030 }
1031 {
1032 let c = &self.wp.cmd;
1033 let st = &mut self.m.ins.st;
1034 st.cmds[wi] = c.layers.iter().map(|l| l.len() as u32).sum();
1035 st.bytes[wi] = c.len.iter().sum();
1036 st.cap[wi] = c.cap.iter().sum();
1037 }
1038 let ov = self.ins_overlay(w);
1040 let h = ov.iter().fold(0xCBF2_9CE4_8422_2325u64, |h, c| mix(h, cmd_hash(c)));
1041 let boxes: Vec<[i32; 4]> = ov
1042 .iter()
1043 .filter_map(cmd_bbox)
1044 .filter_map(|b| {
1045 let (x0, y0, x1, y1) = (b[0].max(0), b[1].max(0), b[2].min(fw), b[3].min(fh));
1046 (x1 > x0 && y1 > y0).then_some([x0, y0, x1 - x0, y1 - y0])
1047 })
1048 .collect();
1049 let mut n = n;
1050 if h != self.m.ins.ov_hash[wi] && n >= 0 {
1051 let old = core::mem::take(&mut self.m.ins.ov_box[wi]);
1052 self.drects.truncate(n as usize);
1053 if n as usize + old.len() + boxes.len() > super::dirty::DRECTS_MAX {
1054 self.drects.clear();
1055 n = -1;
1056 } else {
1057 self.drects.extend_from_slice(&old);
1058 self.drects.extend_from_slice(&boxes);
1059 n += (old.len() + boxes.len()) as i32;
1060 }
1061 }
1062 self.m.ins.ov_now = n != 0 && !ov.is_empty();
1063 self.m.ins.ov[wi] = ov;
1064 self.m.ins.ov_box[wi] = boxes;
1065 self.m.ins.ov_hash[wi] = h;
1066 if self.m.ins.flash != 0 && !self.m.ins.flashes.is_empty() {
1068 self.animating = true;
1069 }
1070 n
1071 }
1072
1073 pub(crate) fn ins_render_end(&mut self) {
1077 let w = self.uiw_cur;
1078 if w as i32 == self.ins_win() {
1079 return;
1080 }
1081 let wi = w as usize & (WIN_MAX - 1);
1082 if self.m.ins.ov_now {
1083 let n = self.wp.rt.present_n;
1084 let pieces = if n < 0 { None } else { Some(disjoint(&self.drects[..(n as usize).min(self.drects.len())])) };
1085 self.ins_draw(false, pieces.as_deref());
1086 }
1087 self.m.ins.st.render_us[wi] = plat::time_us().saturating_sub(self.m.ins.t_render);
1088 }
1089
1090 pub(crate) fn ins_verify(&mut self, shadow: bool) {
1093 if self.uiw_cur as i32 != self.ins_win() {
1094 self.ins_draw(shadow, None);
1095 }
1096 }
1097
1098 fn ins_draw(&mut self, shadow: bool, clip: Option<&[[i32; 4]]>) {
1101 let wi = self.uiw_cur as usize & (WIN_MAX - 1);
1102 if self.m.ins.ov[wi].is_empty() {
1103 return;
1104 }
1105 let icon_style = (self.style, self.color(T_ACCENT), self.theme_dark, self.color(T_TEXT_DISABLED), self.wp.rt.dpi as u32);
1106 let ov = core::mem::take(&mut self.m.ins.ov[wi]);
1107 let Ui { wp, dc, text, icon_hook, .. } = self;
1108 let sf = if shadow { &mut wp.rt.shadow } else { &mut wp.rt.fb };
1109 dc.begin(sf);
1110 match clip {
1111 None => render_layer(&ov, 0, dc, sf, text, icon_hook, icon_style),
1112 Some(rs) => {
1113 for r in rs {
1114 dc.clip_push(r[0], r[1], r[2], r[3]);
1115 render_layer(&ov, 0, dc, sf, text, icon_hook, icon_style);
1116 dc.clip_pop();
1117 }
1118 }
1119 }
1120 dc.end();
1121 self.m.ins.ov[wi] = ov;
1122 }
1123
1124 fn ins_overlay(&mut self, w: u32) -> Vec<Cmd> {
1126 let mut ov = Vec::new();
1127 let (fw, fh) = (self.wp.rt.fb.w, self.wp.rt.fb.h);
1128 let hair = self.hair();
1129 let thick = self.dp(2).max(1);
1130 if self.m.ins.outlines != 0 {
1132 for e in self.m.ins.boxes.iter().filter(|e| e.begin && e.win as u32 == w) {
1133 frame(&mut ov, e.rect, hair, C_BOX);
1134 }
1135 }
1136 if self.m.ins.flash != 0 {
1138 let now = self.now_ms;
1139 for &(fwin, r, t) in &self.m.ins.flashes {
1140 let age = now.wrapping_sub(t);
1141 if fwin as u32 != w || age >= FLASH_MS {
1142 continue;
1143 }
1144 let k = FLASH_MS - age;
1145 ov.push(Cmd::Rect { x: r[0], y: r[1], w: r[2], h: r[3], c: (0x48 * k / FLASH_MS) << 24 | C_DIRTY });
1146 frame(&mut ov, r, hair, (0x60 + 0x9F * k / FLASH_MS) << 24 | C_DIRTY);
1147 }
1148 }
1149 self.ts_font(TS_CAPTION);
1150 let (font, size) = self.text.get_font();
1151 let (asc, desc) = (self.text.ascent(), self.text.descent());
1152 let pad = self.dp(4);
1153 if let Some(r) = self.m.ins.sel_rec.as_ref().filter(|r| r.win as u32 == w) {
1155 ov.push(Cmd::Rect { x: r.rect[0], y: r.rect[1], w: r.rect[2], h: r.rect[3], c: C_SEL_FILL });
1156 frame(&mut ov, r.rect, thick, C_SEL);
1157 }
1158 let mut tag = Vec::new();
1160 let (px, py) = self.m.ins.ptr[w as usize & (WIN_MAX - 1)];
1161 if let Some(k) = self.ins_at(w, px, py) {
1162 let r = self.sems[k].clone();
1163 ov.push(Cmd::Rect { x: r.rect[0], y: r.rect[1], w: r.rect[2], h: r.rect[3], c: C_HOVER_FILL });
1164 frame(&mut ov, r.rect, hair.max(thick / 2), C_HOVER);
1165 let mut s = String::from(kind_name(r.kind));
1166 if r.id != 0 {
1167 s.push_str(" ");
1168 s.push_str(&id_text(r.id));
1169 if let Some(n) = self.id_name(r.id) {
1170 s.push_str(&format!(" (\"{}\")", n));
1171 }
1172 }
1173 if let Some(l) = r.label.as_deref().filter(|l| !l.is_empty()) {
1174 s.push_str(" ");
1175 s.push_str("e(&cut(l)));
1176 }
1177 let dpi = self.wp.rt.dpi;
1178 s.push_str(&format!(" {} x {}", px2dip_at(r.rect[2], dpi), px2dip_at(r.rect[3], dpi)));
1179 let st = state_text(r.state);
1180 if !st.is_empty() {
1181 s.push_str(&format!(" [{}]", st));
1182 }
1183 let tw = self.text.width(&s).min(fw - 2 * pad).max(1);
1184 let (bw, bh) = (tw + 2 * pad, asc + desc + 2 * pad);
1185 let x = r.rect[0].clamp(0, (fw - bw).max(0));
1186 let above = r.rect[1] - bh - hair;
1187 let y = if above >= 0 { above } else { (r.rect[1] + r.rect[3] + hair).min((fh - bh).max(0)) };
1188 let t = hair as u16;
1189 tag.push(Cmd::RRect { x, y, w: bw, h: bh, r: self.dp(3) as u16, t, fill: C_TAG, stroke: C_HOVER, stroke2: C_HOVER });
1190 tag.push(Cmd::Text { x: x + pad, y: y + pad + asc, font: font as u16, size, c: C_TAG_TEXT, maxw: tw, s: s.into_bytes() });
1191 }
1192 if self.m.ins.order != 0 {
1194 let c = &self.wp.ctx;
1195 let list = &c.hits[c.hit_cur ^ 1];
1196 let (stops, _) = super::kbnav::stops(list, c.focus, c.kb.gmem());
1197 let rects: Vec<(usize, [i32; 4])> = stops
1199 .iter()
1200 .enumerate()
1201 .map(|(k, &i)| {
1202 let h = &list[i];
1203 (k, if self.rtl { [fw - h.x1, h.y0, h.x1 - h.x0, h.y1 - h.y0] } else { [h.x0, h.y0, h.x1 - h.x0, h.y1 - h.y0] })
1204 })
1205 .filter(|(_, r)| r[2] > 0 && r[3] > 0)
1206 .collect();
1207 for (k, r) in rects.iter() {
1208 let s = format!("{}", k + 1);
1209 let tw = self.text.width(&s);
1210 let bh = asc + desc + 2;
1211 let bw = (tw + 2 * pad).max(bh);
1212 let (x, y) = ((r[0] - bw / 2).clamp(0, (fw - bw).max(0)), (r[1] - bh / 2).clamp(0, (fh - bh).max(0)));
1214 frame(&mut ov, *r, hair, C_ORDER);
1215 ov.push(Cmd::RRect { x, y, w: bw, h: bh, r: (bh / 2) as u16, t: 0, fill: C_ORDER, stroke: 0, stroke2: 0 });
1216 ov.push(Cmd::Text { x: x + (bw - tw) / 2, y: y + 1 + asc, font: font as u16, size, c: C_TAG_TEXT, maxw: 0, s: s.into_bytes() });
1217 }
1218 }
1219 ov.append(&mut tag); ov
1221 }
1222
1223 pub(crate) fn ins_passes(&mut self, passes: u32, build_us: u64) {
1227 self.m.ins.st.passes = passes;
1228 self.m.ins.st.build_us = build_us;
1229 }
1230
1231 pub(crate) fn ins_gone(&mut self) {
1234 self.m.ins.gone = false;
1235 self.uiw_use(0);
1236 self.window_destroy(INSPECT_WIN);
1237 }
1238
1239 pub(crate) fn sc_inspect(&mut self, app: &mut dyn App, a: &[u8]) {
1243 const BAD: &[u8] = b"inspect on|off|toggle|arm|disarm|pick|select S|option outlines|order|dirty on|off|tab widgets|frame|colors|look|copy|snap|expect ...|set|reset|undo|redo|save|list|do ...|editor \"C\"|open [dry]";
1244 let w = |n: &str| word_eq(a, n).is_some();
1245 if w("on") || w("off") || w("toggle") {
1246 let on = if w("toggle") { !self.m.ins.on } else { w("on") };
1247 self.inspect_show(on);
1248 self.run_passes(app);
1249 } else if w("arm") || w("disarm") {
1250 self.inspect_enable(w("arm"));
1251 self.run_passes(app);
1252 } else if w("pick") {
1253 if !self.m.ins.on {
1254 self.fail(b"the inspector is closed");
1255 return;
1256 }
1257 self.m.ins.picking = true;
1258 self.invalidate();
1259 self.run_passes(app);
1260 } else if let Some(r) = word_eq(a, "select") {
1261 let r = skip_ws(r);
1262 if word_eq(r, "none").is_some() {
1263 self.m.ins.sel = 0;
1264 self.m.ins.sel_rec = None;
1265 self.m.ins.sel_src = None;
1266 } else {
1267 let Some(k) = self.ins_resolve(r) else { return };
1268 let nodes = self.ins_tree();
1269 match nodes.iter().position(|n| n.sem == k) {
1270 Some(i) => self.ins_select(&nodes, i),
1271 None => {
1272 self.fail(b"not in the inspector's tree");
1273 return;
1274 }
1275 }
1276 }
1277 self.invalidate();
1278 self.run_passes(app);
1279 } else if let Some(r) = word_eq(a, "option") {
1280 let r = skip_ws(r);
1281 let v = word_eq(skip_ws(super::auto::next_word(r)), "on").is_some() as i32;
1282 if word_eq(r, "outlines").is_some() {
1283 self.m.ins.outlines = v;
1284 } else if word_eq(r, "order").is_some() {
1285 self.m.ins.order = v;
1286 } else if word_eq(r, "dirty").is_some() {
1287 self.m.ins.flash = v;
1288 } else {
1289 self.fail(b"inspect option outlines|order|dirty on|off");
1290 return;
1291 }
1292 self.invalidate();
1293 self.run_passes(app);
1294 } else if let Some(r) = word_eq(a, "tab") {
1295 let r = skip_ws(r);
1296 let n = if super::inspect_edit::EDIT { 4 } else { 3 };
1298 let t = ["widgets", "frame", "colors", "look"][..n].iter().position(|n| word_eq(r, n).is_some());
1299 match t {
1300 Some(t) => self.m.ins.tab = t as i32,
1301 None => {
1302 self.fail(if n == 4 { &b"inspect tab widgets|frame|colors|look"[..] } else { b"inspect tab widgets|frame|colors" });
1303 return;
1304 }
1305 }
1306 self.invalidate();
1307 self.run_passes(app);
1308 } else if w("copy") {
1309 self.ins_copy();
1310 self.run_passes(app);
1311 } else if w("snap") {
1312 self.render();
1313 self.gp_settle_all();
1314 let fb = &self.wp.rt.fb;
1315 self.m.ins.snap = Some((fb.w, fb.h, fb.px.clone()));
1316 } else if let Some(r) = word_eq(a, "expect") {
1317 self.sc_inspect_expect(skip_ws(r));
1318 } else if !self.isrc_script(app, a) && !self.ied_script(app, a) {
1319 self.fail(BAD);
1322 }
1323 }
1324
1325 fn ins_resolve(&mut self, p: &[u8]) -> Option<usize> {
1327 match self.resolve(p).0 {
1328 None => {
1329 self.sel_fail();
1330 None
1331 }
1332 Some(Sel::Point) => {
1333 self.fail(b"a widget selector (#id or a label), not a point");
1334 None
1335 }
1336 Some(Sel::Rec(k)) => {
1337 if self.sems[k].win as i32 == self.ins_win() {
1338 self.fail(b"a widget of the inspector itself");
1339 return None;
1340 }
1341 Some(k)
1342 }
1343 }
1344 }
1345
1346 fn sc_inspect_expect(&mut self, a: &[u8]) {
1348 let w = |n: &str| word_eq(a, n).is_some();
1349 if w("open") || w("closed") {
1350 if self.m.ins.on != w("open") {
1351 self.fail(if self.m.ins.on { b"the inspector is open" } else { b"the inspector is closed" });
1352 }
1353 } else if let Some(r) = word_eq(a, "pick") {
1354 let want = word_eq(skip_ws(r), "on").is_some();
1355 if self.m.ins.picking != want {
1356 self.fail(if want { b"no pick" } else { b"picking" });
1357 }
1358 } else if let Some(r) = word_eq(a, "flash") {
1359 let now = self.now_ms;
1360 let live = self.m.ins.flash != 0 && self.m.ins.flashes.iter().any(|e| now.wrapping_sub(e.2) < FLASH_MS);
1361 if live != word_eq(skip_ws(r), "on").is_some() {
1362 self.fail(if live { b"repainted rectangles are shown" } else { b"no repainted rectangle is shown" });
1363 }
1364 } else if w("idle") {
1365 let d = self.m.ins.work.wrapping_sub(self.m.ins.work_mark);
1366 self.m.ins.work_mark = self.m.ins.work;
1367 let (b, s) = (self.m.ins.boxes.len(), self.src_count());
1368 if d != 0 || b != 0 || s != 0 || self.m.ins.ov.iter().any(|v| !v.is_empty()) {
1369 self.fail(format!("the inspector worked {} times, {} boxes, {} sources recorded", d, b, s).as_bytes());
1370 }
1371 } else if w("same") {
1372 self.render();
1373 let fb = &self.wp.rt.fb;
1374 let d = match &self.m.ins.snap {
1375 None => Err(String::from("no inspect snap")),
1376 Some((sw, sh, _)) if (*sw, *sh) != (fb.w, fb.h) => Err(format!("the size is {}x{}, was {}x{}", fb.w, fb.h, sw, sh)),
1377 Some((_, _, px)) => {
1378 let n = px.chunks_exact(4).zip(fb.px.chunks_exact(4)).filter(|(a, b)| a[..3] != b[..3]).count();
1379 if n == 0 { Ok(()) } else { Err(format!("{} pixels differ", n)) }
1380 }
1381 };
1382 if let Err(e) = d {
1383 self.fail(e.as_bytes());
1384 }
1385 } else if w("excluded") {
1386 let iw = self.ins_win();
1387 let own = iw >= 0 && self.sems.iter().any(|r| r.win as i32 == iw);
1388 if !own {
1389 self.fail(b"the inspector's window has no records");
1390 } else if self.ins_tree().iter().any(|n| n.rec.win as i32 == iw) {
1391 self.fail(b"the tree holds records of the inspector's window");
1392 }
1393 } else if let Some(r) = word_eq(a, "selected") {
1394 let r = skip_ws(r);
1395 if word_eq(r, "none").is_some() {
1396 if self.m.ins.sel_rec.is_some() {
1397 self.fail(b"a widget is selected");
1398 }
1399 return;
1400 }
1401 let Some(k) = self.ins_resolve(r) else { return };
1402 match &self.m.ins.sel_rec {
1403 Some(s) if same(s, &self.sems[k]) => {}
1404 Some(s) => {
1405 let m = format!("selected: {} {} {:?}", kind_name(s.kind), id_text(s.id), s.label.as_deref().unwrap_or(""));
1406 self.fail(m.as_bytes());
1407 }
1408 None => self.fail(b"nothing selected"),
1409 }
1410 } else if let Some(r) = word_eq(a, "hover") {
1411 let r = skip_ws(r);
1412 let h = (0..WIN_MAX as u32).find_map(|i| {
1413 let (x, y) = self.m.ins.ptr[i as usize];
1414 self.ins_at(i, x, y)
1415 });
1416 if word_eq(r, "none").is_some() {
1417 if h.is_some() {
1418 self.fail(b"a widget is under the pointer");
1419 }
1420 return;
1421 }
1422 let Some(k) = self.ins_resolve(r) else { return };
1423 if h != Some(k) {
1424 let m = match h {
1425 Some(i) => format!("under the pointer: {} {}", kind_name(self.sems[i].kind), id_text(self.sems[i].id)),
1426 None => String::from("nothing under the pointer"),
1427 };
1428 self.fail(m.as_bytes());
1429 }
1430 } else if let Some(r) = word_eq(a, "selector") {
1431 let Some((want, _)) = parse_qstring(skip_ws(r), 256) else {
1432 self.fail(b"expected a quoted string");
1433 return;
1434 };
1435 let have = self.m.ins.sel_rec.clone().map(|s| self.ins_selector(&s)).unwrap_or_default();
1436 if have.as_bytes() != want.as_slice() {
1437 self.fail(format!("selector is {}", have).as_bytes());
1438 }
1439 } else if let Some(r) = word_eq(a, "copied") {
1440 let Some((want, _)) = parse_qstring(skip_ws(r), 256) else {
1441 self.fail(b"expected a quoted string");
1442 return;
1443 };
1444 let have = self.clip_read(CLIP_TEXT).unwrap_or_default();
1445 if have != want {
1446 self.fail(format!("the clipboard holds {:?}", String::from_utf8_lossy(&have)).as_bytes());
1447 }
1448 } else if !self.isrc_expect(a) && !self.ied_expect(a) {
1449 self.fail(b"inspect expect open|closed|pick on|off|flash on|off|idle|same|excluded|selected S|hover S|selector \"s\"|copied \"s\"|source \"s\"|sources \"s\"|opened \"s\"|note \"s\"");
1452 }
1453 }
1454
1455 pub(crate) fn ins_carry(&self) -> (bool, bool, i32, [i32; 3], Option<String>) {
1460 let s = &self.m.ins;
1461 let sel = s.sel_rec.as_ref().map(|r| self.ins_selector(r));
1462 (s.armed, s.on, s.tab, [s.outlines, s.order, s.flash], sel)
1463 }
1464
1465 pub(crate) fn ins_restore(&mut self, app: &mut dyn App, armed: bool, on: bool, tab: i32, opts: [i32; 3], sel: Option<&str>) {
1468 self.inspect_enable(armed || on);
1469 self.inspect_show(on);
1470 [self.m.ins.outlines, self.m.ins.order, self.m.ins.flash] = opts;
1471 self.run_passes(app);
1472 if let Some(Sel::Rec(k)) = sel.filter(|_| on).and_then(|p| self.resolve(p.as_bytes()).0) {
1473 let nodes = self.ins_tree();
1474 if let Some(i) = nodes.iter().position(|n| n.sem == k) {
1475 self.ins_select(&nodes, i);
1476 }
1477 }
1478 self.m.ins.tab = tab.clamp(0, 2);
1479 self.invalidate();
1480 self.run_passes(app);
1481 }
1482}