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 state_text(st: u16) -> String {
300 let mut s = String::new();
301 for (k, n) in STATE_NAMES.iter().enumerate() {
302 if st & (1 << k) != 0 {
303 if !s.is_empty() {
304 s.push_str(", ");
305 }
306 s.push_str(n);
307 }
308 }
309 s
310}
311
312fn frame(ov: &mut Vec<Cmd>, r: [i32; 4], t: i32, c: u32) {
314 let [x, y, w, h] = r;
315 if w <= 0 || h <= 0 {
316 return;
317 }
318 let t = t.min(w).min(h).max(1);
319 ov.push(Cmd::Rect { x, y, w, h: t, c });
320 ov.push(Cmd::Rect { x, y: y + h - t, w, h: t, c });
321 if h > 2 * t {
322 ov.push(Cmd::Rect { x, y: y + t, w: t, h: h - 2 * t, c });
323 ov.push(Cmd::Rect { x: x + w - t, y: y + t, w: t, h: h - 2 * t, c });
324 }
325}
326
327fn disjoint(rs: &[[i32; 4]]) -> Vec<[i32; 4]> {
329 let mut out: Vec<[i32; 4]> = Vec::new();
330 for r in rs {
331 let mut pieces = alloc::vec![[r[0], r[1], r[0] + r[2], r[1] + r[3]]]; for o in &out {
333 let o = [o[0], o[1], o[0] + o[2], o[1] + o[3]];
334 let mut next = Vec::new();
335 for p in pieces {
336 if p[0] >= o[2] || p[2] <= o[0] || p[1] >= o[3] || p[3] <= o[1] {
337 next.push(p);
338 continue;
339 }
340 let (y0, y1) = (p[1].max(o[1]), p[3].min(o[3]));
342 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]] {
343 let q = [q[0].max(p[0]), q[1].max(p[1]), q[2].min(p[2]), q[3].min(p[3])];
344 if q[2] > q[0] && q[3] > q[1] {
345 next.push(q);
346 }
347 }
348 }
349 pieces = next;
350 }
351 out.extend(pieces.iter().map(|p| [p[0], p[1], p[2] - p[0], p[3] - p[1]]));
352 }
353 out
354}
355
356fn same(a: &Sem, b: &Sem) -> bool {
358 a.win == b.win && a.id == b.id && a.kind == b.kind && a.rect == b.rect && a.label == b.label && a.layer == b.layer
359}
360
361impl Ui {
362 pub fn inspect_enable(&mut self, on: bool) {
367 self.m.ins.armed = on;
368 if !on {
369 self.inspect_show(false);
370 }
371 }
372
373 pub fn inspect_show(&mut self, on: bool) {
376 let s = &mut self.m.ins;
377 if on {
378 s.armed = true;
379 }
380 if on == s.on {
381 return;
382 }
383 s.on = on;
384 s.gone = !on;
385 s.boxes.clear();
386 s.picking = false;
387 s.pick_at = None;
388 if on {
389 s.open.set(1);
390 } else {
391 s.open.set(0);
392 s.flashes.clear();
393 s.ptr = [(-1, -1); WIN_MAX];
394 s.hover_key = [0; WIN_MAX];
395 s.ov_hash = [0; WIN_MAX];
396 for v in s.ov.iter_mut() {
397 v.clear();
398 }
399 for v in s.ov_box.iter_mut() {
400 v.clear();
401 }
402 s.work_mark = s.work;
403 self.src_clear(); self.dirty_force_full();
406 for r in self.m.win.recs.iter_mut() {
407 if let Some(p) = r.save.as_deref_mut() {
408 p.dirty.full = true;
409 }
410 }
411 }
412 self.invalidate();
413 self.need_render = true;
414 }
415
416 pub fn inspect_open(&self) -> bool {
418 self.m.ins.on
419 }
420
421 pub fn inspect_keys(&mut self, keys: &[u32]) {
424 self.m.ins.keys = keys.to_vec();
425 }
426
427 pub(crate) fn inspect_args(&mut self, flag: bool) {
429 let env = plat::env("jfgui_INSPECT");
430 let env = env.as_deref().unwrap_or("");
431 if flag || env == "open" {
432 self.inspect_show(true);
433 } else if env.starts_with('1') {
434 self.inspect_enable(true);
435 }
436 }
437
438 fn ins_win(&self) -> i32 {
440 self.uiw_index(INSPECT_WIN)
441 }
442
443 pub(crate) fn ins_event(&mut self, ev: &plat::Event) -> u32 {
448 let w = ev.win as u32 & (WIN_MAX as u32 - 1);
449 let on = self.m.ins.on;
450 match ev.typ {
451 plat::E_KEY_DOWN => {
452 let chord = (ev.data & 0xFFFF) | (ev.mods as u32 & (MOD_SHIFT | MOD_CTRL | MOD_ALT)) << 16;
453 if self.m.ins.keys.contains(&chord) {
454 self.inspect_show(!on);
455 return 1;
456 }
457 if on && self.m.ins.picking && ev.data & 0xFFFF == VK_ESCAPE {
458 self.m.ins.picking = false;
459 self.invalidate();
460 return 1;
461 }
462 0
463 }
464 plat::E_DOWN if on && self.m.ins.picking && w as i32 != self.ins_win() => {
465 let (x, y) = if ev.x != -1 { (ev.x, ev.y) } else { self.m.ins.ptr[w as usize] };
466 self.m.ins.pick_at = Some((w, x, y));
467 self.m.ins.picking = false;
468 self.m.ins.up_eat |= 1 << (ev.btn & 31);
469 self.invalidate();
470 1
471 }
472 plat::E_UP if self.m.ins.up_eat & 1 << (ev.btn & 31) != 0 => {
473 self.m.ins.up_eat &= !(1 << (ev.btn & 31));
474 2
475 }
476 plat::E_MOVE | plat::E_DOWN | plat::E_UP if on && ev.x != -1 => {
477 self.ins_pointer(w, ev.x, ev.y);
478 0
479 }
480 plat::E_LEAVE if on => {
481 self.ins_pointer(w, -1, -1);
482 0
483 }
484 plat::E_FOCUS_OUT => {
485 self.m.ins.up_eat = 0; 0
487 }
488 _ => 0,
489 }
490 }
491
492 fn ins_pointer(&mut self, w: u32, x: i32, y: i32) {
494 for i in 0..WIN_MAX {
495 let p = if i as u32 == w { (x, y) } else { (-1, -1) };
496 if self.m.ins.ptr[i] == p {
497 continue;
498 }
499 self.m.ins.ptr[i] = p;
500 let k = self.ins_at(i as u32, p.0, p.1).map_or(0, |k| {
501 let r = &self.sems[k];
502 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)
503 });
504 if k != self.m.ins.hover_key[i] {
505 self.m.ins.hover_key[i] = k;
506 self.need_render = true;
507 }
508 }
509 }
510
511 pub(crate) fn ins_cmd(&mut self, id: u32) -> bool {
513 match id {
514 UIC_WINCLOSE => {
515 self.inspect_show(false);
516 true
517 }
518 UIC_ESCAPE => {
519 self.m.ins.picking = false;
520 true
521 }
522 UIC_DROP => true,
523 _ => false,
524 }
525 }
526
527 pub(crate) fn ins_box(&mut self, id: u32, r: [i32; 4]) {
529 let rect = self.phys_rect(r);
530 let (layer, at, src) = (self.wp.ctx.cmd_layer_ui as u8, self.sems.len() as u32, self.src_cur());
531 let e = BoxEv { win: self.uiw_cur as u8, begin: true, layer, at, id, rect, src };
532 self.m.ins.boxes.push(e);
533 }
534
535 pub(crate) fn ins_box_end(&mut self) {
537 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 };
538 self.m.ins.boxes.push(e);
539 }
540
541 fn ins_at(&self, w: u32, x: i32, y: i32) -> Option<usize> {
546 if x == -1 && y == -1 {
547 return None;
548 }
549 let mut best: Option<(usize, u8, i64)> = None;
550 for (i, r) in self.sems.iter().enumerate() {
551 if r.win as u32 != w || !inside(&r.rect, x, y) {
552 continue;
553 }
554 let area = r.rect[2] as i64 * r.rect[3] as i64;
555 if best.is_none_or(|(_, l, a)| r.layer > l || (r.layer == l && area <= a)) {
556 best = Some((i, r.layer, area));
557 }
558 }
559 best.map(|b| b.0)
560 }
561
562 fn ins_tree(&self) -> Vec<Node> {
565 let iw = self.ins_win();
566 let mut wins: Vec<u32> = Vec::new();
567 for r in &self.sems {
568 let w = r.win as u32;
569 if w as i32 != iw && !wins.contains(&w) {
570 wins.push(w);
571 }
572 }
573 wins.sort_unstable();
574 let multi = wins.len() > 1;
575 let mut out: Vec<Node> = Vec::new();
576 let mut seen: BTreeMap<u64, u32> = BTreeMap::new();
577 for &w in &wins {
578 let base = multi as u16;
579 let wkey = mix(1, w as u64).max(1);
580 if multi {
581 let fb = self.win_fb(w);
582 let title = if w == 0 { String::from(tr("Hauptfenster")) } else { String::from(self.uiw_title(w)) };
583 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() };
584 out.push(Node { rec, what: N_WIN, sem: usize::MAX, depth: 0, key: wkey, kids: false, src: None });
585 }
586 let evs: Vec<BoxEv> = self.m.ins.boxes.iter().filter(|e| e.win as u32 == w).copied().collect();
587 let mut ei = 0;
588 let mut stack: Vec<(usize, bool)> = Vec::new();
590 let n = self.sems.len();
591 for k in 0..=n {
592 while ei < evs.len() && (k == n || evs[ei].at as usize <= k) {
593 let e = evs[ei];
594 ei += 1;
595 if e.begin {
596 let depth = base + stack.len() as u16;
597 let key = ordinal(&mut seen, mix(mix(2, w as u64), e.id as u64));
598 let rec = Sem { id: e.id, kind: K_BOX, rect: e.rect, layer: e.layer, win: w as u8, ..Sem::default() };
599 out.push(Node { rec, what: N_BOX, sem: usize::MAX, depth, key, kids: false, src: e.src });
600 stack.push((out.len() - 1, false));
601 } else if let Some((i, used)) = stack.pop() {
602 if !used {
603 out.truncate(i); }
605 }
606 }
607 if k == n {
608 break;
609 }
610 let r = &self.sems[k];
611 if r.win as u32 != w {
612 continue;
613 }
614 for s in stack.iter_mut().rev() {
615 if s.1 {
616 break; }
618 s.1 = true;
619 }
620 let depth = base + stack.len() as u16;
621 let parent = stack.last().map_or(wkey, |s| out[s.0].key);
622 let b = if r.id != 0 {
623 mix(mix(3, w as u64), (r.id as u64) << 16 | r.kind as u64)
624 } else {
625 mix(mix(4, parent), hash_str(r.label.as_deref().unwrap_or("")) ^ r.kind as u64)
626 };
627 let key = ordinal(&mut seen, b);
628 out.push(Node { rec: r.clone(), what: N_WIDGET, sem: k, depth, key, kids: false, src: self.src_of(k) });
629 }
630 while let Some((i, used)) = stack.pop() {
631 if !used {
632 out.truncate(i);
633 }
634 }
635 }
636 for i in 0..out.len() {
637 out[i].kids = out.get(i + 1).is_some_and(|n| n.depth > out[i].depth);
638 }
639 out
640 }
641
642 fn ins_pick_node(nodes: &[Node], w: u32, x: i32, y: i32) -> Option<usize> {
645 let mut best: Option<(usize, u8, u8, i64)> = None; for (i, n) in nodes.iter().enumerate() {
647 if n.what == N_WIN || n.rec.win as u32 != w || !inside(&n.rec.rect, x, y) {
648 continue;
649 }
650 let wd = (n.what == N_WIDGET) as u8;
651 let area = n.rec.rect[2] as i64 * n.rec.rect[3] as i64;
652 if best.is_none_or(|(_, bw, l, a)| (wd, n.rec.layer) > (bw, l) || ((wd, n.rec.layer) == (bw, l) && area <= a)) {
653 best = Some((i, wd, n.rec.layer, area));
654 }
655 }
656 best.map(|b| b.0)
657 }
658
659 fn ins_select(&mut self, nodes: &[Node], i: usize) {
661 self.m.ins.sel = nodes[i].key;
662 self.m.ins.sel_rec = Some(nodes[i].rec.clone());
663 self.m.ins.sel_src = nodes[i].src;
664 let mut d = nodes[i].depth;
665 for n in nodes[..i].iter().rev() {
666 if d == 0 {
667 break;
668 }
669 if n.depth < d {
670 self.m.ins.collapsed.remove(&n.key);
671 d = n.depth;
672 }
673 }
674 self.m.ins.reveal = 3;
675 self.m.ins.tab = 0;
676 }
677
678 pub(crate) fn ins_selector(&self, r: &Sem) -> String {
681 let iw = self.ins_win();
682 if let Some(l) = r.label.as_deref().filter(|l| !l.is_empty() && l.len() < 250) {
683 let n = self.sems.iter().filter(|s| s.win as i32 != iw && s.kind == r.kind && s.label.as_deref() == Some(l)).count();
684 if n == 1 {
685 return format!("{}:{}", kind_name(r.kind), quote(l));
686 }
687 }
688 if r.id != 0 {
689 return id_text(r.id);
690 }
691 let dpi = self.win_dpi(r.win as u32);
692 format!("{},{}", px2dip_at(r.rect[0] + r.rect[2] / 2, dpi), px2dip_at(r.rect[1] + r.rect[3] / 2, dpi))
693 }
694
695 fn ins_copy(&mut self) {
697 if let Some(r) = self.m.ins.sel_rec.clone() {
698 let s = self.ins_selector(&r);
699 self.clip_set(CLIP_TEXT, s.as_bytes());
700 }
701 }
702
703 pub(crate) fn inspect_frame(&mut self) {
708 if self.m.ins.open.get() == 0 {
709 self.inspect_show(false); return;
711 }
712 self.m.ins.work = self.m.ins.work.wrapping_add(1);
713 let nodes = self.ins_tree();
714 if let Some((w, x, y)) = self.m.ins.pick_at.take() {
715 if let Some(i) = Ui::ins_pick_node(&nodes, w, x, y) {
716 self.ins_select(&nodes, i);
717 self.repass();
718 }
719 }
720 let sel = self.m.ins.sel;
721 let n = nodes.iter().find(|n| sel != 0 && n.key == sel);
722 self.m.ins.sel_rec = n.map(|n| n.rec.clone());
723 self.m.ins.sel_src = n.and_then(|n| n.src);
724 if self.m.ins.rect.get().flags & WRF_POS == 0 {
725 self.ins_place();
726 }
727 let open = self.m.ins.open.clone();
728 let rect = self.m.ins.rect.clone();
729 self.ied_plain(true); if self.window_begin(INSPECT_WIN, tr("Inspektor"), Some(&open), WF_TOOL | WF_RESIZABLE, Some(&rect)) {
731 self.ins_ui(&nodes);
732 }
733 self.window_end();
734 self.ied_plain(false);
735 self.ied_after(); }
737
738 fn ins_place(&mut self) {
741 let dpi = self.win_dpi(0);
742 let fb = self.win_fb(0);
743 let cw = px2dip_at(fb.w, dpi);
744 let outside = self.headless
745 || match (plat::win_get_rect(0), plat::win_work_area(0)) {
746 (Some(r), Some(a)) => r[0] + r[2] + super::layout::dp_at(WIN_W + 24, dpi) <= a[0] + a[2],
747 _ => false,
748 };
749 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) };
750 self.m.ins.rect.set(rc);
751 }
752
753 fn ins_ui(&mut self, nodes: &[Node]) {
755 self.next_size(-1, -1);
756 self.box_begin(ID_ROOT, BOX_V | pad(8), 6);
757 self.box_begin(0, BOX_H | BOX_WRAP | BOX_XCENTER, 10);
759 let mut v = self.m.ins.picking as i32;
760 if self.toggle_button(ID_PICK, tr("Auswählen"), &mut v) & EV_ACT != 0 {
761 self.m.ins.picking = v != 0;
762 }
763 let mut v = self.m.ins.outlines;
764 self.checkbox(ID_OUTL, tr("Umrisse"), &mut v);
765 self.m.ins.outlines = v;
766 let mut v = self.m.ins.order;
767 self.checkbox(ID_ORDER, tr("Tab-Reihenfolge"), &mut v);
768 self.m.ins.order = v;
769 let mut v = self.m.ins.flash;
770 self.checkbox(ID_DIRTY, tr("Neu gezeichnet"), &mut v);
771 self.m.ins.flash = v;
772 self.box_end();
773 if self.m.ins.picking {
774 self.text(TS_CAPTION | TX_ACCENT, tr("Klicken Sie auf ein Widget (Esc beendet)"));
775 }
776 self.hr_ins_note(); let mut tab = self.m.ins.tab;
778 let mut tabs = alloc::vec![tr("Widgets"), tr("Bildaufbau"), tr("Farben")];
779 if super::inspect_edit::EDIT {
780 tabs.push(tr("Darstellung")); }
782 self.segmented(ID_TABS, &mut tab, &tabs);
783 self.m.ins.tab = tab;
784 match tab {
785 1 => self.ins_stats(nodes),
786 2 => {
787 if !self.ied_colors() {
788 self.ins_colors(); }
790 }
791 3 => self.ied_look(),
792 _ => {
793 self.ins_tree_ui(nodes);
794 self.ins_props();
795 }
796 }
797 if self.kb_chord(VK_C, MOD_CTRL) {
799 self.ins_copy();
800 }
801 self.ied_keys(); if self.key(VK_ESCAPE) {
803 self.m.ins.picking = false;
804 self.repass();
805 }
806 self.box_end();
807 }
808
809 fn ins_row_text(&self, n: &Node) -> (String, Option<String>) {
811 match n.what {
812 N_WIN => (n.rec.label.clone().unwrap_or_default(), (n.rec.id != 0).then(|| id_text(n.rec.id))),
813 N_BOX => (String::from("box"), Some(id_text(n.rec.id))),
814 _ => {
815 let mut s = String::from(kind_name(n.rec.kind));
816 if let Some(l) = n.rec.label.as_deref().filter(|l| !l.is_empty()) {
817 s.push(' ');
818 s.push_str("e(&cut(l)));
819 }
820 (s, (n.rec.id != 0).then(|| id_text(n.rec.id)))
821 }
822 }
823 }
824
825 fn ins_tree_ui(&mut self, nodes: &[Node]) {
827 let mut vis: Vec<usize> = Vec::new();
828 let mut hide: Option<u16> = None;
829 for (i, n) in nodes.iter().enumerate() {
830 if let Some(d) = hide {
831 if n.depth > d {
832 continue;
833 }
834 hide = None;
835 }
836 vis.push(i);
837 if n.kids && self.m.ins.collapsed.contains(&n.key) {
838 hide = Some(n.depth);
839 }
840 }
841 let texts: Vec<(String, Option<String>)> = vis.iter().map(|&i| self.ins_row_text(&nodes[i])).collect();
842 let rows: Vec<TreeRow> = vis
843 .iter()
844 .zip(texts.iter())
845 .map(|(&i, (l, d))| TreeRow {
846 key: nodes[i].key,
847 depth: nodes[i].depth,
848 has_children: nodes[i].kids,
849 check: None,
850 icon: 0,
851 label: l,
852 detail: d.as_deref(),
853 })
854 .collect();
855 let mut st = core::mem::take(&mut self.m.ins.tree);
856 st.expanded = nodes.iter().filter(|n| n.kids && !self.m.ins.collapsed.contains(&n.key)).map(|n| n.key).collect();
857 let sel = self.m.ins.sel;
858 st.cur = (sel != 0).then_some(sel);
859 self.next_size(-1, -1);
860 let ev = self.tree(ID_TREE, &mut st, &rows, ROW_H);
861 if self.m.ins.reveal > 0 {
864 self.m.ins.reveal -= 1;
865 let vh = self.last_rect()[3];
866 if let Some(p) = rows.iter().position(|r| r.key == sel) {
867 let rh = self.dp(ROW_H);
868 let y = p as i32 * rh;
869 let s = self.slot(ID_TREE);
870 if super::list::into_view(y, rh, s.scrolly, vh) != 0 {
871 s.scrolly = (y - (vh - rh) / 2).max(0); s.adur = s.scrolly as u32;
873 self.repass();
874 }
875 }
876 }
877 if let Some(k) = ev.collapsed {
878 self.m.ins.collapsed.insert(k);
879 }
880 if let Some(k) = ev.expanded {
881 self.m.ins.collapsed.remove(&k);
882 }
883 if ev.sel_changed {
884 if let Some(k) = st.cur {
885 self.m.ins.sel = k;
886 let n = nodes.iter().find(|n| n.key == k);
887 self.m.ins.sel_rec = n.map(|n| n.rec.clone());
888 self.m.ins.sel_src = n.and_then(|n| n.src);
889 }
890 }
891 self.m.ins.tree = st;
892 }
893
894 fn ins_props(&mut self) {
896 self.separator();
897 let Some(r) = self.m.ins.sel_rec.clone() else {
898 self.text(TS_BODY | TX_SECONDARY, tr("Nichts ausgewählt"));
899 return;
900 };
901 let w = r.win as u32;
902 let dpi = self.win_dpi(w);
903 let d = |v: i32| px2dip_at(v, dpi);
904 self.key_value(tr("Art"), kind_name(r.kind), KEY_W);
905 let mut id = if r.id == 0 { String::from("0") } else { format!("{:#010X} ({})", r.id, r.id).replace("0X", "0x") };
906 if let Some(n) = self.id_name(r.id) {
907 id.push_str(&format!(" \"{}\"", n));
908 }
909 self.key_value(tr("Kennung"), &id, KEY_W);
910 let label = r.label.clone().unwrap_or_default();
911 self.key_value(tr("Beschriftung"), &cut(&label), KEY_W);
912 let rc = r.rect;
913 self.key_value(tr("Rechteck"), &format!("{}, {} {} x {} DIP", d(rc[0]), d(rc[1]), d(rc[2]), d(rc[3])), KEY_W);
914 self.key_value("", &format!("{}, {} {} x {} px", rc[0], rc[1], rc[2], rc[3]), KEY_W);
915 let value = if r.min != 0 { format!("{} / {} .. {}", r.value, r.min, r.max) } else { format!("{} / {}", r.value, r.max) };
916 self.key_value(tr("Wert"), &value, KEY_W);
917 let st = state_text(r.state);
918 self.key_value(tr("Zustand"), if st.is_empty() { "-" } else { &st }, KEY_W);
919 let layer = format!("{} ({})", r.layer, LAYER_NAMES[(r.layer as usize) & (L_COUNT - 1)]);
920 self.key_value(tr("Ebene"), &layer, KEY_W);
921 let win = if w == 0 { String::from(tr("Hauptfenster")) } else { format!("{} \"{}\"", self.uiw_rec_id(w), self.uiw_title(w)) };
922 self.key_value(tr("Fenster"), &win, KEY_W);
923 let sel = self.ins_selector(&r);
925 self.box_begin(0, BOX_H | BOX_XCENTER, 12);
926 self.next_size(KEY_W, 0);
927 self.text(TS_BODY | TX_SECONDARY | TX_ELLIPSIS, tr("Selektor"));
928 self.next_size(-1, 0);
929 self.text(TS_BODY | TX_ELLIPSIS, &sel);
930 self.next_flags(ST_COMPACT);
931 if self.button(ID_COPY, tr("Kopieren")) & EV_ACT != 0 {
932 self.ins_copy();
933 }
934 self.box_end();
935 if super::inspect_src::SRC {
936 let src = self.m.ins.sel_src;
937 self.isrc_props(src); }
939 self.ied_operate(&r); }
941
942 fn ins_stats(&mut self, nodes: &[Node]) {
944 let iw = self.ins_win();
945 let s = self.m.ins.st;
946 let app = |i: usize| i as i32 != iw;
947 let sum = |v: &[u64; WIN_MAX]| (0..WIN_MAX).filter(|&i| app(i)).map(|i| v[i]).sum::<u64>();
948 let sumi = |v: &[i64; WIN_MAX]| (0..WIN_MAX).filter(|&i| app(i)).map(|i| v[i]).sum::<i64>();
949 let sumu = |v: &[u32; WIN_MAX]| (0..WIN_MAX).filter(|&i| app(i)).map(|i| v[i] as u64).sum::<u64>();
950 self.key_value(tr("Durchgänge"), &format!("{}", s.passes), KEY_W);
951 self.key_value(tr("Aufbau"), &format!("{} µs", s.build_us), KEY_W);
952 self.key_value(tr("Zeichnen"), &format!("{} µs", sum(&s.render_us)), KEY_W);
953 let (px, area) = (sumi(&s.dirty_px), sumi(&s.area_px).max(1));
954 let pm = (px * 1000 / area) as i32;
955 self.key_value(tr("Neu gezeichnete Fläche"), &format!("{} px ({}.{} %)", px, pm / 10, pm % 10), KEY_W);
956 let cmds = format!("{} ({} / {} KiB)", sumu(&s.cmds), sumu(&s.bytes) / 1024, sumu(&s.cap) / 1024);
957 self.key_value(tr("Zeichenbefehle"), &cmds, KEY_W);
958 let widgets = nodes.iter().filter(|n| n.what == N_WIDGET).count();
959 self.key_value(tr("Widgets"), &format!("{}", widgets), KEY_W);
960 let (n, nmax, b, bmax) = self.text.gc_usage();
961 self.key_value(tr("Glyphen-Cache"), &format!("{} / {} ({} / {} KiB)", n, nmax, b / 1024, bmax / 1024), KEY_W);
962 }
963
964 fn ins_colors(&mut self) {
966 self.next_size(-1, -1);
967 self.scroll_begin(ID_TOKENS, SC_V);
968 let stroke = self.color(T_CTRL_STROKE);
969 for (i, name) in TOKEN_NAMES.iter().enumerate() {
970 let c = self.theme[i];
971 self.box_begin(0, BOX_H | BOX_XCENTER, 10);
972 self.next_size(32, 18);
973 let r = self.place(0, 0);
974 let h2 = r[2] / 2;
976 self.draw_color(stroke);
977 self.draw_rect(r[0], r[1], r[2], r[3]);
978 self.draw_color(0xFFFF_FFFF);
979 self.draw_rect(r[0] + 1, r[1] + 1, h2 - 1, r[3] - 2);
980 self.draw_color(0xFF00_0000);
981 self.draw_rect(r[0] + h2, r[1] + 1, r[2] - h2 - 1, r[3] - 2);
982 self.draw_color(c);
983 self.draw_rect(r[0] + 1, r[1] + 1, r[2] - 2, r[3] - 2);
984 self.next_size(160, 0);
985 self.text(TS_CAPTION, &format!("T_{}", name));
986 self.text(TS_CAPTION | TX_SECONDARY, &format!("#{:08X}", c));
987 self.box_end();
988 }
989 self.scroll_end();
990 }
991
992 pub(crate) fn ins_render_begin(&mut self, n: i32) -> i32 {
998 self.m.ins.ov_now = false;
999 let w = self.uiw_cur;
1000 if w as i32 == self.ins_win() {
1001 return n; }
1003 let wi = w as usize & (WIN_MAX - 1);
1004 self.m.ins.work = self.m.ins.work.wrapping_add(1);
1005 self.m.ins.t_render = plat::time_us();
1006 let (fw, fh) = (self.wp.rt.fb.w, self.wp.rt.fb.h);
1007 if n != 0 {
1009 let area = fw as i64 * fh as i64;
1010 let px = if n < 0 { area } else { self.drects[..n as usize].iter().map(|r| r[2] as i64 * r[3] as i64).sum() };
1011 self.m.ins.st.dirty_px[wi] = px;
1012 self.m.ins.st.area_px[wi] = area;
1013 if self.m.ins.flash != 0 {
1014 let now = self.now_ms;
1015 let rs: Vec<[i32; 4]> = if n < 0 { alloc::vec![[0, 0, fw, fh]] } else { self.drects[..n as usize].to_vec() };
1016 let f = &mut self.m.ins.flashes;
1017 f.retain(|e| now.wrapping_sub(e.2) < FLASH_MS);
1018 for r in rs {
1019 f.push((w as u8, r, now));
1020 }
1021 if f.len() > FLASH_MAX {
1022 let k = f.len() - FLASH_MAX;
1023 f.drain(..k);
1024 }
1025 }
1026 }
1027 {
1028 let c = &self.wp.cmd;
1029 let st = &mut self.m.ins.st;
1030 st.cmds[wi] = c.layers.iter().map(|l| l.len() as u32).sum();
1031 st.bytes[wi] = c.len.iter().sum();
1032 st.cap[wi] = c.cap.iter().sum();
1033 }
1034 let ov = self.ins_overlay(w);
1036 let h = ov.iter().fold(0xCBF2_9CE4_8422_2325u64, |h, c| mix(h, cmd_hash(c)));
1037 let boxes: Vec<[i32; 4]> = ov
1038 .iter()
1039 .filter_map(cmd_bbox)
1040 .filter_map(|b| {
1041 let (x0, y0, x1, y1) = (b[0].max(0), b[1].max(0), b[2].min(fw), b[3].min(fh));
1042 (x1 > x0 && y1 > y0).then_some([x0, y0, x1 - x0, y1 - y0])
1043 })
1044 .collect();
1045 let mut n = n;
1046 if h != self.m.ins.ov_hash[wi] && n >= 0 {
1047 let old = core::mem::take(&mut self.m.ins.ov_box[wi]);
1048 self.drects.truncate(n as usize);
1049 if n as usize + old.len() + boxes.len() > super::dirty::DRECTS_MAX {
1050 self.drects.clear();
1051 n = -1;
1052 } else {
1053 self.drects.extend_from_slice(&old);
1054 self.drects.extend_from_slice(&boxes);
1055 n += (old.len() + boxes.len()) as i32;
1056 }
1057 }
1058 self.m.ins.ov_now = n != 0 && !ov.is_empty();
1059 self.m.ins.ov[wi] = ov;
1060 self.m.ins.ov_box[wi] = boxes;
1061 self.m.ins.ov_hash[wi] = h;
1062 if self.m.ins.flash != 0 && !self.m.ins.flashes.is_empty() {
1064 self.animating = true;
1065 }
1066 n
1067 }
1068
1069 pub(crate) fn ins_render_end(&mut self) {
1073 let w = self.uiw_cur;
1074 if w as i32 == self.ins_win() {
1075 return;
1076 }
1077 let wi = w as usize & (WIN_MAX - 1);
1078 if self.m.ins.ov_now {
1079 let n = self.wp.rt.present_n;
1080 let pieces = if n < 0 { None } else { Some(disjoint(&self.drects[..(n as usize).min(self.drects.len())])) };
1081 self.ins_draw(false, pieces.as_deref());
1082 }
1083 self.m.ins.st.render_us[wi] = plat::time_us().saturating_sub(self.m.ins.t_render);
1084 }
1085
1086 pub(crate) fn ins_verify(&mut self, shadow: bool) {
1089 if self.uiw_cur as i32 != self.ins_win() {
1090 self.ins_draw(shadow, None);
1091 }
1092 }
1093
1094 fn ins_draw(&mut self, shadow: bool, clip: Option<&[[i32; 4]]>) {
1097 let wi = self.uiw_cur as usize & (WIN_MAX - 1);
1098 if self.m.ins.ov[wi].is_empty() {
1099 return;
1100 }
1101 let icon_style = (self.style, self.color(T_ACCENT), self.theme_dark, self.color(T_TEXT_DISABLED), self.wp.rt.dpi as u32);
1102 let ov = core::mem::take(&mut self.m.ins.ov[wi]);
1103 let Ui { wp, dc, text, icon_hook, .. } = self;
1104 let sf = if shadow { &mut wp.rt.shadow } else { &mut wp.rt.fb };
1105 dc.begin(sf);
1106 match clip {
1107 None => render_layer(&ov, 0, dc, sf, text, icon_hook, icon_style),
1108 Some(rs) => {
1109 for r in rs {
1110 dc.clip_push(r[0], r[1], r[2], r[3]);
1111 render_layer(&ov, 0, dc, sf, text, icon_hook, icon_style);
1112 dc.clip_pop();
1113 }
1114 }
1115 }
1116 dc.end();
1117 self.m.ins.ov[wi] = ov;
1118 }
1119
1120 fn ins_overlay(&mut self, w: u32) -> Vec<Cmd> {
1122 let mut ov = Vec::new();
1123 let (fw, fh) = (self.wp.rt.fb.w, self.wp.rt.fb.h);
1124 let hair = self.hair();
1125 let thick = self.dp(2).max(1);
1126 if self.m.ins.outlines != 0 {
1128 for e in self.m.ins.boxes.iter().filter(|e| e.begin && e.win as u32 == w) {
1129 frame(&mut ov, e.rect, hair, C_BOX);
1130 }
1131 }
1132 if self.m.ins.flash != 0 {
1134 let now = self.now_ms;
1135 for &(fwin, r, t) in &self.m.ins.flashes {
1136 let age = now.wrapping_sub(t);
1137 if fwin as u32 != w || age >= FLASH_MS {
1138 continue;
1139 }
1140 let k = FLASH_MS - age;
1141 ov.push(Cmd::Rect { x: r[0], y: r[1], w: r[2], h: r[3], c: (0x48 * k / FLASH_MS) << 24 | C_DIRTY });
1142 frame(&mut ov, r, hair, (0x60 + 0x9F * k / FLASH_MS) << 24 | C_DIRTY);
1143 }
1144 }
1145 self.ts_font(TS_CAPTION);
1146 let (font, size) = self.text.get_font();
1147 let (asc, desc) = (self.text.ascent(), self.text.descent());
1148 let pad = self.dp(4);
1149 if let Some(r) = self.m.ins.sel_rec.as_ref().filter(|r| r.win as u32 == w) {
1151 ov.push(Cmd::Rect { x: r.rect[0], y: r.rect[1], w: r.rect[2], h: r.rect[3], c: C_SEL_FILL });
1152 frame(&mut ov, r.rect, thick, C_SEL);
1153 }
1154 let mut tag = Vec::new();
1156 let (px, py) = self.m.ins.ptr[w as usize & (WIN_MAX - 1)];
1157 if let Some(k) = self.ins_at(w, px, py) {
1158 let r = self.sems[k].clone();
1159 ov.push(Cmd::Rect { x: r.rect[0], y: r.rect[1], w: r.rect[2], h: r.rect[3], c: C_HOVER_FILL });
1160 frame(&mut ov, r.rect, hair.max(thick / 2), C_HOVER);
1161 let mut s = String::from(kind_name(r.kind));
1162 if r.id != 0 {
1163 s.push_str(" ");
1164 s.push_str(&id_text(r.id));
1165 if let Some(n) = self.id_name(r.id) {
1166 s.push_str(&format!(" (\"{}\")", n));
1167 }
1168 }
1169 if let Some(l) = r.label.as_deref().filter(|l| !l.is_empty()) {
1170 s.push_str(" ");
1171 s.push_str("e(&cut(l)));
1172 }
1173 let dpi = self.wp.rt.dpi;
1174 s.push_str(&format!(" {} x {}", px2dip_at(r.rect[2], dpi), px2dip_at(r.rect[3], dpi)));
1175 let st = state_text(r.state);
1176 if !st.is_empty() {
1177 s.push_str(&format!(" [{}]", st));
1178 }
1179 let tw = self.text.width(&s).min(fw - 2 * pad).max(1);
1180 let (bw, bh) = (tw + 2 * pad, asc + desc + 2 * pad);
1181 let x = r.rect[0].clamp(0, (fw - bw).max(0));
1182 let above = r.rect[1] - bh - hair;
1183 let y = if above >= 0 { above } else { (r.rect[1] + r.rect[3] + hair).min((fh - bh).max(0)) };
1184 let t = hair as u16;
1185 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 });
1186 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() });
1187 }
1188 if self.m.ins.order != 0 {
1190 let c = &self.wp.ctx;
1191 let list = &c.hits[c.hit_cur ^ 1];
1192 let (stops, _) = super::kbnav::stops(list, c.focus, c.kb.gmem());
1193 let rects: Vec<(usize, [i32; 4])> = stops
1195 .iter()
1196 .enumerate()
1197 .map(|(k, &i)| {
1198 let h = &list[i];
1199 (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] })
1200 })
1201 .filter(|(_, r)| r[2] > 0 && r[3] > 0)
1202 .collect();
1203 for (k, r) in rects.iter() {
1204 let s = format!("{}", k + 1);
1205 let tw = self.text.width(&s);
1206 let bh = asc + desc + 2;
1207 let bw = (tw + 2 * pad).max(bh);
1208 let (x, y) = ((r[0] - bw / 2).clamp(0, (fw - bw).max(0)), (r[1] - bh / 2).clamp(0, (fh - bh).max(0)));
1210 frame(&mut ov, *r, hair, C_ORDER);
1211 ov.push(Cmd::RRect { x, y, w: bw, h: bh, r: (bh / 2) as u16, t: 0, fill: C_ORDER, stroke: 0, stroke2: 0 });
1212 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() });
1213 }
1214 }
1215 ov.append(&mut tag); ov
1217 }
1218
1219 pub(crate) fn ins_passes(&mut self, passes: u32, build_us: u64) {
1223 self.m.ins.st.passes = passes;
1224 self.m.ins.st.build_us = build_us;
1225 }
1226
1227 pub(crate) fn ins_gone(&mut self) {
1230 self.m.ins.gone = false;
1231 self.uiw_use(0);
1232 self.window_destroy(INSPECT_WIN);
1233 }
1234
1235 pub(crate) fn sc_inspect(&mut self, app: &mut dyn App, a: &[u8]) {
1239 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]";
1240 let w = |n: &str| word_eq(a, n).is_some();
1241 if w("on") || w("off") || w("toggle") {
1242 let on = if w("toggle") { !self.m.ins.on } else { w("on") };
1243 self.inspect_show(on);
1244 self.run_passes(app);
1245 } else if w("arm") || w("disarm") {
1246 self.inspect_enable(w("arm"));
1247 self.run_passes(app);
1248 } else if w("pick") {
1249 if !self.m.ins.on {
1250 self.fail(b"the inspector is closed");
1251 return;
1252 }
1253 self.m.ins.picking = true;
1254 self.invalidate();
1255 self.run_passes(app);
1256 } else if let Some(r) = word_eq(a, "select") {
1257 let r = skip_ws(r);
1258 if word_eq(r, "none").is_some() {
1259 self.m.ins.sel = 0;
1260 self.m.ins.sel_rec = None;
1261 self.m.ins.sel_src = None;
1262 } else {
1263 let Some(k) = self.ins_resolve(r) else { return };
1264 let nodes = self.ins_tree();
1265 match nodes.iter().position(|n| n.sem == k) {
1266 Some(i) => self.ins_select(&nodes, i),
1267 None => {
1268 self.fail(b"not in the inspector's tree");
1269 return;
1270 }
1271 }
1272 }
1273 self.invalidate();
1274 self.run_passes(app);
1275 } else if let Some(r) = word_eq(a, "option") {
1276 let r = skip_ws(r);
1277 let v = word_eq(skip_ws(super::auto::next_word(r)), "on").is_some() as i32;
1278 if word_eq(r, "outlines").is_some() {
1279 self.m.ins.outlines = v;
1280 } else if word_eq(r, "order").is_some() {
1281 self.m.ins.order = v;
1282 } else if word_eq(r, "dirty").is_some() {
1283 self.m.ins.flash = v;
1284 } else {
1285 self.fail(b"inspect option outlines|order|dirty on|off");
1286 return;
1287 }
1288 self.invalidate();
1289 self.run_passes(app);
1290 } else if let Some(r) = word_eq(a, "tab") {
1291 let r = skip_ws(r);
1292 let n = if super::inspect_edit::EDIT { 4 } else { 3 };
1294 let t = ["widgets", "frame", "colors", "look"][..n].iter().position(|n| word_eq(r, n).is_some());
1295 match t {
1296 Some(t) => self.m.ins.tab = t as i32,
1297 None => {
1298 self.fail(if n == 4 { &b"inspect tab widgets|frame|colors|look"[..] } else { b"inspect tab widgets|frame|colors" });
1299 return;
1300 }
1301 }
1302 self.invalidate();
1303 self.run_passes(app);
1304 } else if w("copy") {
1305 self.ins_copy();
1306 self.run_passes(app);
1307 } else if w("snap") {
1308 self.render();
1309 self.gp_settle_all();
1310 let fb = &self.wp.rt.fb;
1311 self.m.ins.snap = Some((fb.w, fb.h, fb.px.clone()));
1312 } else if let Some(r) = word_eq(a, "expect") {
1313 self.sc_inspect_expect(skip_ws(r));
1314 } else if !self.isrc_script(app, a) && !self.ied_script(app, a) {
1315 self.fail(BAD);
1318 }
1319 }
1320
1321 fn ins_resolve(&mut self, p: &[u8]) -> Option<usize> {
1323 match self.resolve(p).0 {
1324 None => {
1325 self.sel_fail();
1326 None
1327 }
1328 Some(Sel::Point) => {
1329 self.fail(b"a widget selector (#id or a label), not a point");
1330 None
1331 }
1332 Some(Sel::Rec(k)) => {
1333 if self.sems[k].win as i32 == self.ins_win() {
1334 self.fail(b"a widget of the inspector itself");
1335 return None;
1336 }
1337 Some(k)
1338 }
1339 }
1340 }
1341
1342 fn sc_inspect_expect(&mut self, a: &[u8]) {
1344 let w = |n: &str| word_eq(a, n).is_some();
1345 if w("open") || w("closed") {
1346 if self.m.ins.on != w("open") {
1347 self.fail(if self.m.ins.on { b"the inspector is open" } else { b"the inspector is closed" });
1348 }
1349 } else if let Some(r) = word_eq(a, "pick") {
1350 let want = word_eq(skip_ws(r), "on").is_some();
1351 if self.m.ins.picking != want {
1352 self.fail(if want { b"no pick" } else { b"picking" });
1353 }
1354 } else if let Some(r) = word_eq(a, "flash") {
1355 let now = self.now_ms;
1356 let live = self.m.ins.flash != 0 && self.m.ins.flashes.iter().any(|e| now.wrapping_sub(e.2) < FLASH_MS);
1357 if live != word_eq(skip_ws(r), "on").is_some() {
1358 self.fail(if live { b"repainted rectangles are shown" } else { b"no repainted rectangle is shown" });
1359 }
1360 } else if w("idle") {
1361 let d = self.m.ins.work.wrapping_sub(self.m.ins.work_mark);
1362 self.m.ins.work_mark = self.m.ins.work;
1363 let (b, s) = (self.m.ins.boxes.len(), self.src_count());
1364 if d != 0 || b != 0 || s != 0 || self.m.ins.ov.iter().any(|v| !v.is_empty()) {
1365 self.fail(format!("the inspector worked {} times, {} boxes, {} sources recorded", d, b, s).as_bytes());
1366 }
1367 } else if w("same") {
1368 self.render();
1369 let fb = &self.wp.rt.fb;
1370 let d = match &self.m.ins.snap {
1371 None => Err(String::from("no inspect snap")),
1372 Some((sw, sh, _)) if (*sw, *sh) != (fb.w, fb.h) => Err(format!("the size is {}x{}, was {}x{}", fb.w, fb.h, sw, sh)),
1373 Some((_, _, px)) => {
1374 let n = px.chunks_exact(4).zip(fb.px.chunks_exact(4)).filter(|(a, b)| a[..3] != b[..3]).count();
1375 if n == 0 { Ok(()) } else { Err(format!("{} pixels differ", n)) }
1376 }
1377 };
1378 if let Err(e) = d {
1379 self.fail(e.as_bytes());
1380 }
1381 } else if w("excluded") {
1382 let iw = self.ins_win();
1383 let own = iw >= 0 && self.sems.iter().any(|r| r.win as i32 == iw);
1384 if !own {
1385 self.fail(b"the inspector's window has no records");
1386 } else if self.ins_tree().iter().any(|n| n.rec.win as i32 == iw) {
1387 self.fail(b"the tree holds records of the inspector's window");
1388 }
1389 } else if let Some(r) = word_eq(a, "selected") {
1390 let r = skip_ws(r);
1391 if word_eq(r, "none").is_some() {
1392 if self.m.ins.sel_rec.is_some() {
1393 self.fail(b"a widget is selected");
1394 }
1395 return;
1396 }
1397 let Some(k) = self.ins_resolve(r) else { return };
1398 match &self.m.ins.sel_rec {
1399 Some(s) if same(s, &self.sems[k]) => {}
1400 Some(s) => {
1401 let m = format!("selected: {} {} {:?}", kind_name(s.kind), id_text(s.id), s.label.as_deref().unwrap_or(""));
1402 self.fail(m.as_bytes());
1403 }
1404 None => self.fail(b"nothing selected"),
1405 }
1406 } else if let Some(r) = word_eq(a, "hover") {
1407 let r = skip_ws(r);
1408 let h = (0..WIN_MAX as u32).find_map(|i| {
1409 let (x, y) = self.m.ins.ptr[i as usize];
1410 self.ins_at(i, x, y)
1411 });
1412 if word_eq(r, "none").is_some() {
1413 if h.is_some() {
1414 self.fail(b"a widget is under the pointer");
1415 }
1416 return;
1417 }
1418 let Some(k) = self.ins_resolve(r) else { return };
1419 if h != Some(k) {
1420 let m = match h {
1421 Some(i) => format!("under the pointer: {} {}", kind_name(self.sems[i].kind), id_text(self.sems[i].id)),
1422 None => String::from("nothing under the pointer"),
1423 };
1424 self.fail(m.as_bytes());
1425 }
1426 } else if let Some(r) = word_eq(a, "selector") {
1427 let Some((want, _)) = parse_qstring(skip_ws(r), 256) else {
1428 self.fail(b"expected a quoted string");
1429 return;
1430 };
1431 let have = self.m.ins.sel_rec.clone().map(|s| self.ins_selector(&s)).unwrap_or_default();
1432 if have.as_bytes() != want.as_slice() {
1433 self.fail(format!("selector is {}", have).as_bytes());
1434 }
1435 } else if let Some(r) = word_eq(a, "copied") {
1436 let Some((want, _)) = parse_qstring(skip_ws(r), 256) else {
1437 self.fail(b"expected a quoted string");
1438 return;
1439 };
1440 let have = self.clip_read(CLIP_TEXT).unwrap_or_default();
1441 if have != want {
1442 self.fail(format!("the clipboard holds {:?}", String::from_utf8_lossy(&have)).as_bytes());
1443 }
1444 } else if !self.isrc_expect(a) && !self.ied_expect(a) {
1445 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\"");
1448 }
1449 }
1450
1451 pub(crate) fn ins_carry(&self) -> (bool, bool, i32, [i32; 3], Option<String>) {
1456 let s = &self.m.ins;
1457 let sel = s.sel_rec.as_ref().map(|r| self.ins_selector(r));
1458 (s.armed, s.on, s.tab, [s.outlines, s.order, s.flash], sel)
1459 }
1460
1461 pub(crate) fn ins_restore(&mut self, app: &mut dyn App, armed: bool, on: bool, tab: i32, opts: [i32; 3], sel: Option<&str>) {
1464 self.inspect_enable(armed || on);
1465 self.inspect_show(on);
1466 [self.m.ins.outlines, self.m.ins.order, self.m.ins.flash] = opts;
1467 self.run_passes(app);
1468 if let Some(Sel::Rec(k)) = sel.filter(|_| on).and_then(|p| self.resolve(p.as_bytes()).0) {
1469 let nodes = self.ins_tree();
1470 if let Some(i) = nodes.iter().position(|n| n.sem == k) {
1471 self.ins_select(&nodes, i);
1472 }
1473 }
1474 self.m.ins.tab = tab.clamp(0, 2);
1475 self.invalidate();
1476 self.run_passes(app);
1477 }
1478}