Skip to main content

par_term_render/custom_shader_renderer/
state.rs

1//! Public state mutation methods for [`CustomShaderRenderer`].
2//!
3//! Collects all setter/getter methods that update renderer state without
4//! touching the GPU pipeline. These are called from the render loop and
5//! settings UI to configure shader behavior at runtime.
6
7use std::collections::BTreeMap;
8use std::time::Instant;
9
10use super::CustomShaderRenderer;
11use super::cubemap::CubemapTexture;
12use super::textures::ChannelTexture;
13use anyhow::Result;
14use wgpu::*;
15
16impl CustomShaderRenderer {
17    // ---- Animation ----
18
19    /// Check if animation is enabled
20    pub fn animation_enabled(&self) -> bool {
21        self.animation_enabled
22    }
23
24    /// Set animation enabled state
25    pub fn set_animation_enabled(&mut self, enabled: bool) {
26        let now = Instant::now();
27        self.start_time = super::animation_start_after_enabled_update(
28            self.animation_enabled,
29            enabled,
30            self.start_time,
31            now,
32        );
33        self.animation_enabled = enabled;
34    }
35
36    /// Update animation speed multiplier
37    pub fn set_animation_speed(&mut self, speed: f32) {
38        self.animation_speed = speed.max(0.0);
39    }
40
41    // ---- Window / display ----
42
43    /// Update window opacity
44    pub fn set_opacity(&mut self, opacity: f32) {
45        self.window_opacity = opacity.clamp(0.0, 1.0);
46    }
47
48    /// Update shader brightness multiplier
49    pub fn set_brightness(&mut self, brightness: f32) {
50        self.brightness = brightness.clamp(0.05, 1.0);
51    }
52
53    /// Update automatic dimming beneath terminal text/content.
54    pub fn set_auto_dim_under_text(&mut self, enabled: bool, strength: f32) {
55        self.auto_dim_under_text = enabled;
56        self.auto_dim_strength = strength.clamp(0.0, 1.0);
57    }
58
59    /// Update full content mode
60    pub fn set_full_content_mode(&mut self, enabled: bool) {
61        self.full_content_mode = enabled;
62    }
63
64    /// Check if full content mode is enabled
65    pub fn full_content_mode(&self) -> bool {
66        self.full_content_mode
67    }
68
69    /// Set whether text should always be rendered at full opacity
70    /// When true, overrides text_opacity to 1.0
71    pub fn set_keep_text_opaque(&mut self, keep_opaque: bool) {
72        self.keep_text_opaque = keep_opaque;
73    }
74
75    // ---- Mouse ----
76
77    /// Update mouse position in pixel coordinates
78    pub fn set_mouse_position(&mut self, x: f32, y: f32) {
79        self.mouse_position = [x, y];
80    }
81
82    /// Update mouse button state and click position
83    pub fn set_mouse_button(&mut self, pressed: bool, x: f32, y: f32) {
84        self.mouse_button_down = pressed;
85        if pressed {
86            self.mouse_click_position = [x, y];
87        }
88    }
89
90    // ---- Key press ----
91
92    /// Update key press time for shader effects
93    ///
94    /// Call this when a key is pressed to enable key-press-based shader effects
95    /// like screen pulses or typing animations.
96    pub fn update_key_press(&mut self) {
97        self.key_press_time = if self.animation_enabled {
98            self.start_time.elapsed().as_secs_f32() * self.animation_speed.max(0.0)
99        } else {
100            0.0
101        };
102        log::trace!("Key pressed at shader time={:.3}", self.key_press_time);
103    }
104
105    // ---- Channel textures ----
106
107    /// Update a channel texture at runtime
108    pub fn update_channel_texture(
109        &mut self,
110        device: &Device,
111        queue: &Queue,
112        channel: u8,
113        path: Option<&std::path::Path>,
114    ) -> Result<()> {
115        if !(1..=4).contains(&channel) {
116            anyhow::bail!("Invalid channel index: {} (must be 1-4)", channel);
117        }
118
119        let index = (channel - 1) as usize;
120
121        let new_texture = match path {
122            Some(p) => ChannelTexture::from_file(device, queue, p)?,
123            None => ChannelTexture::placeholder(device, queue),
124        };
125
126        self.channel_textures[index] = new_texture;
127        // Use recreate_bind_group to properly handle use_background_as_channel0 logic
128        self.recreate_bind_group(device);
129
130        log::info!(
131            "Updated iChannel{} texture: {}",
132            channel,
133            path.map(|p| p.display().to_string())
134                .unwrap_or_else(|| "placeholder".to_string())
135        );
136
137        Ok(())
138    }
139
140    // ---- Cubemap ----
141
142    /// Update the cubemap texture at runtime
143    pub fn update_cubemap(
144        &mut self,
145        device: &Device,
146        queue: &Queue,
147        path: Option<&std::path::Path>,
148    ) -> Result<()> {
149        let new_cubemap = match path {
150            Some(p) => CubemapTexture::from_prefix(device, queue, p)?,
151            None => CubemapTexture::placeholder(device, queue),
152        };
153
154        self.cubemap = new_cubemap;
155
156        // Use recreate_bind_group to properly handle use_background_as_channel0 logic
157        self.recreate_bind_group(device);
158
159        log::info!(
160            "Updated cubemap texture: {}",
161            path.map(|p| p.display().to_string())
162                .unwrap_or_else(|| "placeholder".to_string())
163        );
164
165        Ok(())
166    }
167
168    // ---- Background as iChannel0 ----
169
170    /// Set whether to use the background image as iChannel0.
171    ///
172    /// When enabled and a background texture is set, the background image will be
173    /// used as iChannel0 instead of the configured channel0 texture file.
174    ///
175    /// Note: This only updates the flag. Use `update_use_background_as_channel0`
176    /// if you also need to recreate the bind group.
177    pub fn set_use_background_as_channel0(&mut self, use_background: bool) {
178        if self.use_background_as_channel0 != use_background {
179            self.use_background_as_channel0 = use_background;
180            log::info!("use_background_as_channel0 set to {}", use_background);
181        }
182    }
183
184    /// Check if using background image as iChannel0.
185    pub fn use_background_as_channel0(&self) -> bool {
186        self.use_background_as_channel0
187    }
188
189    /// Set the background texture to use as iChannel0 when enabled.
190    ///
191    /// Call this whenever the background image changes to update the shader's
192    /// channel0 binding. The device parameter is needed to recreate the bind group.
193    ///
194    /// When use_background_as_channel0 is enabled, the background texture takes
195    /// priority over any configured channel0 texture.
196    ///
197    /// # Arguments
198    /// * `device` - The wgpu device
199    /// * `texture` - The background texture (view, sampler, dimensions), or None to clear
200    pub fn set_background_texture(&mut self, device: &Device, texture: Option<ChannelTexture>) {
201        self.background_channel_texture = texture;
202
203        // Recreate bind group if we're using background as channel0
204        // The background texture takes priority over configured channel0 when enabled
205        if self.use_background_as_channel0 {
206            self.recreate_bind_group(device);
207        }
208    }
209
210    /// Set the solid background color for shader compositing.
211    ///
212    /// When set (alpha > 0), the shader uses this color as background instead of shader output.
213    /// This allows solid background colors to show through properly with window transparency.
214    ///
215    /// # Arguments
216    /// * `color` - RGB color values [R, G, B] (0.0-1.0, NOT premultiplied)
217    /// * `active` - Whether solid color mode is active (sets alpha to 1.0 or 0.0)
218    pub fn set_background_color(&mut self, color: [f32; 3], active: bool) {
219        self.background_color = [color[0], color[1], color[2], if active { 1.0 } else { 0.0 }];
220    }
221
222    /// Update the use_background_as_channel0 setting and recreate bind group if needed.
223    ///
224    /// Call this when the setting changes in the UI or config.
225    pub fn update_use_background_as_channel0(&mut self, device: &Device, use_background: bool) {
226        if self.use_background_as_channel0 != use_background {
227            self.use_background_as_channel0 = use_background;
228            self.recreate_bind_group(device);
229            log::info!("use_background_as_channel0 toggled to {}", use_background);
230        }
231    }
232
233    /// Update the background channel blend-mode hint exposed to shaders.
234    pub fn set_background_channel0_blend_mode(
235        &mut self,
236        mode: par_term_config::ShaderBackgroundBlendMode,
237    ) {
238        self.background_channel0_blend_mode = mode;
239    }
240
241    // ---- Progress / command / scroll / pane state ----
242
243    /// Update progress bar state for shader effects.
244    ///
245    /// # Arguments
246    /// * `state` - Progress state (0=hidden, 1=normal, 2=error, 3=indeterminate, 4=warning)
247    /// * `percent` - Progress percentage as 0.0-1.0
248    /// * `is_active` - 1.0 if any progress bar is active, 0.0 otherwise
249    /// * `active_count` - Total count of active bars (simple + named)
250    pub fn update_progress(&mut self, state: f32, percent: f32, is_active: f32, active_count: f32) {
251        self.progress_data = [state, percent, is_active, active_count];
252    }
253
254    /// Update command lifecycle state for shader effects.
255    ///
256    /// `state`: 0=unknown, 1=running, 2=success, 3=failure.
257    /// `exit_code`: last exit code, or 0 when unknown/running.
258    /// `running`: 1 when a command is currently running, otherwise 0.
259    pub fn update_command_status(&mut self, state: f32, exit_code: f32, running: f32) {
260        let state = state.clamp(0.0, 3.0);
261        let exit_code = if exit_code.is_finite() {
262            exit_code
263        } else {
264            0.0
265        };
266        let running = if running > 0.5 { 1.0 } else { 0.0 };
267        let changed = (self.command_data[0] - state).abs() > f32::EPSILON
268            || (self.command_data[1] - exit_code).abs() > f32::EPSILON
269            || (self.command_data[3] - running).abs() > f32::EPSILON;
270
271        if changed {
272            let event_time = if self.animation_enabled {
273                self.start_time.elapsed().as_secs_f32() * self.animation_speed.max(0.0)
274            } else {
275                0.0
276            };
277            self.command_data = [state, exit_code, event_time, running];
278        }
279    }
280
281    /// Update focused pane bounds in bottom-left-origin pixels.
282    pub fn update_focused_pane(&mut self, x: f32, y: f32, width: f32, height: f32) {
283        self.focused_pane = [x.max(0.0), y.max(0.0), width.max(0.0), height.max(0.0)];
284    }
285
286    /// Update scrollback context for shader effects.
287    pub fn update_scrollback(&mut self, offset: f32, visible_lines: f32, scrollback_lines: f32) {
288        let offset = offset.max(0.0);
289        let scrollback_lines = scrollback_lines.max(0.0);
290        let normalized = if scrollback_lines > 0.0 {
291            (offset / scrollback_lines).clamp(0.0, 1.0)
292        } else {
293            0.0
294        };
295        self.scroll_data = [offset, visible_lines.max(0.0), scrollback_lines, normalized];
296    }
297
298    // ---- Content insets ----
299
300    /// Set the right content inset (e.g., AI Inspector panel).
301    ///
302    /// When non-zero, the shader will render to a viewport that excludes
303    /// the right inset area, ensuring effects don't appear under the panel.
304    pub fn set_content_inset_right(&mut self, inset: f32) {
305        self.content_inset_right = inset;
306    }
307
308    // ---- Custom controls ----
309
310    /// Update custom shader uniform values keyed by control name.
311    pub fn set_custom_uniform_values(
312        &mut self,
313        values: BTreeMap<String, par_term_config::ShaderUniformValue>,
314    ) {
315        self.custom_uniform_values = values;
316    }
317}