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 CrosshairWidget {
35    /// Create a new crosshair widget
36    pub fn new() -> Self {
37        Self {
38            position: CrosshairPosition::default(),
39            style: Style::default()
40                .bg(Color::DarkGray)
41                .add_modifier(Modifier::REVERSED),
42        }
43    }
44
45    /// Update the crosshair position
46    pub fn set_position(&mut self, row: usize, column: usize) {
47        self.position.row = row;
48        self.position.column = column;
49        self.position.visible = true;
50        debug!("Crosshair position updated to ({}, {})", row, column);
51    }
52
53    /// Hide the crosshair
54    pub fn hide(&mut self) {
55        self.position.visible = false;
56    }
57
58    /// Show the crosshair
59    pub fn show(&mut self) {
60        self.position.visible = true;
61    }
62
63    /// Get the current position
64    pub fn position(&self) -> &CrosshairPosition {
65        &self.position
66    }
67
68    /// Set custom style for the crosshair
69    pub fn set_style(&mut self, style: Style) {
70        self.style = style;
71    }
72
73    /// Render the crosshair overlay on the table
74    /// This should be called AFTER rendering the table
75    pub fn render_overlay(
76        &self,
77        f: &mut Frame,
78        table_area: Rect,
79        viewport_row_offset: usize,
80        viewport_col_offset: usize,
81        row_heights: &[u16], // Height of each visible row
82        col_widths: &[u16],  // Width of each visible column
83    ) {
84        if !self.position.visible {
85            return;
86        }
87
88        // Check if crosshair is within the visible viewport
89        let visible_rows = row_heights.len();
90        let visible_cols = col_widths.len();
91
92        // Calculate relative position within viewport
93        if self.position.row < viewport_row_offset {
94            return; // Above viewport
95        }
96        let relative_row = self.position.row - viewport_row_offset;
97        if relative_row >= visible_rows {
98            return; // Below viewport
99        }
100
101        if self.position.column < viewport_col_offset {
102            return; // Left of viewport
103        }
104        let relative_col = self.position.column - viewport_col_offset;
105        if relative_col >= visible_cols {
106            return; // Right of viewport
107        }
108
109        // Calculate pixel position for the crosshair
110        let mut y = table_area.y + 2; // Account for table border and header
111        for i in 0..relative_row {
112            y += row_heights[i];
113        }
114
115        let mut x = table_area.x + 1; // Account for table border
116        for i in 0..relative_col {
117            x += col_widths[i] + 1; // +1 for column separator
118        }
119
120        // Draw the crosshair cell
121        let cell_width = col_widths[relative_col];
122        let cell_rect = Rect {
123            x,
124            y,
125            width: cell_width,
126            height: 1,
127        };
128
129        // Apply crosshair style to the cell
130        // Note: This is a simplified version. In practice, we'd need to
131        // re-render just the cell content with the crosshair style
132        trace!(
133            "Rendering crosshair at viewport ({}, {}) -> screen ({}, {})",
134            relative_row,
135            relative_col,
136            x,
137            y
138        );
139    }
140
141    /// Calculate if scrolling is needed to show the crosshair
142    pub fn calculate_scroll_offset(
143        &self,
144        current_row_offset: usize,
145        current_col_offset: usize,
146        viewport_height: usize,
147        viewport_width: usize,
148    ) -> (usize, usize) {
149        let mut new_row_offset = current_row_offset;
150        let mut new_col_offset = current_col_offset;
151
152        // Vertical scrolling
153        if self.position.row < current_row_offset {
154            // Crosshair is above viewport, scroll up
155            new_row_offset = self.position.row;
156        } else if self.position.row >= current_row_offset + viewport_height {
157            // Crosshair is below viewport, center it
158            new_row_offset = self.position.row.saturating_sub(viewport_height / 2);
159        }
160
161        // Horizontal scrolling
162        if self.position.column < current_col_offset {
163            // Crosshair is left of viewport, scroll left
164            new_col_offset = self.position.column;
165        } else if self.position.column >= current_col_offset + viewport_width {
166            // Crosshair is right of viewport, scroll right
167            new_col_offset = self.position.column.saturating_sub(viewport_width / 2);
168        }
169
170        (new_row_offset, new_col_offset)
171    }
172}