1use std::any::Any;
12use std::collections::HashMap;
13
14#[derive(Debug, Clone, PartialEq)]
18pub enum InspectorValue {
19 Bool(bool),
20 Int(i64),
21 Float(f64),
22 String(String),
23 Vec2([f32; 2]),
24 Vec3([f32; 3]),
25 Vec4([f32; 4]),
26 Color([f32; 4]),
28 List(Vec<InspectorValue>),
29 Map(HashMap<String, InspectorValue>),
30 Enum { variant: String, variants: Vec<String> },
31}
32
33impl InspectorValue {
34 pub fn is_numeric(&self) -> bool {
36 matches!(self, InspectorValue::Int(_) | InspectorValue::Float(_))
37 }
38
39 pub fn as_f64(&self) -> Option<f64> {
41 match self {
42 InspectorValue::Int(v) => Some(*v as f64),
43 InspectorValue::Float(v) => Some(*v),
44 _ => None,
45 }
46 }
47
48 pub fn as_i64(&self) -> Option<i64> {
50 match self {
51 InspectorValue::Int(v) => Some(*v),
52 InspectorValue::Float(v) => Some(*v as i64),
53 _ => None,
54 }
55 }
56
57 pub fn display(&self) -> String {
59 match self {
60 InspectorValue::Bool(v) => v.to_string(),
61 InspectorValue::Int(v) => v.to_string(),
62 InspectorValue::Float(v) => format!("{:.4}", v),
63 InspectorValue::String(v) => v.clone(),
64 InspectorValue::Vec2(v) => format!("[{:.3}, {:.3}]", v[0], v[1]),
65 InspectorValue::Vec3(v) => format!("[{:.3}, {:.3}, {:.3}]", v[0], v[1], v[2]),
66 InspectorValue::Vec4(v) => format!("[{:.3}, {:.3}, {:.3}, {:.3}]", v[0], v[1], v[2], v[3]),
67 InspectorValue::Color(v) => format!("rgba({:.2},{:.2},{:.2},{:.2})", v[0], v[1], v[2], v[3]),
68 InspectorValue::List(v) => format!("[{}]", v.len()),
69 InspectorValue::Map(v) => format!("{{{}}}", v.len()),
70 InspectorValue::Enum { variant, .. } => variant.clone(),
71 }
72 }
73
74 pub fn type_name(&self) -> &'static str {
76 match self {
77 InspectorValue::Bool(_) => "bool",
78 InspectorValue::Int(_) => "int",
79 InspectorValue::Float(_) => "float",
80 InspectorValue::String(_) => "string",
81 InspectorValue::Vec2(_) => "vec2",
82 InspectorValue::Vec3(_) => "vec3",
83 InspectorValue::Vec4(_) => "vec4",
84 InspectorValue::Color(_) => "color",
85 InspectorValue::List(_) => "list",
86 InspectorValue::Map(_) => "map",
87 InspectorValue::Enum { .. } => "enum",
88 }
89 }
90}
91
92impl Default for InspectorValue {
93 fn default() -> Self { InspectorValue::Int(0) }
94}
95
96impl From<bool> for InspectorValue { fn from(v: bool) -> Self { InspectorValue::Bool(v) } }
97impl From<i32> for InspectorValue { fn from(v: i32) -> Self { InspectorValue::Int(v as i64) } }
98impl From<i64> for InspectorValue { fn from(v: i64) -> Self { InspectorValue::Int(v) } }
99impl From<f32> for InspectorValue { fn from(v: f32) -> Self { InspectorValue::Float(v as f64) } }
100impl From<f64> for InspectorValue { fn from(v: f64) -> Self { InspectorValue::Float(v) } }
101impl From<String> for InspectorValue { fn from(v: String) -> Self { InspectorValue::String(v) } }
102impl From<&str> for InspectorValue { fn from(v: &str) -> Self { InspectorValue::String(v.to_owned()) } }
103
104#[derive(Debug, Clone)]
108pub struct InspectorField {
109 pub name: String,
111 pub value: InspectorValue,
113 pub editable: bool,
115 pub tooltip: Option<String>,
117 pub range: Option<(f64, f64)>,
119 pub step: Option<f64>,
121}
122
123impl InspectorField {
124 pub fn new(name: impl Into<String>, value: InspectorValue) -> Self {
125 Self {
126 name: name.into(),
127 value,
128 editable: false,
129 tooltip: None,
130 range: None,
131 step: None,
132 }
133 }
134
135 pub fn editable(mut self) -> Self { self.editable = true; self }
136 pub fn with_tooltip(mut self, tip: impl Into<String>) -> Self { self.tooltip = Some(tip.into()); self }
137 pub fn with_range(mut self, min: f64, max: f64) -> Self { self.range = Some((min, max)); self }
138 pub fn with_step(mut self, step: f64) -> Self { self.step = Some(step); self }
139
140 pub fn readonly(name: impl Into<String>, value: InspectorValue) -> Self {
141 Self::new(name, value)
142 }
143
144 pub fn read_write(name: impl Into<String>, value: InspectorValue) -> Self {
145 Self::new(name, value).editable()
146 }
147}
148
149pub trait Inspectable {
153 fn inspect(&self) -> Vec<InspectorField>;
155
156 fn apply_changes(&mut self, changes: Vec<(String, InspectorValue)>);
158}
159
160impl Inspectable for f32 {
163 fn inspect(&self) -> Vec<InspectorField> {
164 vec![InspectorField::read_write("value", InspectorValue::Float(*self as f64))
165 .with_step(0.01).with_range(f64::NEG_INFINITY, f64::INFINITY)]
166 }
167 fn apply_changes(&mut self, changes: Vec<(String, InspectorValue)>) {
168 for (name, val) in changes {
169 if name == "value" {
170 if let Some(v) = val.as_f64() { *self = v as f32; }
171 }
172 }
173 }
174}
175
176impl Inspectable for f64 {
177 fn inspect(&self) -> Vec<InspectorField> {
178 vec![InspectorField::read_write("value", InspectorValue::Float(*self))
179 .with_step(0.001)]
180 }
181 fn apply_changes(&mut self, changes: Vec<(String, InspectorValue)>) {
182 for (name, val) in changes {
183 if name == "value" {
184 if let Some(v) = val.as_f64() { *self = v; }
185 }
186 }
187 }
188}
189
190impl Inspectable for i32 {
191 fn inspect(&self) -> Vec<InspectorField> {
192 vec![InspectorField::read_write("value", InspectorValue::Int(*self as i64))
193 .with_step(1.0)]
194 }
195 fn apply_changes(&mut self, changes: Vec<(String, InspectorValue)>) {
196 for (name, val) in changes {
197 if name == "value" {
198 if let Some(v) = val.as_i64() { *self = v as i32; }
199 }
200 }
201 }
202}
203
204impl Inspectable for i64 {
205 fn inspect(&self) -> Vec<InspectorField> {
206 vec![InspectorField::read_write("value", InspectorValue::Int(*self))
207 .with_step(1.0)]
208 }
209 fn apply_changes(&mut self, changes: Vec<(String, InspectorValue)>) {
210 for (name, val) in changes {
211 if name == "value" {
212 if let Some(v) = val.as_i64() { *self = v; }
213 }
214 }
215 }
216}
217
218impl Inspectable for bool {
219 fn inspect(&self) -> Vec<InspectorField> {
220 vec![InspectorField::read_write("value", InspectorValue::Bool(*self))]
221 }
222 fn apply_changes(&mut self, changes: Vec<(String, InspectorValue)>) {
223 for (name, val) in changes {
224 if name == "value" {
225 if let InspectorValue::Bool(v) = val { *self = v; }
226 }
227 }
228 }
229}
230
231impl Inspectable for String {
232 fn inspect(&self) -> Vec<InspectorField> {
233 vec![InspectorField::read_write("value", InspectorValue::String(self.clone()))]
234 }
235 fn apply_changes(&mut self, changes: Vec<(String, InspectorValue)>) {
236 for (name, val) in changes {
237 if name == "value" {
238 if let InspectorValue::String(v) = val { *self = v; }
239 }
240 }
241 }
242}
243
244#[derive(Debug, Clone, Copy, PartialEq, Default)]
246pub struct Vec2([f32; 2]);
247
248impl Vec2 {
249 pub fn new(x: f32, y: f32) -> Self { Self([x, y]) }
250 pub fn x(&self) -> f32 { self.0[0] }
251 pub fn y(&self) -> f32 { self.0[1] }
252}
253
254impl Inspectable for Vec2 {
255 fn inspect(&self) -> Vec<InspectorField> {
256 vec![InspectorField::read_write("value", InspectorValue::Vec2(self.0)).with_step(0.01)]
257 }
258 fn apply_changes(&mut self, changes: Vec<(String, InspectorValue)>) {
259 for (name, val) in changes {
260 if name == "value" {
261 if let InspectorValue::Vec2(v) = val { self.0 = v; }
262 }
263 }
264 }
265}
266
267#[derive(Debug, Clone, Copy, PartialEq, Default)]
269pub struct Vec3([f32; 3]);
270
271impl Vec3 {
272 pub fn new(x: f32, y: f32, z: f32) -> Self { Self([x, y, z]) }
273 pub fn x(&self) -> f32 { self.0[0] }
274 pub fn y(&self) -> f32 { self.0[1] }
275 pub fn z(&self) -> f32 { self.0[2] }
276}
277
278impl Inspectable for Vec3 {
279 fn inspect(&self) -> Vec<InspectorField> {
280 vec![InspectorField::read_write("value", InspectorValue::Vec3(self.0)).with_step(0.01)]
281 }
282 fn apply_changes(&mut self, changes: Vec<(String, InspectorValue)>) {
283 for (name, val) in changes {
284 if name == "value" {
285 if let InspectorValue::Vec3(v) = val { self.0 = v; }
286 }
287 }
288 }
289}
290
291#[derive(Debug, Clone, Copy, PartialEq, Default)]
293pub struct Vec4([f32; 4]);
294
295impl Vec4 {
296 pub fn new(x: f32, y: f32, z: f32, w: f32) -> Self { Self([x, y, z, w]) }
297}
298
299impl Inspectable for Vec4 {
300 fn inspect(&self) -> Vec<InspectorField> {
301 vec![InspectorField::read_write("value", InspectorValue::Vec4(self.0)).with_step(0.01)]
302 }
303 fn apply_changes(&mut self, changes: Vec<(String, InspectorValue)>) {
304 for (name, val) in changes {
305 if name == "value" {
306 if let InspectorValue::Vec4(v) = val { self.0 = v; }
307 }
308 }
309 }
310}
311
312impl Inspectable for [f32; 3] {
313 fn inspect(&self) -> Vec<InspectorField> {
314 vec![InspectorField::read_write("value", InspectorValue::Vec3(*self)).with_step(0.01)]
315 }
316 fn apply_changes(&mut self, changes: Vec<(String, InspectorValue)>) {
317 for (name, val) in changes {
318 if name == "value" {
319 if let InspectorValue::Vec3(v) = val { *self = v; }
320 }
321 }
322 }
323}
324
325impl Inspectable for [f32; 4] {
326 fn inspect(&self) -> Vec<InspectorField> {
327 vec![InspectorField::read_write("value", InspectorValue::Vec4(*self)).with_step(0.01)]
328 }
329 fn apply_changes(&mut self, changes: Vec<(String, InspectorValue)>) {
330 for (name, val) in changes {
331 if name == "value" {
332 if let InspectorValue::Vec4(v) = val { *self = v; }
333 }
334 }
335 }
336}
337
338pub struct InspectorWatcher {
342 pub object_name: String,
343 last_seen: HashMap<String, InspectorValue>,
344}
345
346impl InspectorWatcher {
347 pub fn new(object_name: impl Into<String>) -> Self {
348 Self { object_name: object_name.into(), last_seen: HashMap::new() }
349 }
350
351 pub fn check_changes(&mut self, current_fields: &[InspectorField])
354 -> Vec<(String, InspectorValue, InspectorValue)>
355 {
356 let mut changes = Vec::new();
357
358 for field in current_fields {
359 match self.last_seen.get(&field.name) {
360 Some(old) if old != &field.value => {
361 changes.push((field.name.clone(), old.clone(), field.value.clone()));
362 self.last_seen.insert(field.name.clone(), field.value.clone());
363 }
364 None => {
365 self.last_seen.insert(field.name.clone(), field.value.clone());
367 }
368 _ => {}
369 }
370 }
371
372 changes
373 }
374
375 pub fn reset(&mut self, fields: &[InspectorField]) {
377 self.last_seen.clear();
378 for f in fields {
379 self.last_seen.insert(f.name.clone(), f.value.clone());
380 }
381 }
382
383 pub fn field_count(&self) -> usize { self.last_seen.len() }
384}
385
386struct ObjectEntry {
391 data: Box<dyn Any>,
392 inspect: Box<dyn Fn(&dyn Any) -> Vec<InspectorField>>,
393 apply: Box<dyn Fn(&mut dyn Any, Vec<(String, InspectorValue)>)>,
394}
395
396pub struct RuntimeInspector {
403 objects: HashMap<String, ObjectEntry>,
404 order: Vec<String>,
406}
407
408impl RuntimeInspector {
409 pub fn new() -> Self {
410 Self { objects: HashMap::new(), order: Vec::new() }
411 }
412
413 pub fn register<T>(&mut self, name: impl Into<String>, obj: T)
418 where
419 T: Inspectable + Any + 'static,
420 {
421 let name = name.into();
422 if !self.objects.contains_key(&name) {
423 self.order.push(name.clone());
424 }
425 let entry = ObjectEntry {
426 data: Box::new(obj),
427 inspect: Box::new(|any| {
428 if let Some(v) = any.downcast_ref::<T>() {
429 v.inspect()
430 } else {
431 Vec::new()
432 }
433 }),
434 apply: Box::new(|any, changes| {
435 if let Some(v) = any.downcast_mut::<T>() {
436 v.apply_changes(changes);
437 }
438 }),
439 };
440 self.objects.insert(name, entry);
441 }
442
443 pub fn inspect(&self, name: &str) -> Option<Vec<InspectorField>> {
445 self.objects.get(name).map(|e| (e.inspect)(e.data.as_ref()))
446 }
447
448 pub fn apply(&mut self, name: &str, changes: Vec<(String, InspectorValue)>) {
450 if let Some(e) = self.objects.get_mut(name) {
451 (e.apply)(e.data.as_mut(), changes);
452 }
453 }
454
455 pub fn watch(&self, name: &str) -> Option<InspectorWatcher> {
459 let fields = self.inspect(name)?;
460 let mut watcher = InspectorWatcher::new(name);
461 watcher.reset(&fields);
462 Some(watcher)
463 }
464
465 pub fn get_object<T: Any>(&self, name: &str) -> Option<&T> {
467 self.objects.get(name)?.data.downcast_ref::<T>()
468 }
469
470 pub fn get_object_mut<T: Any>(&mut self, name: &str) -> Option<&mut T> {
472 self.objects.get_mut(name)?.data.downcast_mut::<T>()
473 }
474
475 pub fn unregister(&mut self, name: &str) {
477 self.objects.remove(name);
478 self.order.retain(|n| n != name);
479 }
480
481 pub fn names(&self) -> &[String] { &self.order }
483
484 pub fn len(&self) -> usize { self.objects.len() }
486
487 pub fn is_empty(&self) -> bool { self.objects.is_empty() }
488
489 pub fn format_table(&self) -> String {
491 let mut lines = vec!["=== Runtime Inspector ===".to_owned()];
492 for name in &self.order {
493 lines.push(format!(" [{}]", name));
494 if let Some(fields) = self.inspect(name) {
495 for f in fields {
496 let rw = if f.editable { "rw" } else { "ro" };
497 lines.push(format!(" {:20} ({:3}) = {}", f.name, rw, f.value.display()));
498 }
499 }
500 }
501 lines.join("\n")
502 }
503}
504
505impl Default for RuntimeInspector {
506 fn default() -> Self { Self::new() }
507}
508
509pub struct VariableMonitor {
516 vars: HashMap<String, MonitorVar>,
517 order: Vec<String>,
518 title: String,
519 max_key_width: usize,
520}
521
522#[derive(Debug, Clone)]
523struct MonitorVar {
524 value: InspectorValue,
525 category: Option<String>,
526 color: Option<String>,
527}
528
529impl VariableMonitor {
530 pub fn new() -> Self {
531 Self {
532 vars: HashMap::new(),
533 order: Vec::new(),
534 title: "Variables".to_owned(),
535 max_key_width: 0,
536 }
537 }
538
539 pub fn with_title(mut self, title: impl Into<String>) -> Self {
540 self.title = title.into();
541 self
542 }
543
544 pub fn set(&mut self, name: impl Into<String>, value: impl Into<InspectorValue>) {
546 let name = name.into();
547 let value = value.into();
548 if !self.vars.contains_key(&name) {
549 self.order.push(name.clone());
550 }
551 self.max_key_width = self.max_key_width.max(name.len());
552 self.vars.insert(name, MonitorVar { value, category: None, color: None });
553 }
554
555 pub fn set_categorized(&mut self, name: impl Into<String>, value: impl Into<InspectorValue>, category: impl Into<String>) {
557 let name = name.into();
558 let value = value.into();
559 let category = category.into();
560 if !self.vars.contains_key(&name) {
561 self.order.push(name.clone());
562 }
563 self.max_key_width = self.max_key_width.max(name.len());
564 self.vars.insert(name, MonitorVar { value, category: Some(category), color: None });
565 }
566
567 pub fn remove(&mut self, name: &str) {
569 self.vars.remove(name);
570 self.order.retain(|n| n != name);
571 self.max_key_width = self.order.iter()
572 .map(|n| n.len())
573 .max()
574 .unwrap_or(0);
575 }
576
577 pub fn clear(&mut self) {
579 self.vars.clear();
580 self.order.clear();
581 self.max_key_width = 0;
582 }
583
584 pub fn render(&self) -> String {
586 if self.vars.is_empty() { return format!("[ {} — (empty) ]", self.title); }
587
588 let kw = self.max_key_width.max(4);
589 let total = kw + 3 + 24; let border = format!("┌{}┐", "─".repeat(total + 2));
591 let title = format!("│ {:<width$} │", self.title, width = total);
592 let sep = format!("├{}┤", "─".repeat(total + 2));
593 let bottom = format!("└{}┘", "─".repeat(total + 2));
594
595 let mut lines = vec![border, title, sep];
596
597 let mut last_cat: Option<String> = None;
599 for name in &self.order {
600 if let Some(var) = self.vars.get(name) {
601 let cat = var.category.as_deref();
602 if cat != last_cat.as_deref() {
603 if let Some(c) = cat {
604 let cat_line = format!("│ {:─<width$} │", format!("─ {} ", c), width = total);
605 lines.push(cat_line);
606 }
607 last_cat = cat.map(str::to_owned);
608 }
609 let val_str = var.value.display();
610 lines.push(format!("│ {:<kw$} │ {:<24} │", name, val_str, kw = kw));
611 }
612 }
613
614 lines.push(bottom);
615 lines.join("\n")
616 }
617
618 pub fn len(&self) -> usize { self.vars.len() }
620 pub fn is_empty(&self) -> bool { self.vars.is_empty() }
621
622 pub fn get(&self, name: &str) -> Option<&InspectorValue> {
624 self.vars.get(name).map(|v| &v.value)
625 }
626}
627
628impl Default for VariableMonitor {
629 fn default() -> Self { Self::new() }
630}
631
632pub struct CommandRegistry {
640 commands: Vec<RegisteredCommand>,
641}
642
643struct RegisteredCommand {
644 name: String,
645 description: String,
646 aliases: Vec<String>,
647 handler: Box<dyn Fn(Vec<String>) -> String + Send + Sync>,
648}
649
650impl CommandRegistry {
651 pub fn new() -> Self {
652 let mut reg = Self { commands: Vec::new() };
653 reg.register_builtins();
654 reg
655 }
656
657 fn register_builtins(&mut self) {
658 self.register(
659 "help",
660 "List all available commands",
661 Box::new(|_args| "Available commands: help, echo, version, clear".to_owned()),
662 );
663 self.register(
664 "echo",
665 "Echo arguments back",
666 Box::new(|args| args.join(" ")),
667 );
668 self.register(
669 "version",
670 "Print engine version",
671 Box::new(|_args| "Proof Engine v0.1.0".to_owned()),
672 );
673 }
674
675 pub fn register(
677 &mut self,
678 name: impl Into<String>,
679 description: impl Into<String>,
680 handler: Box<dyn Fn(Vec<String>) -> String + Send + Sync>,
681 ) {
682 let name = name.into();
683 self.commands.retain(|c| c.name != name);
685 self.commands.push(RegisteredCommand {
686 name,
687 description: description.into(),
688 aliases: Vec::new(),
689 handler,
690 });
691 }
692
693 pub fn add_alias(&mut self, name: &str, alias: impl Into<String>) {
695 if let Some(cmd) = self.commands.iter_mut().find(|c| c.name == name) {
696 cmd.aliases.push(alias.into());
697 }
698 }
699
700 pub fn unregister(&mut self, name: &str) {
702 self.commands.retain(|c| c.name != name);
703 }
704
705 pub fn execute(&self, input: &str) -> Result<String, String> {
708 let tokens = tokenize(input);
709 if tokens.is_empty() { return Ok(String::new()); }
710
711 let cmd_name = &tokens[0];
712 let args: Vec<String> = tokens[1..].to_vec();
713
714 let cmd = self.commands.iter().find(|c| {
716 c.name == *cmd_name || c.aliases.iter().any(|a| a == cmd_name)
717 });
718
719 match cmd {
720 Some(c) => Ok((c.handler)(args)),
721 None => Err(format!("Unknown command: '{}'. Type 'help' for a list.", cmd_name)),
722 }
723 }
724
725 pub fn completions(&self, prefix: &str) -> Vec<String> {
727 let mut matches: Vec<String> = self.commands.iter()
728 .flat_map(|c| {
729 let mut names: Vec<String> = std::iter::once(c.name.clone())
730 .chain(c.aliases.iter().cloned())
731 .collect();
732 names.retain(|n| n.starts_with(prefix));
733 names
734 })
735 .collect();
736 matches.sort();
737 matches.dedup();
738 matches
739 }
740
741 pub fn help_text(&self) -> String {
743 let mut lines = Vec::new();
744 for c in &self.commands {
745 let aliases = if c.aliases.is_empty() {
746 String::new()
747 } else {
748 format!(" (aliases: {})", c.aliases.join(", "))
749 };
750 lines.push(format!(" {:16} — {}{}", c.name, c.description, aliases));
751 }
752 lines.join("\n")
753 }
754
755 pub fn len(&self) -> usize { self.commands.len() }
757 pub fn is_empty(&self) -> bool { self.commands.is_empty() }
758}
759
760impl Default for CommandRegistry {
761 fn default() -> Self { Self::new() }
762}
763
764fn tokenize(input: &str) -> Vec<String> {
766 let mut tokens = Vec::new();
767 let mut current = String::new();
768 let mut in_quote = false;
769 let mut quote_ch = '"';
770
771 for c in input.chars() {
772 match c {
773 '"' | '\'' if !in_quote => { in_quote = true; quote_ch = c; }
774 c if in_quote && c == quote_ch => { in_quote = false; }
775 ' ' | '\t' if !in_quote => {
776 if !current.is_empty() { tokens.push(std::mem::take(&mut current)); }
777 }
778 _ => current.push(c),
779 }
780 }
781 if !current.is_empty() { tokens.push(current); }
782 tokens
783}
784
785#[derive(Debug, Clone)]
789pub struct FieldDiff {
790 pub field: String,
791 pub old_value: InspectorValue,
792 pub new_value: InspectorValue,
793}
794
795impl FieldDiff {
796 pub fn compute(old: &[InspectorField], new: &[InspectorField]) -> Vec<FieldDiff> {
797 let old_map: HashMap<&str, &InspectorValue> = old.iter()
798 .map(|f| (f.name.as_str(), &f.value))
799 .collect();
800 let mut diffs = Vec::new();
801 for nf in new {
802 if let Some(&ov) = old_map.get(nf.name.as_str()) {
803 if ov != &nf.value {
804 diffs.push(FieldDiff {
805 field: nf.name.clone(),
806 old_value: ov.clone(),
807 new_value: nf.value.clone(),
808 });
809 }
810 }
811 }
812 diffs
813 }
814}
815
816pub struct InspectorHistory {
820 object_name: String,
821 undo_stack: Vec<Vec<InspectorField>>,
822 redo_stack: Vec<Vec<InspectorField>>,
823 max_depth: usize,
824}
825
826impl InspectorHistory {
827 pub fn new(object_name: impl Into<String>, max_depth: usize) -> Self {
828 Self {
829 object_name: object_name.into(),
830 undo_stack: Vec::new(),
831 redo_stack: Vec::new(),
832 max_depth: max_depth.max(1),
833 }
834 }
835
836 pub fn push_snapshot(&mut self, fields: Vec<InspectorField>) {
838 self.redo_stack.clear();
839 self.undo_stack.push(fields);
840 if self.undo_stack.len() > self.max_depth {
841 self.undo_stack.remove(0);
842 }
843 }
844
845 pub fn undo(&mut self) -> Option<Vec<InspectorField>> {
847 let snap = self.undo_stack.pop()?;
848 Some(snap)
849 }
850
851 pub fn can_undo(&self) -> bool { !self.undo_stack.is_empty() }
853
854 pub fn undo_depth(&self) -> usize { self.undo_stack.len() }
856
857 pub fn object_name(&self) -> &str { &self.object_name }
858}
859
860#[cfg(test)]
863mod tests {
864 use super::*;
865
866 #[test]
867 fn inspector_value_display() {
868 assert_eq!(InspectorValue::Bool(true).display(), "true");
869 assert_eq!(InspectorValue::Int(42).display(), "42");
870 assert_eq!(InspectorValue::String("hello".into()).display(), "hello");
871 }
872
873 #[test]
874 fn inspector_value_as_f64() {
875 assert_eq!(InspectorValue::Int(5).as_f64(), Some(5.0));
876 assert_eq!(InspectorValue::Float(3.14).as_f64(), Some(3.14));
877 assert_eq!(InspectorValue::Bool(true).as_f64(), None);
878 }
879
880 #[test]
881 fn inspectable_f32() {
882 let mut v: f32 = 1.0;
883 let fields = v.inspect();
884 assert_eq!(fields.len(), 1);
885 assert!(fields[0].editable);
886 v.apply_changes(vec![("value".to_owned(), InspectorValue::Float(2.5))]);
887 assert!((v - 2.5_f32).abs() < 1e-5);
888 }
889
890 #[test]
891 fn inspectable_bool() {
892 let mut b = false;
893 let fields = b.inspect();
894 assert_eq!(fields[0].value, InspectorValue::Bool(false));
895 b.apply_changes(vec![("value".to_owned(), InspectorValue::Bool(true))]);
896 assert!(b);
897 }
898
899 #[test]
900 fn inspectable_array3() {
901 let mut arr: [f32; 3] = [1.0, 2.0, 3.0];
902 let fields = arr.inspect();
903 assert_eq!(fields.len(), 1);
904 arr.apply_changes(vec![("value".to_owned(), InspectorValue::Vec3([4.0, 5.0, 6.0]))]);
905 assert_eq!(arr, [4.0, 5.0, 6.0]);
906 }
907
908 #[test]
909 fn runtime_inspector_register_inspect() {
910 let mut ri = RuntimeInspector::new();
911 ri.register("speed", 42.0_f32);
912 let fields = ri.inspect("speed").unwrap();
913 assert_eq!(fields.len(), 1);
914 if let InspectorValue::Float(v) = &fields[0].value {
915 assert!((*v - 42.0).abs() < 1e-5);
916 } else {
917 panic!("expected Float");
918 }
919 }
920
921 #[test]
922 fn runtime_inspector_apply() {
923 let mut ri = RuntimeInspector::new();
924 ri.register("hp", 100_i32);
925 ri.apply("hp", vec![("value".to_owned(), InspectorValue::Int(50))]);
926 let v = ri.get_object::<i32>("hp").copied().unwrap();
927 assert_eq!(v, 50);
928 }
929
930 #[test]
931 fn inspector_watcher_detects_changes() {
932 let mut watcher = InspectorWatcher::new("obj");
933 let fields1 = vec![
934 InspectorField::readonly("x", InspectorValue::Float(1.0)),
935 ];
936 let changes = watcher.check_changes(&fields1);
938 assert!(changes.is_empty(), "first check should not report changes");
939
940 let fields2 = vec![
941 InspectorField::readonly("x", InspectorValue::Float(2.0)),
942 ];
943 let changes = watcher.check_changes(&fields2);
944 assert_eq!(changes.len(), 1);
945 assert_eq!(changes[0].0, "x");
946 }
947
948 #[test]
949 fn variable_monitor_set_render() {
950 let mut mon = VariableMonitor::new();
951 mon.set("fps", 60.0_f64);
952 mon.set("frames", 1234_i64);
953 mon.set("name", "test");
954 let out = mon.render();
955 assert!(out.contains("fps"), "should contain fps");
956 assert!(out.contains("frames"), "should contain frames");
957 assert!(out.contains("test"), "should contain value");
958 }
959
960 #[test]
961 fn variable_monitor_remove() {
962 let mut mon = VariableMonitor::new();
963 mon.set("a", 1_i64);
964 mon.set("b", 2_i64);
965 mon.remove("a");
966 assert_eq!(mon.len(), 1);
967 assert!(mon.get("a").is_none());
968 }
969
970 #[test]
971 fn command_registry_execute() {
972 let reg = CommandRegistry::new();
973 let result = reg.execute("echo hello world").unwrap();
974 assert_eq!(result, "hello world");
975 }
976
977 #[test]
978 fn command_registry_unknown_command() {
979 let reg = CommandRegistry::new();
980 let result = reg.execute("unknown_cmd");
981 assert!(result.is_err());
982 }
983
984 #[test]
985 fn command_registry_completions() {
986 let reg = CommandRegistry::new();
987 let c = reg.completions("he");
988 assert!(c.contains(&"help".to_owned()));
989 }
990
991 #[test]
992 fn command_registry_register_custom() {
993 let mut reg = CommandRegistry::new();
994 reg.register("greet", "Say hello", Box::new(|args| {
995 format!("Hello, {}!", args.first().map(|s| s.as_str()).unwrap_or("World"))
996 }));
997 let out = reg.execute("greet Alice").unwrap();
998 assert_eq!(out, "Hello, Alice!");
999 }
1000
1001 #[test]
1002 fn field_diff_compute() {
1003 let old = vec![
1004 InspectorField::readonly("x", InspectorValue::Float(1.0)),
1005 InspectorField::readonly("y", InspectorValue::Float(2.0)),
1006 ];
1007 let new_fields = vec![
1008 InspectorField::readonly("x", InspectorValue::Float(3.0)),
1009 InspectorField::readonly("y", InspectorValue::Float(2.0)),
1010 ];
1011 let diffs = FieldDiff::compute(&old, &new_fields);
1012 assert_eq!(diffs.len(), 1);
1013 assert_eq!(diffs[0].field, "x");
1014 }
1015
1016 #[test]
1017 fn inspector_history_undo() {
1018 let mut hist = InspectorHistory::new("obj", 10);
1019 let snap = vec![InspectorField::readonly("hp", InspectorValue::Int(100))];
1020 hist.push_snapshot(snap.clone());
1021 assert!(hist.can_undo());
1022 let restored = hist.undo().unwrap();
1023 assert_eq!(restored[0].name, "hp");
1024 assert!(!hist.can_undo());
1025 }
1026}