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