par_term/renderer/shaders.rs
1use super::Renderer;
2use crate::custom_shader_renderer::CustomShaderRenderer;
3use anyhow::Result;
4
5impl Renderer {
6 /// Enable or disable animation for the custom shader at runtime
7 #[allow(dead_code)]
8 pub fn set_custom_shader_animation(&mut self, enabled: bool) {
9 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
10 custom_shader.set_animation_enabled(enabled);
11 self.dirty = true;
12 }
13 }
14
15 /// Update mouse position for custom shader (iMouse uniform)
16 ///
17 /// # Arguments
18 /// * `x` - Mouse X position in pixels (0 = left edge)
19 /// * `y` - Mouse Y position in pixels (0 = top edge)
20 pub fn set_shader_mouse_position(&mut self, x: f32, y: f32) {
21 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
22 custom_shader.set_mouse_position(x, y);
23 }
24 }
25
26 /// Update mouse button state for custom shader (iMouse uniform)
27 ///
28 /// # Arguments
29 /// * `pressed` - True if left mouse button is pressed
30 /// * `x` - Mouse X position at time of click/release
31 /// * `y` - Mouse Y position at time of click/release
32 pub fn set_shader_mouse_button(&mut self, pressed: bool, x: f32, y: f32) {
33 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
34 custom_shader.set_mouse_button(pressed, x, y);
35 }
36 }
37
38 /// Update cursor state for custom shader (Ghostty-compatible cursor uniforms)
39 ///
40 /// This enables cursor trail effects and other cursor-based animations in custom shaders.
41 ///
42 /// # Arguments
43 /// * `col` - Cursor column position (0-based)
44 /// * `row` - Cursor row position (0-based)
45 /// * `opacity` - Cursor opacity (0.0 = invisible, 1.0 = fully visible)
46 /// * `color` - Cursor RGBA color
47 /// * `style` - Cursor style (Block, Beam, Underline)
48 pub fn update_shader_cursor(
49 &mut self,
50 col: usize,
51 row: usize,
52 opacity: f32,
53 color: [f32; 4],
54 style: par_term_emu_core_rust::cursor::CursorStyle,
55 ) {
56 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
57 custom_shader.update_cursor(col, row, opacity, color, style);
58 }
59 // Also update cursor shader renderer
60 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
61 cursor_shader.update_cursor(col, row, opacity, color, style);
62 }
63 }
64
65 /// Update cursor shader configuration from config values
66 ///
67 /// # Arguments
68 /// * `color` - Cursor color for shader effects [R, G, B] (0-255)
69 /// * `trail_duration` - Duration of cursor trail effect in seconds
70 /// * `glow_radius` - Radius of cursor glow effect in pixels
71 /// * `glow_intensity` - Intensity of cursor glow effect (0.0-1.0)
72 pub fn update_cursor_shader_config(
73 &mut self,
74 color: [u8; 3],
75 trail_duration: f32,
76 glow_radius: f32,
77 glow_intensity: f32,
78 ) {
79 // Update both shaders with cursor config
80 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
81 custom_shader.update_cursor_shader_config(
82 color,
83 trail_duration,
84 glow_radius,
85 glow_intensity,
86 );
87 }
88 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
89 cursor_shader.update_cursor_shader_config(
90 color,
91 trail_duration,
92 glow_radius,
93 glow_intensity,
94 );
95 }
96 }
97
98 /// Enable or disable the cursor shader at runtime
99 ///
100 /// # Arguments
101 /// * `enabled` - Whether to enable the cursor shader
102 /// * `path` - Optional shader path (relative to shaders folder or absolute)
103 /// * `window_opacity` - Current window opacity
104 /// * `animation_enabled` - Whether animation is enabled
105 /// * `animation_speed` - Animation speed multiplier
106 ///
107 /// # Returns
108 /// Ok(()) if successful, Err with error message if compilation fails
109 #[allow(clippy::too_many_arguments)]
110 pub fn set_cursor_shader_enabled(
111 &mut self,
112 enabled: bool,
113 path: Option<&str>,
114 window_opacity: f32,
115 animation_enabled: bool,
116 animation_speed: f32,
117 ) -> Result<(), String> {
118 match (enabled, path) {
119 (true, Some(path)) => {
120 let path_changed = self.cursor_shader_path.as_ref().is_none_or(|p| p != path);
121
122 // If we already have a shader renderer and path hasn't changed, just update flags
123 if let Some(renderer) = &mut self.cursor_shader_renderer
124 && !path_changed
125 {
126 renderer.set_animation_enabled(animation_enabled);
127 renderer.set_animation_speed(animation_speed);
128 renderer.set_opacity(window_opacity);
129 self.dirty = true;
130 return Ok(());
131 }
132
133 let shader_path_full = crate::config::Config::shader_path(path);
134 // Cursor shader doesn't use channel textures
135 let empty_channels: [Option<std::path::PathBuf>; 4] = [None, None, None, None];
136 match CustomShaderRenderer::new(
137 self.cell_renderer.device(),
138 self.cell_renderer.queue(),
139 self.cell_renderer.surface_format(),
140 &shader_path_full,
141 self.size.width,
142 self.size.height,
143 animation_enabled,
144 animation_speed,
145 window_opacity,
146 1.0, // Text opacity (cursor shader always uses 1.0)
147 true, // Full content mode (cursor shader always uses full content)
148 &empty_channels,
149 ) {
150 Ok(mut renderer) => {
151 // Sync cell dimensions for cursor position calculation
152 renderer.update_cell_dimensions(
153 self.cell_renderer.cell_width(),
154 self.cell_renderer.cell_height(),
155 self.cell_renderer.window_padding(),
156 );
157 log::info!(
158 "Cursor shader enabled at runtime: {}",
159 shader_path_full.display()
160 );
161 self.cursor_shader_renderer = Some(renderer);
162 self.cursor_shader_path = Some(path.to_string());
163 self.dirty = true;
164 Ok(())
165 }
166 Err(e) => {
167 let error_msg = format!(
168 "Failed to load cursor shader '{}': {}",
169 shader_path_full.display(),
170 e
171 );
172 log::error!("{}", error_msg);
173 Err(error_msg)
174 }
175 }
176 }
177 _ => {
178 if self.cursor_shader_renderer.is_some() {
179 log::info!("Cursor shader disabled at runtime");
180 }
181 self.cursor_shader_renderer = None;
182 self.cursor_shader_path = None;
183 self.dirty = true;
184 Ok(())
185 }
186 }
187 }
188
189 /// Get the current cursor shader path
190 #[allow(dead_code)]
191 pub fn cursor_shader_path(&self) -> Option<&str> {
192 self.cursor_shader_path.as_deref()
193 }
194
195 /// Reload the cursor shader from source code
196 pub fn reload_cursor_shader_from_source(&mut self, source: &str) -> Result<()> {
197 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
198 cursor_shader.reload_from_source(
199 self.cell_renderer.device(),
200 source,
201 "cursor_editor",
202 )?;
203 self.dirty = true;
204 Ok(())
205 } else {
206 Err(anyhow::anyhow!("No cursor shader renderer active"))
207 }
208 }
209
210 /// Reload the custom shader from source code
211 ///
212 /// This method compiles the new shader source and replaces the current pipeline.
213 /// If compilation fails, returns an error and the old shader remains active.
214 ///
215 /// # Arguments
216 /// * `source` - The GLSL shader source code
217 ///
218 /// # Returns
219 /// Ok(()) if successful, Err with error message if compilation fails
220 pub fn reload_shader_from_source(&mut self, source: &str) -> Result<()> {
221 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
222 custom_shader.reload_from_source(self.cell_renderer.device(), source, "editor")?;
223 self.dirty = true;
224 Ok(())
225 } else {
226 Err(anyhow::anyhow!(
227 "No custom shader is currently loaded. Enable a custom shader first."
228 ))
229 }
230 }
231
232 /// Enable/disable custom shader at runtime. When enabling, tries to
233 /// (re)load the shader from the given path; when disabling, drops the
234 /// renderer instance.
235 ///
236 /// Returns Ok(()) on success, or Err with error message on failure.
237 #[allow(clippy::too_many_arguments)]
238 pub fn set_custom_shader_enabled(
239 &mut self,
240 enabled: bool,
241 shader_path: Option<&str>,
242 window_opacity: f32,
243 text_opacity: f32,
244 animation_enabled: bool,
245 animation_speed: f32,
246 full_content: bool,
247 brightness: f32,
248 channel_paths: &[Option<std::path::PathBuf>; 4],
249 ) -> Result<(), String> {
250 match (enabled, shader_path) {
251 (true, Some(path)) => {
252 // Check if the shader path has changed
253 let path_changed = self.custom_shader_path.as_deref() != Some(path);
254
255 // If we already have a shader renderer and path hasn't changed, just update flags
256 if let Some(renderer) = &mut self.custom_shader_renderer {
257 if !path_changed {
258 renderer.set_animation_enabled(animation_enabled);
259 renderer.set_animation_speed(animation_speed);
260 renderer.set_opacity(window_opacity);
261 renderer.set_full_content_mode(full_content);
262 renderer.set_brightness(brightness);
263 return Ok(());
264 }
265 // Path changed - we need to reload, so drop the old renderer
266 log::info!("Shader path changed, reloading shader");
267 }
268
269 let shader_path_full = crate::config::Config::shader_path(path);
270 match CustomShaderRenderer::new(
271 self.cell_renderer.device(),
272 self.cell_renderer.queue(),
273 self.cell_renderer.surface_format(),
274 &shader_path_full,
275 self.size.width,
276 self.size.height,
277 animation_enabled,
278 animation_speed,
279 window_opacity,
280 text_opacity,
281 full_content,
282 channel_paths,
283 ) {
284 Ok(mut renderer) => {
285 // Sync cell dimensions for cursor position calculation
286 renderer.update_cell_dimensions(
287 self.cell_renderer.cell_width(),
288 self.cell_renderer.cell_height(),
289 self.cell_renderer.window_padding(),
290 );
291 // Apply brightness setting
292 renderer.set_brightness(brightness);
293 log::info!(
294 "Custom shader enabled at runtime: {}",
295 shader_path_full.display()
296 );
297 self.custom_shader_renderer = Some(renderer);
298 self.custom_shader_path = Some(path.to_string());
299 self.dirty = true;
300 Ok(())
301 }
302 Err(e) => {
303 let error_msg = format!(
304 "Failed to load shader '{}': {}",
305 shader_path_full.display(),
306 e
307 );
308 log::error!("{}", error_msg);
309 Err(error_msg)
310 }
311 }
312 }
313 _ => {
314 if self.custom_shader_renderer.is_some() {
315 log::info!("Custom shader disabled at runtime");
316 }
317 self.custom_shader_renderer = None;
318 self.custom_shader_path = None;
319 self.dirty = true;
320 Ok(())
321 }
322 }
323 }
324}