Skip to main content

rab/tui/
container.rs

1use crate::tui::Component;
2use crate::tui::component::RenderCache;
3
4/// Per-child render cache entry.
5struct ChildCache {
6    /// Cached render output.
7    cache: Option<RenderCache>,
8    /// Whether child needs re-render.
9    dirty: bool,
10}
11
12impl ChildCache {
13    fn new() -> Self {
14        Self {
15            cache: None,
16            dirty: true,
17        }
18    }
19}
20
21/// Container - a component that contains other components rendered vertically.
22/// Supports per-child caching for efficient re-rendering.
23pub struct Container {
24    children: Vec<Box<dyn Component>>,
25    /// Per-child cache state.
26    child_caches: Vec<ChildCache>,
27}
28
29impl Container {
30    pub fn new() -> Self {
31        Self {
32            children: Vec::new(),
33            child_caches: Vec::new(),
34        }
35    }
36
37    pub fn add_child(&mut self, component: Box<dyn Component>) {
38        self.child_caches.push(ChildCache::new());
39        self.children.push(component);
40    }
41
42    pub fn remove_child(&mut self, component: &dyn Component) {
43        // Use pointer-based identity check - simplistic but works for our use case
44        let idx = self.children.iter().position(|c| {
45            std::ptr::eq(
46                c.as_ref() as *const dyn Component,
47                component as *const dyn Component,
48            )
49        });
50        if let Some(idx) = idx {
51            self.children.remove(idx);
52            self.child_caches.remove(idx);
53        }
54    }
55
56    pub fn clear(&mut self) {
57        self.children.clear();
58        self.child_caches.clear();
59    }
60
61    pub fn children(&self) -> &[Box<dyn Component>] {
62        &self.children
63    }
64
65    pub fn children_mut(&mut self) -> &mut [Box<dyn Component>] {
66        &mut self.children
67    }
68
69    /// Mark all children as needing re-render.
70    pub fn invalidate_all(&mut self) {
71        for cache in &mut self.child_caches {
72            cache.dirty = true;
73            cache.cache = None;
74        }
75    }
76
77    /// Mark a specific child as needing re-render by index.
78    pub fn invalidate_child(&mut self, index: usize) {
79        if let Some(cache) = self.child_caches.get_mut(index) {
80            cache.dirty = true;
81            cache.cache = None;
82        }
83    }
84
85    /// Get the number of children.
86    pub fn len(&self) -> usize {
87        self.children.len()
88    }
89
90    /// Check if empty.
91    pub fn is_empty(&self) -> bool {
92        self.children.is_empty()
93    }
94}
95
96impl Default for Container {
97    fn default() -> Self {
98        Self::new()
99    }
100}
101
102impl Component for Container {
103    fn render(&self, width: usize) -> Vec<String> {
104        // NOTE: We can't use caching here because we don't have &mut self.
105        // Caching is handled at a higher level (TUI or parent container).
106        // For now, just render all children.
107        let mut lines = Vec::new();
108        for child in &self.children {
109            let child_lines = child.render(width);
110            lines.extend(child_lines);
111        }
112        lines
113    }
114
115    fn handle_input(&mut self, key: &crossterm::event::KeyEvent) -> bool {
116        for child in self.children.iter_mut().rev() {
117            if child.handle_input(key) {
118                return true;
119            }
120        }
121        false
122    }
123
124    fn invalidate(&mut self) {
125        for child in &mut self.children {
126            child.invalidate();
127        }
128        // Also clear all caches
129        for cache in &mut self.child_caches {
130            cache.dirty = true;
131            cache.cache = None;
132        }
133    }
134
135    fn is_dirty(&self) -> bool {
136        // Container is dirty if any child is dirty
137        self.child_caches.iter().any(|c| c.dirty)
138    }
139
140    fn clear_dirty(&mut self) {
141        for cache in &mut self.child_caches {
142            cache.dirty = false;
143        }
144    }
145}
146
147/// CachedContainer - a Container that caches its rendered output.
148/// Used for components that need efficient re-rendering (e.g., chat area).
149pub struct CachedContainer {
150    inner: Container,
151    cache: Option<RenderCache>,
152    dirty: bool,
153}
154
155impl CachedContainer {
156    pub fn new() -> Self {
157        Self {
158            inner: Container::new(),
159            cache: None,
160            dirty: true,
161        }
162    }
163
164    pub fn inner(&self) -> &Container {
165        &self.inner
166    }
167
168    pub fn inner_mut(&mut self) -> &mut Container {
169        self.dirty = true;
170        &mut self.inner
171    }
172
173    /// Add a child component.
174    pub fn add_child(&mut self, component: Box<dyn Component>) {
175        self.inner.add_child(component);
176        self.dirty = true;
177    }
178
179    /// Remove a child component.
180    pub fn remove_child(&mut self, component: &dyn Component) {
181        self.inner.remove_child(component);
182        self.dirty = true;
183    }
184
185    /// Clear all children.
186    pub fn clear(&mut self) {
187        self.inner.clear();
188        self.cache = None;
189        self.dirty = true;
190    }
191
192    /// Mark as needing re-render.
193    pub fn invalidate(&mut self) {
194        self.dirty = true;
195        self.cache = None;
196        self.inner.invalidate_all();
197    }
198
199    /// Get children.
200    pub fn children(&self) -> &[Box<dyn Component>] {
201        self.inner.children()
202    }
203
204    /// Get mutable children.
205    pub fn children_mut(&mut self) -> &mut [Box<dyn Component>] {
206        self.dirty = true;
207        self.inner.children_mut()
208    }
209
210    /// Get number of children.
211    pub fn len(&self) -> usize {
212        self.inner.len()
213    }
214
215    /// Check if empty.
216    pub fn is_empty(&self) -> bool {
217        self.inner.is_empty()
218    }
219}
220
221impl Default for CachedContainer {
222    fn default() -> Self {
223        Self::new()
224    }
225}
226
227impl Component for CachedContainer {
228    fn render(&self, width: usize) -> Vec<String> {
229        // For now, just delegate to inner container.
230        // Full caching requires &mut self which render() doesn't have.
231        // This is handled at the TUI level with render_with_cache().
232        self.inner.render(width)
233    }
234
235    fn handle_input(&mut self, key: &crossterm::event::KeyEvent) -> bool {
236        let result = self.inner.handle_input(key);
237        if result {
238            self.dirty = true;
239        }
240        result
241    }
242
243    fn invalidate(&mut self) {
244        self.dirty = true;
245        self.cache = None;
246        self.inner.invalidate();
247    }
248
249    fn is_dirty(&self) -> bool {
250        self.dirty || self.inner.is_dirty()
251    }
252
253    fn clear_dirty(&mut self) {
254        self.dirty = false;
255        self.inner.clear_dirty();
256    }
257}