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