sql_cli/ui/rendering/
table_widget_manager.rs

1//! Table Widget Manager - Centralized table state and rendering
2//!
3//! This manager owns the table widget state and ensures all updates
4//! go through a single interface, properly triggering re-renders.
5
6use crate::data::data_view::DataView;
7use crate::ui::rendering::render_state::RenderState;
8use crate::ui::viewport_manager::ViewportManager;
9use std::sync::Arc;
10use tracing::{debug, info, trace};
11
12/// Position in the table (row, column)
13#[derive(Debug, Clone, Copy, PartialEq)]
14pub struct TablePosition {
15    pub row: usize,
16    pub column: usize,
17}
18
19/// Table widget manager that owns all table-related state
20pub struct TableWidgetManager {
21    /// Current cursor/crosshair position
22    position: TablePosition,
23    /// Previous position (for clearing old crosshair)
24    previous_position: Option<TablePosition>,
25    /// Viewport manager for column/row visibility
26    viewport_manager: Option<ViewportManager>,
27    /// Render state tracker
28    render_state: RenderState,
29    /// Current data view
30    dataview: Option<Arc<DataView>>,
31    /// Scroll offset (row, column)
32    scroll_offset: (usize, usize),
33}
34
35impl Default for TableWidgetManager {
36    fn default() -> Self {
37        Self::new()
38    }
39}
40
41impl TableWidgetManager {
42    /// Create a new table widget manager
43    #[must_use]
44    pub fn new() -> Self {
45        Self {
46            position: TablePosition { row: 0, column: 0 },
47            previous_position: None,
48            viewport_manager: None,
49            render_state: RenderState::new(),
50            dataview: None,
51            scroll_offset: (0, 0),
52        }
53    }
54
55    /// Set the data view for the table
56    pub fn set_dataview(&mut self, dataview: Arc<DataView>) {
57        debug!("TableWidgetManager: Setting new dataview");
58        self.dataview = Some(dataview.clone());
59
60        // Update viewport manager with new dataview
61        if let Some(ref mut vm) = self.viewport_manager {
62            vm.set_dataview(dataview);
63        } else {
64            self.viewport_manager = Some(ViewportManager::new(dataview));
65        }
66
67        self.render_state.on_data_change();
68    }
69
70    /// Navigate to a specific position
71    pub fn navigate_to(&mut self, row: usize, column: usize) {
72        let old_pos = self.position;
73        info!(
74            "TableWidgetManager: Navigate from ({}, {}) to ({}, {})",
75            old_pos.row, old_pos.column, row, column
76        );
77
78        // Store previous position for clearing
79        if self.position.row != row || self.position.column != column {
80            self.previous_position = Some(self.position);
81            self.position = TablePosition { row, column };
82
83            info!("TableWidgetManager: Position changed, marking dirty for re-render");
84
85            // Update viewport manager crosshair
86            if let Some(ref mut vm) = self.viewport_manager {
87                vm.set_crosshair(row, column);
88                info!(
89                    "TableWidgetManager: Updated ViewportManager crosshair to ({}, {})",
90                    row, column
91                );
92
93                // Calculate if we need to scroll
94                let viewport_height = 79; // TODO: Get from actual terminal size
95                let viewport_width = 100; // TODO: Get from actual terminal size
96
97                // Update scroll offset if needed
98                let new_row_offset = if row < self.scroll_offset.0 {
99                    info!(
100                        "TableWidgetManager: Row {} is above viewport, scrolling up",
101                        row
102                    );
103                    row // Scroll up
104                } else if row >= self.scroll_offset.0 + viewport_height {
105                    let centered = row.saturating_sub(viewport_height / 2);
106                    info!(
107                        "TableWidgetManager: Row {} is below viewport, centering at {}",
108                        row, centered
109                    );
110                    centered // Center it
111                } else {
112                    trace!(
113                        "TableWidgetManager: Row {} is visible in current viewport",
114                        row
115                    );
116                    self.scroll_offset.0 // Keep current
117                };
118
119                if new_row_offset != self.scroll_offset.0 {
120                    info!(
121                        "TableWidgetManager: Changing scroll offset from {} to {}",
122                        self.scroll_offset.0, new_row_offset
123                    );
124                    self.scroll_offset.0 = new_row_offset;
125                    vm.set_viewport(
126                        new_row_offset,
127                        self.scroll_offset.1,
128                        viewport_width as u16,
129                        viewport_height as u16,
130                    );
131                }
132            }
133
134            // Mark for re-render
135            self.render_state.on_navigation_change();
136            info!("TableWidgetManager: State marked dirty, will trigger re-render");
137        } else {
138            trace!("TableWidgetManager: Position unchanged, no re-render needed");
139        }
140    }
141
142    /// Move cursor by relative amount
143    pub fn move_cursor(&mut self, row_delta: isize, col_delta: isize) {
144        let new_row = (self.position.row as isize + row_delta).max(0) as usize;
145        let new_col = (self.position.column as isize + col_delta).max(0) as usize;
146
147        // Clamp to data bounds
148        if let Some(ref dv) = self.dataview {
149            let max_row = dv.row_count().saturating_sub(1);
150            let max_col = dv.column_count().saturating_sub(1);
151            let clamped_row = new_row.min(max_row);
152            let clamped_col = new_col.min(max_col);
153
154            self.navigate_to(clamped_row, clamped_col);
155        }
156    }
157
158    /// Handle search result navigation
159    pub fn navigate_to_search_match(&mut self, row: usize, column: usize) {
160        info!(
161            "TableWidgetManager: Navigate to search match at ({}, {})",
162            row, column
163        );
164
165        // Force immediate render for search results
166        self.navigate_to(row, column);
167        self.render_state.on_search_update();
168    }
169
170    /// Check if render is needed
171    #[must_use]
172    pub fn needs_render(&self) -> bool {
173        self.render_state.needs_render()
174    }
175
176    /// Mark that render has completed
177    pub fn rendered(&mut self) {
178        self.render_state.rendered();
179        // Clear previous position after successful render
180        self.previous_position = None;
181    }
182
183    /// Get current position
184    #[must_use]
185    pub fn position(&self) -> TablePosition {
186        self.position
187    }
188
189    /// Get previous position (for clearing)
190    #[must_use]
191    pub fn previous_position(&self) -> Option<TablePosition> {
192        self.previous_position
193    }
194
195    /// Force a re-render
196    pub fn force_render(&mut self) {
197        debug!("TableWidgetManager: Forcing render");
198        self.render_state.force_render();
199    }
200
201    /// Set high-frequency mode for responsive updates
202    pub fn set_high_frequency_mode(&mut self, enabled: bool) {
203        self.render_state.set_high_frequency_mode(enabled);
204    }
205
206    /// Get the viewport manager
207    #[must_use]
208    pub fn viewport_manager(&self) -> Option<&ViewportManager> {
209        self.viewport_manager.as_ref()
210    }
211
212    /// Get mutable viewport manager
213    pub fn viewport_manager_mut(&mut self) -> Option<&mut ViewportManager> {
214        self.viewport_manager.as_mut()
215    }
216
217    /// Handle debounced search action
218    pub fn on_debounced_search(&mut self, row: usize, column: usize) {
219        info!(
220            "TableWidgetManager: Debounced search navigating to ({}, {})",
221            row, column
222        );
223        self.navigate_to(row, column);
224        self.render_state.on_debounced_action();
225    }
226
227    /// Get render state for debugging
228    #[must_use]
229    pub fn render_state(&self) -> &RenderState {
230        &self.render_state
231    }
232
233    /// Update scroll offset
234    pub fn set_scroll_offset(&mut self, row_offset: usize, col_offset: usize) {
235        if self.scroll_offset != (row_offset, col_offset) {
236            debug!(
237                "TableWidgetManager: Scroll offset changed to ({}, {})",
238                row_offset, col_offset
239            );
240            self.scroll_offset = (row_offset, col_offset);
241            self.render_state.on_navigation_change();
242        }
243    }
244
245    /// Get current scroll offset
246    #[must_use]
247    pub fn scroll_offset(&self) -> (usize, usize) {
248        self.scroll_offset
249    }
250
251    /// Check and perform render if needed
252    /// Returns true if render was performed
253    pub fn check_and_render<F>(&mut self, mut render_fn: F) -> bool
254    where
255        F: FnMut(&TablePosition, &RenderState),
256    {
257        if self.needs_render() {
258            info!("═══════════════════════════════════════════════════════");
259            info!("TableWidgetManager: RENDERING TABLE");
260            info!(
261                "  Crosshair position: ({}, {})",
262                self.position.row, self.position.column
263            );
264            info!(
265                "  Scroll offset: ({}, {})",
266                self.scroll_offset.0, self.scroll_offset.1
267            );
268            info!("  Render reason: {:?}", self.render_state.dirty_reason());
269            if let Some(prev) = self.previous_position {
270                info!("  Previous position: ({}, {})", prev.row, prev.column);
271            }
272            info!("═══════════════════════════════════════════════════════");
273
274            // Call the actual render function
275            render_fn(&self.position, &self.render_state);
276
277            // Mark as rendered
278            self.rendered();
279
280            info!("TableWidgetManager: Render complete");
281            true
282        } else {
283            trace!("TableWidgetManager: No render needed (not dirty or throttled)");
284            false
285        }
286    }
287}