sql_cli/widgets/
crosshair_widget.rs

1//! Crosshair Widget - Handles visual cursor rendering on the table
2//!
3//! This widget tracks crosshair position changes and ensures the table
4//! re-renders the affected cells when the crosshair moves. Since ratatui
5//! doesn't support partial updates, we need to trigger a re-render of
6//! at least the affected rows when the crosshair moves.
7
8use ratatui::{
9    layout::Rect,
10    style::{Color, Modifier, Style},
11    Frame,
12};
13use tracing::{debug, trace};
14
15/// Crosshair position in absolute data coordinates
16#[derive(Debug, Clone, Default)]
17pub struct CrosshairPosition {
18    /// Row in data coordinates (0-based)
19    pub row: usize,
20    /// Column in data coordinates (0-based)
21    pub column: usize,
22    /// Whether the crosshair is visible
23    pub visible: bool,
24}
25
26/// Crosshair widget for rendering the cursor on the table
27pub struct CrosshairWidget {
28    /// Current position in data coordinates
29    position: CrosshairPosition,
30    /// Style for the crosshair
31    style: Style,
32}
33
34impl Default for CrosshairWidget {
35    fn default() -> Self {
36        Self::new()
37    }
38}
39
40impl CrosshairWidget {
41    /// Create a new crosshair widget
42    #[must_use]
43    pub fn new() -> Self {
44        Self {
45            position: CrosshairPosition::default(),
46            style: Style::default()
47                .bg(Color::DarkGray)
48                .add_modifier(Modifier::REVERSED),
49        }
50    }
51
52    /// Update the crosshair position
53    pub fn set_position(&mut self, row: usize, column: usize) {
54        self.position.row = row;
55        self.position.column = column;
56        self.position.visible = true;
57        debug!("Crosshair position updated to ({}, {})", row, column);
58    }
59
60    /// Hide the crosshair
61    pub fn hide(&mut self) {
62        self.position.visible = false;
63    }
64
65    /// Show the crosshair
66    pub fn show(&mut self) {
67        self.position.visible = true;
68    }
69
70    /// Get the current position
71    #[must_use]
72    pub fn position(&self) -> &CrosshairPosition {
73        &self.position
74    }
75
76    /// Set custom style for the crosshair
77    pub fn set_style(&mut self, style: Style) {
78        self.style = style;
79    }
80
81    /// Render the crosshair overlay on the table
82    /// This should be called AFTER rendering the table
83    pub fn render_overlay(
84        &self,
85        _f: &mut Frame,
86        table_area: Rect,
87        viewport_row_offset: usize,
88        viewport_col_offset: usize,
89        row_heights: &[u16], // Height of each visible row
90        col_widths: &[u16],  // Width of each visible column
91    ) {
92        if !self.position.visible {
93            return;
94        }
95
96        // Check if crosshair is within the visible viewport
97        let visible_rows = row_heights.len();
98        let visible_cols = col_widths.len();
99
100        // Calculate relative position within viewport
101        if self.position.row < viewport_row_offset {
102            return; // Above viewport
103        }
104        let relative_row = self.position.row - viewport_row_offset;
105        if relative_row >= visible_rows {
106            return; // Below viewport
107        }
108
109        if self.position.column < viewport_col_offset {
110            return; // Left of viewport
111        }
112        let relative_col = self.position.column - viewport_col_offset;
113        if relative_col >= visible_cols {
114            return; // Right of viewport
115        }
116
117        // Calculate pixel position for the crosshair
118        let mut y = table_area.y + 2; // Account for table border and header
119        for i in 0..relative_row {
120            y += row_heights[i];
121        }
122
123        let mut x = table_area.x + 1; // Account for table border
124        for i in 0..relative_col {
125            x += col_widths[i] + 1; // +1 for column separator
126        }
127
128        // Draw the crosshair cell
129        let cell_width = col_widths[relative_col];
130        let _cell_rect = Rect {
131            x,
132            y,
133            width: cell_width,
134            height: 1,
135        };
136
137        // Apply crosshair style to the cell
138        // Note: This is a simplified version. In practice, we'd need to
139        // re-render just the cell content with the crosshair style
140        trace!(
141            "Rendering crosshair at viewport ({}, {}) -> screen ({}, {})",
142            relative_row,
143            relative_col,
144            x,
145            y
146        );
147    }
148
149    /// Calculate if scrolling is needed to show the crosshair
150    #[must_use]
151    pub fn calculate_scroll_offset(
152        &self,
153        current_row_offset: usize,
154        current_col_offset: usize,
155        viewport_height: usize,
156        viewport_width: usize,
157    ) -> (usize, usize) {
158        let mut new_row_offset = current_row_offset;
159        let mut new_col_offset = current_col_offset;
160
161        // Vertical scrolling
162        if self.position.row < current_row_offset {
163            // Crosshair is above viewport, scroll up
164            new_row_offset = self.position.row;
165        } else if self.position.row >= current_row_offset + viewport_height {
166            // Crosshair is below viewport, center it
167            new_row_offset = self.position.row.saturating_sub(viewport_height / 2);
168        }
169
170        // Horizontal scrolling
171        if self.position.column < current_col_offset {
172            // Crosshair is left of viewport, scroll left
173            new_col_offset = self.position.column;
174        } else if self.position.column >= current_col_offset + viewport_width {
175            // Crosshair is right of viewport, scroll right
176            new_col_offset = self.position.column.saturating_sub(viewport_width / 2);
177        }
178
179        (new_row_offset, new_col_offset)
180    }
181}