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}