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}