envision/annotation/
registry.rs

1//! Registry for storing widget annotations during rendering.
2
3use ratatui::layout::Rect;
4use serde::{Deserialize, Serialize};
5
6use super::types::{Annotation, WidgetType};
7
8/// Information about an annotated region.
9#[derive(Clone, Debug, Serialize, Deserialize)]
10pub struct RegionInfo {
11    /// The rectangular area of this region
12    pub area: SerializableRect,
13
14    /// The annotation for this region
15    pub annotation: Annotation,
16
17    /// Parent region index (if nested)
18    pub parent: Option<usize>,
19
20    /// Child region indices
21    pub children: Vec<usize>,
22
23    /// Depth in the widget tree (0 = root)
24    pub depth: usize,
25}
26
27/// A serializable version of ratatui's Rect.
28#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
29pub struct SerializableRect {
30    /// The x coordinate of the top-left corner.
31    pub x: u16,
32    /// The y coordinate of the top-left corner.
33    pub y: u16,
34    /// The width of the rectangle.
35    pub width: u16,
36    /// The height of the rectangle.
37    pub height: u16,
38}
39
40impl From<Rect> for SerializableRect {
41    fn from(rect: Rect) -> Self {
42        Self {
43            x: rect.x,
44            y: rect.y,
45            width: rect.width,
46            height: rect.height,
47        }
48    }
49}
50
51impl From<SerializableRect> for Rect {
52    fn from(rect: SerializableRect) -> Self {
53        Rect::new(rect.x, rect.y, rect.width, rect.height)
54    }
55}
56
57impl SerializableRect {
58    /// Creates a new rect.
59    pub fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
60        Self {
61            x,
62            y,
63            width,
64            height,
65        }
66    }
67
68    /// Returns true if this rect contains the given point.
69    pub fn contains(&self, x: u16, y: u16) -> bool {
70        x >= self.x
71            && x < self.x.saturating_add(self.width)
72            && y >= self.y
73            && y < self.y.saturating_add(self.height)
74    }
75
76    /// Returns true if this rect intersects with another.
77    pub fn intersects(&self, other: &Self) -> bool {
78        self.x < other.x.saturating_add(other.width)
79            && self.x.saturating_add(self.width) > other.x
80            && self.y < other.y.saturating_add(other.height)
81            && self.y.saturating_add(self.height) > other.y
82    }
83}
84
85/// Registry that collects widget annotations during rendering.
86///
87/// The registry maintains a tree structure of annotated regions,
88/// enabling queries like "what widget is at position X,Y" or
89/// "find all buttons".
90#[derive(Clone, Debug, Default, Serialize, Deserialize)]
91pub struct AnnotationRegistry {
92    /// All registered regions
93    regions: Vec<RegionInfo>,
94
95    /// Stack of currently open regions (for nesting)
96    #[serde(skip)]
97    open_stack: Vec<usize>,
98
99    /// Current nesting depth
100    #[serde(skip)]
101    current_depth: usize,
102}
103
104impl AnnotationRegistry {
105    /// Creates a new empty registry.
106    pub fn new() -> Self {
107        Self::default()
108    }
109
110    /// Clears all registered annotations.
111    pub fn clear(&mut self) {
112        self.regions.clear();
113        self.open_stack.clear();
114        self.current_depth = 0;
115    }
116
117    /// Registers a new annotated region.
118    ///
119    /// Returns the index of the registered region.
120    pub fn register(&mut self, area: Rect, annotation: Annotation) -> usize {
121        let parent = self.open_stack.last().copied();
122        let index = self.regions.len();
123
124        self.regions.push(RegionInfo {
125            area: area.into(),
126            annotation,
127            parent,
128            children: Vec::new(),
129            depth: self.current_depth,
130        });
131
132        // Add as child of parent
133        if let Some(parent_idx) = parent {
134            self.regions[parent_idx].children.push(index);
135        }
136
137        index
138    }
139
140    /// Opens a region (for nested widgets).
141    ///
142    /// Subsequent registrations will be children of this region.
143    pub fn open(&mut self, area: Rect, annotation: Annotation) -> usize {
144        let index = self.register(area, annotation);
145        self.open_stack.push(index);
146        self.current_depth += 1;
147        index
148    }
149
150    /// Closes the current region.
151    pub fn close(&mut self) {
152        self.open_stack.pop();
153        self.current_depth = self.current_depth.saturating_sub(1);
154    }
155
156    /// Returns the number of registered regions.
157    pub fn len(&self) -> usize {
158        self.regions.len()
159    }
160
161    /// Returns true if no regions are registered.
162    pub fn is_empty(&self) -> bool {
163        self.regions.is_empty()
164    }
165
166    /// Returns all registered regions.
167    pub fn regions(&self) -> &[RegionInfo] {
168        &self.regions
169    }
170
171    /// Returns a region by index.
172    pub fn get(&self, index: usize) -> Option<&RegionInfo> {
173        self.regions.get(index)
174    }
175
176    /// Returns the region at the given position.
177    ///
178    /// If multiple regions overlap, returns the deepest one (most specific).
179    pub fn region_at(&self, x: u16, y: u16) -> Option<&RegionInfo> {
180        self.regions
181            .iter()
182            .filter(|r| r.area.contains(x, y))
183            .max_by_key(|r| r.depth)
184    }
185
186    /// Returns all regions at the given position.
187    pub fn regions_at(&self, x: u16, y: u16) -> Vec<&RegionInfo> {
188        self.regions
189            .iter()
190            .filter(|r| r.area.contains(x, y))
191            .collect()
192    }
193
194    /// Finds regions by annotation id.
195    pub fn find_by_id(&self, id: &str) -> Vec<&RegionInfo> {
196        self.regions
197            .iter()
198            .filter(|r| r.annotation.has_id(id))
199            .collect()
200    }
201
202    /// Finds the first region with the given id.
203    pub fn get_by_id(&self, id: &str) -> Option<&RegionInfo> {
204        self.regions.iter().find(|r| r.annotation.has_id(id))
205    }
206
207    /// Finds regions by widget type.
208    pub fn find_by_type(&self, widget_type: &WidgetType) -> Vec<&RegionInfo> {
209        self.regions
210            .iter()
211            .filter(|r| r.annotation.is_type(widget_type))
212            .collect()
213    }
214
215    /// Returns all interactive regions.
216    pub fn interactive_regions(&self) -> Vec<&RegionInfo> {
217        self.regions
218            .iter()
219            .filter(|r| r.annotation.is_interactive())
220            .collect()
221    }
222
223    /// Returns the currently focused region, if any.
224    pub fn focused_region(&self) -> Option<&RegionInfo> {
225        self.regions.iter().find(|r| r.annotation.focused)
226    }
227
228    /// Returns root regions (depth 0).
229    pub fn root_regions(&self) -> Vec<&RegionInfo> {
230        self.regions.iter().filter(|r| r.depth == 0).collect()
231    }
232
233    /// Returns children of a region.
234    pub fn children_of(&self, index: usize) -> Vec<&RegionInfo> {
235        if let Some(region) = self.regions.get(index) {
236            region
237                .children
238                .iter()
239                .filter_map(|&i| self.regions.get(i))
240                .collect()
241        } else {
242            Vec::new()
243        }
244    }
245
246    /// Formats the registry as a tree for debugging.
247    pub fn format_tree(&self) -> String {
248        let mut output = String::new();
249
250        for region in &self.regions {
251            if region.parent.is_none() {
252                self.format_region(&mut output, region, 0);
253            }
254        }
255
256        output
257    }
258
259    fn format_region(&self, output: &mut String, region: &RegionInfo, indent: usize) {
260        let prefix = "  ".repeat(indent);
261        output.push_str(&format!(
262            "{}[{},{}+{}x{}] {}\n",
263            prefix,
264            region.area.x,
265            region.area.y,
266            region.area.width,
267            region.area.height,
268            region.annotation.description()
269        ));
270
271        for &child_idx in &region.children {
272            if let Some(child) = self.regions.get(child_idx) {
273                self.format_region(output, child, indent + 1);
274            }
275        }
276    }
277}
278
279#[cfg(test)]
280mod tests {
281    use super::*;
282
283    #[test]
284    fn test_registry_register() {
285        let mut registry = AnnotationRegistry::new();
286
287        let idx = registry.register(Rect::new(0, 0, 80, 24), Annotation::container("main"));
288
289        assert_eq!(idx, 0);
290        assert_eq!(registry.len(), 1);
291    }
292
293    #[test]
294    fn test_registry_nesting() {
295        let mut registry = AnnotationRegistry::new();
296
297        // Open container
298        let container = registry.open(Rect::new(0, 0, 80, 24), Annotation::container("main"));
299
300        // Add child
301        let button = registry.register(Rect::new(10, 10, 20, 3), Annotation::button("submit"));
302
303        // Close container
304        registry.close();
305
306        assert_eq!(registry.len(), 2);
307
308        let container_info = registry.get(container).unwrap();
309        assert_eq!(container_info.children, vec![button]);
310
311        let button_info = registry.get(button).unwrap();
312        assert_eq!(button_info.parent, Some(container));
313        assert_eq!(button_info.depth, 1);
314    }
315
316    #[test]
317    fn test_registry_region_at() {
318        let mut registry = AnnotationRegistry::new();
319
320        // Container
321        registry.open(Rect::new(0, 0, 80, 24), Annotation::container("main"));
322
323        // Button inside
324        registry.register(Rect::new(10, 10, 20, 3), Annotation::button("submit"));
325
326        registry.close();
327
328        // Point inside button
329        let region = registry.region_at(15, 11).unwrap();
330        assert!(region.annotation.has_id("submit"));
331
332        // Point outside button but inside container
333        let region = registry.region_at(5, 5).unwrap();
334        assert!(region.annotation.has_id("main"));
335
336        // Point outside everything
337        assert!(registry.region_at(100, 100).is_none());
338    }
339
340    #[test]
341    fn test_registry_find_by_id() {
342        let mut registry = AnnotationRegistry::new();
343
344        registry.register(Rect::new(0, 0, 10, 1), Annotation::input("username"));
345        registry.register(Rect::new(0, 2, 10, 1), Annotation::input("password"));
346        registry.register(Rect::new(0, 4, 10, 1), Annotation::button("submit"));
347
348        let found = registry.find_by_id("password");
349        assert_eq!(found.len(), 1);
350        assert!(found[0].annotation.has_id("password"));
351
352        let submit = registry.get_by_id("submit").unwrap();
353        assert_eq!(submit.annotation.widget_type, WidgetType::Button);
354    }
355
356    #[test]
357    fn test_registry_find_by_type() {
358        let mut registry = AnnotationRegistry::new();
359
360        registry.register(Rect::new(0, 0, 10, 1), Annotation::input("a"));
361        registry.register(Rect::new(0, 2, 10, 1), Annotation::input("b"));
362        registry.register(Rect::new(0, 4, 10, 1), Annotation::button("c"));
363
364        let inputs = registry.find_by_type(&WidgetType::Input);
365        assert_eq!(inputs.len(), 2);
366
367        let buttons = registry.find_by_type(&WidgetType::Button);
368        assert_eq!(buttons.len(), 1);
369    }
370
371    #[test]
372    fn test_registry_focused() {
373        let mut registry = AnnotationRegistry::new();
374
375        registry.register(Rect::new(0, 0, 10, 1), Annotation::input("a"));
376        registry.register(
377            Rect::new(0, 2, 10, 1),
378            Annotation::input("b").with_focus(true),
379        );
380
381        let focused = registry.focused_region().unwrap();
382        assert!(focused.annotation.has_id("b"));
383    }
384
385    #[test]
386    fn test_serializable_rect() {
387        let rect = SerializableRect::new(5, 10, 20, 30);
388
389        assert!(rect.contains(5, 10));
390        assert!(rect.contains(24, 39));
391        assert!(!rect.contains(25, 10));
392        assert!(!rect.contains(5, 40));
393    }
394
395    #[test]
396    fn test_rect_intersects() {
397        let a = SerializableRect::new(0, 0, 10, 10);
398        let b = SerializableRect::new(5, 5, 10, 10);
399        let c = SerializableRect::new(20, 20, 10, 10);
400
401        assert!(a.intersects(&b));
402        assert!(b.intersects(&a));
403        assert!(!a.intersects(&c));
404    }
405
406    #[test]
407    fn test_format_tree() {
408        let mut registry = AnnotationRegistry::new();
409
410        registry.open(Rect::new(0, 0, 80, 24), Annotation::dialog("Login"));
411        registry.register(Rect::new(5, 5, 30, 1), Annotation::input("username"));
412        registry.register(Rect::new(5, 7, 30, 1), Annotation::input("password"));
413        registry.register(Rect::new(5, 10, 10, 1), Annotation::button("submit"));
414        registry.close();
415
416        let tree = registry.format_tree();
417        assert!(tree.contains("Dialog"));
418        assert!(tree.contains("Input"));
419        assert!(tree.contains("Button"));
420    }
421
422    #[test]
423    fn test_registry_clear() {
424        let mut registry = AnnotationRegistry::new();
425
426        registry.register(Rect::new(0, 0, 10, 1), Annotation::button("a"));
427        registry.register(Rect::new(0, 2, 10, 1), Annotation::button("b"));
428
429        assert_eq!(registry.len(), 2);
430
431        registry.clear();
432
433        assert_eq!(registry.len(), 0);
434        assert!(registry.is_empty());
435    }
436
437    #[test]
438    fn test_registry_is_empty() {
439        let registry = AnnotationRegistry::new();
440        assert!(registry.is_empty());
441
442        let mut registry2 = AnnotationRegistry::new();
443        registry2.register(Rect::new(0, 0, 10, 1), Annotation::button("btn"));
444        assert!(!registry2.is_empty());
445    }
446
447    #[test]
448    fn test_registry_regions_at() {
449        let mut registry = AnnotationRegistry::new();
450
451        // Container at depth 0
452        registry.open(Rect::new(0, 0, 80, 24), Annotation::container("main"));
453        // Button at depth 1
454        registry.register(Rect::new(10, 10, 20, 3), Annotation::button("submit"));
455        registry.close();
456
457        // Point inside button overlaps both container and button
458        let regions = registry.regions_at(15, 11);
459        assert_eq!(regions.len(), 2);
460    }
461
462    #[test]
463    fn test_registry_interactive_regions() {
464        let mut registry = AnnotationRegistry::new();
465
466        // Non-interactive
467        registry.register(Rect::new(0, 0, 80, 24), Annotation::container("main"));
468        registry.register(Rect::new(0, 0, 10, 1), Annotation::label("title"));
469
470        // Interactive
471        registry.register(Rect::new(0, 2, 10, 1), Annotation::button("btn"));
472        registry.register(Rect::new(0, 4, 10, 1), Annotation::input("input"));
473        registry.register(Rect::new(0, 6, 10, 1), Annotation::checkbox("checkbox"));
474
475        let interactive = registry.interactive_regions();
476        assert_eq!(interactive.len(), 3);
477    }
478
479    #[test]
480    fn test_registry_root_regions() {
481        let mut registry = AnnotationRegistry::new();
482
483        // Root level
484        registry.open(Rect::new(0, 0, 40, 24), Annotation::container("left"));
485        registry.register(Rect::new(5, 5, 10, 1), Annotation::button("btn1"));
486        registry.close();
487
488        registry.open(Rect::new(40, 0, 40, 24), Annotation::container("right"));
489        registry.register(Rect::new(45, 5, 10, 1), Annotation::button("btn2"));
490        registry.close();
491
492        let roots = registry.root_regions();
493        assert_eq!(roots.len(), 2);
494    }
495
496    #[test]
497    fn test_registry_children_of() {
498        let mut registry = AnnotationRegistry::new();
499
500        let parent = registry.open(Rect::new(0, 0, 80, 24), Annotation::container("parent"));
501        registry.register(Rect::new(5, 5, 10, 1), Annotation::button("child1"));
502        registry.register(Rect::new(5, 8, 10, 1), Annotation::button("child2"));
503        registry.close();
504
505        let children = registry.children_of(parent);
506        assert_eq!(children.len(), 2);
507
508        // Non-existent index returns empty
509        let children = registry.children_of(999);
510        assert!(children.is_empty());
511    }
512
513    #[test]
514    fn test_registry_regions_accessor() {
515        let mut registry = AnnotationRegistry::new();
516
517        registry.register(Rect::new(0, 0, 10, 1), Annotation::button("a"));
518        registry.register(Rect::new(0, 2, 10, 1), Annotation::button("b"));
519
520        let regions = registry.regions();
521        assert_eq!(regions.len(), 2);
522    }
523
524    #[test]
525    fn test_serializable_rect_from_rect() {
526        let ratatui_rect = Rect::new(10, 20, 30, 40);
527        let serializable: SerializableRect = ratatui_rect.into();
528
529        assert_eq!(serializable.x, 10);
530        assert_eq!(serializable.y, 20);
531        assert_eq!(serializable.width, 30);
532        assert_eq!(serializable.height, 40);
533    }
534
535    #[test]
536    fn test_rect_from_serializable_rect() {
537        let serializable = SerializableRect::new(10, 20, 30, 40);
538        let ratatui_rect: Rect = serializable.into();
539
540        assert_eq!(ratatui_rect.x, 10);
541        assert_eq!(ratatui_rect.y, 20);
542        assert_eq!(ratatui_rect.width, 30);
543        assert_eq!(ratatui_rect.height, 40);
544    }
545
546    #[test]
547    fn test_registry_get_non_existent() {
548        let registry = AnnotationRegistry::new();
549        assert!(registry.get(0).is_none());
550        assert!(registry.get(999).is_none());
551    }
552
553    #[test]
554    fn test_registry_get_by_id_not_found() {
555        let mut registry = AnnotationRegistry::new();
556        registry.register(Rect::new(0, 0, 10, 1), Annotation::button("exists"));
557
558        assert!(registry.get_by_id("nonexistent").is_none());
559    }
560
561    #[test]
562    fn test_registry_focused_region_none() {
563        let mut registry = AnnotationRegistry::new();
564
565        registry.register(Rect::new(0, 0, 10, 1), Annotation::input("a"));
566        registry.register(Rect::new(0, 2, 10, 1), Annotation::input("b"));
567
568        // No focused region
569        assert!(registry.focused_region().is_none());
570    }
571
572    #[test]
573    fn test_registry_close_at_zero_depth() {
574        let mut registry = AnnotationRegistry::new();
575
576        // Register without opening
577        registry.register(Rect::new(0, 0, 10, 1), Annotation::button("btn"));
578
579        // Close when already at depth 0 should not panic
580        registry.close();
581        registry.close(); // Extra close
582
583        assert_eq!(registry.len(), 1);
584    }
585
586    #[test]
587    fn test_registry_default() {
588        let registry = AnnotationRegistry::default();
589        assert!(registry.is_empty());
590    }
591
592    #[test]
593    fn test_region_info_fields() {
594        let mut registry = AnnotationRegistry::new();
595
596        let parent_idx = registry.open(Rect::new(0, 0, 80, 24), Annotation::container("parent"));
597        let child_idx = registry.register(Rect::new(5, 5, 10, 1), Annotation::button("child"));
598        registry.close();
599
600        let child = registry.get(child_idx).unwrap();
601        assert_eq!(child.area.x, 5);
602        assert_eq!(child.area.y, 5);
603        assert_eq!(child.area.width, 10);
604        assert_eq!(child.area.height, 1);
605        assert_eq!(child.parent, Some(parent_idx));
606        assert!(child.children.is_empty());
607        assert_eq!(child.depth, 1);
608    }
609}