Skip to main content

par_term_render/custom_shader_renderer/
cursor.rs

1//! Cursor tracking methods for Ghostty-compatible shader effects.
2//!
3//! This module provides cursor position tracking and style-based dimension
4//! calculations for shader-based cursor animations like trails and glows.
5
6use par_term_config::color_u8_to_f32_a;
7use par_term_emu_core_rust::cursor::CursorStyle;
8
9use super::CustomShaderRenderer;
10
11impl CustomShaderRenderer {
12    /// Update cursor position and appearance for shader effects
13    ///
14    /// This method tracks cursor movement and records the time of change,
15    /// enabling Ghostty-compatible cursor trail effects and animations.
16    ///
17    /// # Arguments
18    /// * `col` - Cursor column position (0-based)
19    /// * `row` - Cursor row position (0-based)
20    /// * `opacity` - Cursor opacity (0.0 = invisible, 1.0 = fully visible)
21    /// * `cursor_color` - Cursor RGBA color
22    /// * `style` - Cursor style (Block, Beam, Underline)
23    pub fn update_cursor(
24        &mut self,
25        col: usize,
26        row: usize,
27        opacity: f32,
28        cursor_color: [f32; 4],
29        style: CursorStyle,
30    ) {
31        let new_pos = (col, row);
32        let style_changed = style != self.current_cursor_style;
33        let pos_changed = new_pos != self.current_cursor_pos;
34
35        if pos_changed || style_changed {
36            // Store previous state before updating
37            self.previous_cursor_pos = self.current_cursor_pos;
38            self.previous_cursor_opacity = self.current_cursor_opacity;
39            self.previous_cursor_color = self.current_cursor_color;
40            self.previous_cursor_style = self.current_cursor_style;
41            self.current_cursor_pos = new_pos;
42            self.current_cursor_style = style;
43
44            // Record time of change (same timebase as iTime)
45            self.cursor_change_time = if self.animation_enabled {
46                self.start_time.elapsed().as_secs_f32() * self.animation_speed.max(0.0)
47            } else {
48                0.0
49            };
50
51            if pos_changed {
52                log::trace!(
53                    "Cursor moved: ({}, {}) -> ({}, {}), change_time={:.3}",
54                    self.previous_cursor_pos.0,
55                    self.previous_cursor_pos.1,
56                    col,
57                    row,
58                    self.cursor_change_time
59                );
60            }
61        }
62        self.current_cursor_opacity = opacity;
63        self.current_cursor_color = cursor_color;
64    }
65
66    /// Hide cursor-driven shader effects until a visible cursor update arrives.
67    pub fn clear_cursor(&mut self) {
68        if self.current_cursor_opacity == 0.0 && self.current_cursor_color == [0.0, 0.0, 0.0, 0.0] {
69            return;
70        }
71
72        self.previous_cursor_pos = self.current_cursor_pos;
73        self.previous_cursor_opacity = self.current_cursor_opacity;
74        self.previous_cursor_color = self.current_cursor_color;
75        self.previous_cursor_style = self.current_cursor_style;
76        self.current_cursor_opacity = 0.0;
77        self.current_cursor_color = [0.0, 0.0, 0.0, 0.0];
78        self.cursor_change_time = if self.animation_enabled {
79            self.start_time.elapsed().as_secs_f32() * self.animation_speed.max(0.0)
80        } else {
81            0.0
82        };
83    }
84
85    /// Update cell dimensions for cursor pixel position calculation
86    ///
87    /// # Arguments
88    /// * `cell_width` - Cell width in pixels
89    /// * `cell_height` - Cell height in pixels
90    /// * `padding` - Window padding in pixels
91    pub fn update_cell_dimensions(&mut self, cell_width: f32, cell_height: f32, padding: f32) {
92        self.cursor_cell_width = cell_width;
93        self.cursor_cell_height = cell_height;
94        self.cursor_window_padding = padding;
95    }
96
97    /// Set vertical content offset (e.g., tab bar height)
98    pub fn set_content_offset_y(&mut self, offset: f32) {
99        self.cursor_content_offset_y = offset;
100    }
101
102    /// Set horizontal content offset (e.g., tab bar on left)
103    pub fn set_content_offset_x(&mut self, offset: f32) {
104        self.cursor_content_offset_x = offset;
105    }
106
107    /// Set display scale factor for DPI-aware cursor sizing
108    pub fn set_scale_factor(&mut self, scale_factor: f32) {
109        self.scale_factor = scale_factor;
110    }
111
112    /// Convert cursor cell coordinates to pixel coordinates
113    ///
114    /// Returns (x, y) in pixels from top-left corner of the window.
115    pub(super) fn cursor_to_pixels(&self, col: usize, row: usize) -> (f32, f32) {
116        let x = self.cursor_window_padding
117            + self.cursor_content_offset_x
118            + (col as f32 * self.cursor_cell_width);
119        let y = self.cursor_window_padding
120            + self.cursor_content_offset_y
121            + (row as f32 * self.cursor_cell_height);
122        (x, y)
123    }
124
125    /// Get cursor width in pixels based on cursor style.
126    /// Returns physical pixels (cell dimensions are already in physical pixels).
127    pub(super) fn cursor_width_for_style(&self, style: CursorStyle, scale_factor: f32) -> f32 {
128        match style {
129            // Block cursor: full cell width
130            CursorStyle::SteadyBlock | CursorStyle::BlinkingBlock => self.cursor_cell_width,
131            // Beam/Bar cursor: thin vertical line (2 logical pixels, scaled)
132            CursorStyle::SteadyBar | CursorStyle::BlinkingBar => 2.0 * scale_factor,
133            // Underline cursor: full cell width
134            CursorStyle::SteadyUnderline | CursorStyle::BlinkingUnderline => self.cursor_cell_width,
135        }
136    }
137
138    /// Get cursor height in pixels based on cursor style.
139    /// Returns physical pixels (cell dimensions are already in physical pixels).
140    pub(super) fn cursor_height_for_style(&self, style: CursorStyle, scale_factor: f32) -> f32 {
141        match style {
142            // Block cursor: full cell height
143            CursorStyle::SteadyBlock | CursorStyle::BlinkingBlock => self.cursor_cell_height,
144            // Beam/Bar cursor: full cell height
145            CursorStyle::SteadyBar | CursorStyle::BlinkingBar => self.cursor_cell_height,
146            // Underline cursor: thin horizontal line (2 logical pixels, scaled)
147            CursorStyle::SteadyUnderline | CursorStyle::BlinkingUnderline => 2.0 * scale_factor,
148        }
149    }
150
151    /// Check if cursor animation might need continuous rendering
152    ///
153    /// Returns true if a cursor trail animation is likely still in progress
154    /// (within 1 second of the last cursor movement).
155    pub fn cursor_needs_animation(&self) -> bool {
156        if self.animation_enabled {
157            let current_time =
158                self.start_time.elapsed().as_secs_f32() * self.animation_speed.max(0.0);
159            // Allow 1 second for cursor trail animations to complete
160            (current_time - self.cursor_change_time) < 1.0
161        } else {
162            false
163        }
164    }
165
166    /// Update cursor shader configuration from config values
167    ///
168    /// # Arguments
169    /// * `color` - Cursor color for shader effects [R, G, B] (0-255)
170    /// * `trail_duration` - Duration of cursor trail effect in seconds
171    /// * `glow_radius` - Radius of cursor glow effect in pixels
172    /// * `glow_intensity` - Intensity of cursor glow effect (0.0-1.0)
173    pub fn update_cursor_shader_config(
174        &mut self,
175        color: [u8; 3],
176        trail_duration: f32,
177        glow_radius: f32,
178        glow_intensity: f32,
179    ) {
180        self.cursor_shader_color = color_u8_to_f32_a(color, 1.0);
181        self.cursor_trail_duration = trail_duration.max(0.0);
182        self.cursor_glow_radius = glow_radius.max(0.0);
183        self.cursor_glow_intensity = glow_intensity.clamp(0.0, 1.0);
184    }
185}