1use super::Renderer;
2use crate::cell_renderer::CellRenderer;
3use crate::custom_shader_renderer::CustomShaderRenderer;
4use anyhow::Result;
5
6#[allow(clippy::too_many_arguments)]
10pub(super) fn init_custom_shader(
11 cell_renderer: &CellRenderer,
12 size_width: u32,
13 size_height: u32,
14 window_padding: f32,
15 custom_shader_path: Option<&str>,
16 custom_shader_enabled: bool,
17 custom_shader_animation: bool,
18 custom_shader_animation_speed: f32,
19 window_opacity: f32,
20 custom_shader_full_content: bool,
21 custom_shader_brightness: f32,
22 custom_shader_channel_paths: &[Option<std::path::PathBuf>; 4],
23 custom_shader_cubemap_path: Option<&std::path::Path>,
24 use_background_as_channel0: bool,
25) -> (Option<CustomShaderRenderer>, Option<String>) {
26 if !custom_shader_enabled {
27 return (None, None);
28 }
29
30 let Some(shader_path) = custom_shader_path else {
31 return (None, None);
32 };
33
34 let path = par_term_config::Config::shader_path(shader_path);
35 match CustomShaderRenderer::new(
36 cell_renderer.device(),
37 cell_renderer.queue(),
38 cell_renderer.surface_format(),
39 &path,
40 size_width,
41 size_height,
42 custom_shader_animation,
43 custom_shader_animation_speed,
44 window_opacity,
45 custom_shader_full_content,
46 custom_shader_channel_paths,
47 custom_shader_cubemap_path,
48 ) {
49 Ok(mut renderer) => {
50 renderer.update_cell_dimensions(
51 cell_renderer.cell_width(),
52 cell_renderer.cell_height(),
53 window_padding,
54 );
55 renderer.set_scale_factor(cell_renderer.scale_factor);
56 renderer.set_brightness(custom_shader_brightness);
57
58 if use_background_as_channel0 {
60 let bg_texture = cell_renderer.get_background_as_channel_texture();
62 renderer.set_background_texture(cell_renderer.device(), bg_texture);
63 renderer.update_use_background_as_channel0(
64 cell_renderer.device(),
65 use_background_as_channel0,
66 );
67 }
68
69 log::info!(
70 "[SHADER] Custom shader renderer initialized from: {} (use_bg_as_ch0={})",
71 path.display(),
72 use_background_as_channel0
73 );
74 (Some(renderer), Some(shader_path.to_string()))
75 }
76 Err(e) => {
77 log::info!(
78 "[SHADER] ERROR: Failed to load custom shader '{}': {}",
79 path.display(),
80 e
81 );
82 (None, None)
83 }
84 }
85}
86
87#[allow(clippy::too_many_arguments)]
91pub(super) fn init_cursor_shader(
92 cell_renderer: &CellRenderer,
93 size_width: u32,
94 size_height: u32,
95 window_padding: f32,
96 cursor_shader_path: Option<&str>,
97 cursor_shader_enabled: bool,
98 cursor_shader_animation: bool,
99 cursor_shader_animation_speed: f32,
100 window_opacity: f32,
101) -> (Option<CustomShaderRenderer>, Option<String>) {
102 log::debug!(
103 "[cursor-shader] Init: enabled={}, path={:?}, animation={}, speed={}",
104 cursor_shader_enabled,
105 cursor_shader_path,
106 cursor_shader_animation,
107 cursor_shader_animation_speed
108 );
109
110 if !cursor_shader_enabled {
111 log::info!("[cursor-shader] Disabled by config");
112 return (None, None);
113 }
114
115 let Some(shader_path) = cursor_shader_path else {
116 log::info!("[cursor-shader] Enabled but no path provided");
117 return (None, None);
118 };
119
120 let path = par_term_config::Config::shader_path(shader_path);
121 let empty_channels: [Option<std::path::PathBuf>; 4] = [None, None, None, None];
122
123 match CustomShaderRenderer::new(
124 cell_renderer.device(),
125 cell_renderer.queue(),
126 cell_renderer.surface_format(),
127 &path,
128 size_width,
129 size_height,
130 cursor_shader_animation,
131 cursor_shader_animation_speed,
132 window_opacity,
133 true, &empty_channels,
135 None, ) {
137 Ok(mut renderer) => {
138 let cell_w = cell_renderer.cell_width();
139 let cell_h = cell_renderer.cell_height();
140 renderer.update_cell_dimensions(cell_w, cell_h, window_padding);
141 renderer.set_scale_factor(cell_renderer.scale_factor);
142 log::info!(
143 "[SHADER] Cursor shader renderer initialized from: {} (cell={}x{}, padding={})",
144 path.display(),
145 cell_w,
146 cell_h,
147 window_padding
148 );
149 (Some(renderer), Some(shader_path.to_string()))
150 }
151 Err(e) => {
152 log::info!(
153 "[SHADER] ERROR: Failed to load cursor shader '{}': {}",
154 path.display(),
155 e
156 );
157 (None, None)
158 }
159 }
160}
161
162impl Renderer {
163 #[allow(dead_code)]
165 pub fn set_custom_shader_animation(&mut self, enabled: bool) {
166 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
167 custom_shader.set_animation_enabled(enabled);
168 self.dirty = true;
169 }
170 }
171
172 pub fn set_shader_mouse_position(&mut self, x: f32, y: f32) {
178 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
179 custom_shader.set_mouse_position(x, y);
180 }
181 }
182
183 pub fn set_shader_mouse_button(&mut self, pressed: bool, x: f32, y: f32) {
190 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
191 custom_shader.set_mouse_button(pressed, x, y);
192 }
193 }
194
195 pub fn update_key_press_time(&mut self) {
200 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
201 custom_shader.update_key_press();
202 }
203 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
204 cursor_shader.update_key_press();
205 }
206 }
207
208 pub fn update_shader_cursor(
219 &mut self,
220 col: usize,
221 row: usize,
222 opacity: f32,
223 color: [f32; 4],
224 style: par_term_emu_core_rust::cursor::CursorStyle,
225 ) {
226 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
227 custom_shader.update_cursor(col, row, opacity, color, style);
228 }
229 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
231 cursor_shader.update_cursor(col, row, opacity, color, style);
232 }
233 }
234
235 pub fn update_shader_progress(
243 &mut self,
244 state: f32,
245 percent: f32,
246 is_active: f32,
247 active_count: f32,
248 ) {
249 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
250 custom_shader.update_progress(state, percent, is_active, active_count);
251 }
252 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
253 cursor_shader.update_progress(state, percent, is_active, active_count);
254 }
255 }
256
257 pub fn update_cursor_shader_config(
266 &mut self,
267 color: [u8; 3],
268 trail_duration: f32,
269 glow_radius: f32,
270 glow_intensity: f32,
271 ) {
272 let physical_glow_radius = glow_radius * self.cell_renderer.scale_factor;
273 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
275 custom_shader.update_cursor_shader_config(
276 color,
277 trail_duration,
278 physical_glow_radius,
279 glow_intensity,
280 );
281 }
282 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
283 cursor_shader.update_cursor_shader_config(
284 color,
285 trail_duration,
286 physical_glow_radius,
287 glow_intensity,
288 );
289 }
290 }
291
292 #[allow(clippy::too_many_arguments)]
304 pub fn set_cursor_shader_enabled(
305 &mut self,
306 enabled: bool,
307 path: Option<&str>,
308 window_opacity: f32,
309 animation_enabled: bool,
310 animation_speed: f32,
311 ) -> Result<(), String> {
312 log::debug!(
313 "[cursor-shader] Toggle: enabled={}, path={:?}, animation={}, speed={}, opacity={}",
314 enabled,
315 path,
316 animation_enabled,
317 animation_speed,
318 window_opacity
319 );
320 match (enabled, path) {
321 (true, Some(path)) => {
322 let path_changed = self.cursor_shader_path.as_ref().is_none_or(|p| p != path);
323
324 if let Some(renderer) = &mut self.cursor_shader_renderer
326 && !path_changed
327 {
328 renderer.set_animation_enabled(animation_enabled);
329 renderer.set_animation_speed(animation_speed);
330 renderer.set_opacity(window_opacity);
331 self.dirty = true;
332 log::info!("[cursor-shader] Already loaded; updated animation/opacities");
333 return Ok(());
334 }
335
336 let shader_path_full = par_term_config::Config::shader_path(path);
337 let empty_channels: [Option<std::path::PathBuf>; 4] = [None, None, None, None];
339 match CustomShaderRenderer::new(
340 self.cell_renderer.device(),
341 self.cell_renderer.queue(),
342 self.cell_renderer.surface_format(),
343 &shader_path_full,
344 self.size.width,
345 self.size.height,
346 animation_enabled,
347 animation_speed,
348 window_opacity,
349 true, &empty_channels,
351 None, ) {
353 Ok(mut renderer) => {
354 renderer.update_cell_dimensions(
356 self.cell_renderer.cell_width(),
357 self.cell_renderer.cell_height(),
358 self.cell_renderer.window_padding(),
359 );
360 renderer.set_scale_factor(self.cell_renderer.scale_factor);
362 renderer.set_keep_text_opaque(self.cell_renderer.keep_text_opaque());
364 let has_background_shader = self.custom_shader_renderer.is_some();
367
368 if has_background_shader {
369 renderer.set_background_color([0.0, 0.0, 0.0], false);
371 renderer.set_background_texture(self.cell_renderer.device(), None);
372 renderer.update_use_background_as_channel0(
373 self.cell_renderer.device(),
374 false,
375 );
376 } else {
377 renderer.set_background_color(
379 self.cell_renderer.solid_background_color(),
380 self.cell_renderer.is_solid_color_background(),
381 );
382 let is_image_mode = self.cell_renderer.has_background_image()
384 && !self.cell_renderer.is_solid_color_background();
385 if is_image_mode {
386 let bg_texture =
387 self.cell_renderer.get_background_as_channel_texture();
388 renderer.set_background_texture(
389 self.cell_renderer.device(),
390 bg_texture,
391 );
392 renderer.update_use_background_as_channel0(
393 self.cell_renderer.device(),
394 true,
395 );
396 }
397 }
398 log::info!(
399 "[cursor-shader] Enabled at runtime: {}",
400 shader_path_full.display()
401 );
402 self.cursor_shader_renderer = Some(renderer);
403 self.cursor_shader_path = Some(path.to_string());
404 self.dirty = true;
405 Ok(())
406 }
407 Err(e) => {
408 let error_msg = format!(
409 "Failed to load cursor shader '{}': {}",
410 shader_path_full.display(),
411 e
412 );
413 log::error!("[cursor-shader] {}", error_msg);
414 Err(error_msg)
415 }
416 }
417 }
418 _ => {
419 if self.cursor_shader_renderer.is_some() {
420 log::info!("[cursor-shader] Disabled at runtime");
421 } else {
422 log::debug!("[cursor-shader] Already disabled");
423 }
424 self.cursor_shader_renderer = None;
425 self.cursor_shader_path = None;
426 self.dirty = true;
427 Ok(())
428 }
429 }
430 }
431
432 #[allow(dead_code)]
434 pub fn cursor_shader_path(&self) -> Option<&str> {
435 self.cursor_shader_path.as_deref()
436 }
437
438 pub fn reload_cursor_shader_from_source(&mut self, source: &str) -> Result<()> {
440 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
441 cursor_shader.reload_from_source(
442 self.cell_renderer.device(),
443 source,
444 "cursor_editor",
445 )?;
446 self.dirty = true;
447 Ok(())
448 } else {
449 Err(anyhow::anyhow!("No cursor shader renderer active"))
450 }
451 }
452
453 pub fn reload_shader_from_source(&mut self, source: &str) -> Result<()> {
464 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
465 custom_shader.reload_from_source(self.cell_renderer.device(), source, "editor")?;
466 self.dirty = true;
467 Ok(())
468 } else {
469 Err(anyhow::anyhow!(
470 "No custom shader is currently loaded. Enable a custom shader first."
471 ))
472 }
473 }
474
475 #[allow(clippy::too_many_arguments)]
481 pub fn set_custom_shader_enabled(
482 &mut self,
483 enabled: bool,
484 shader_path: Option<&str>,
485 window_opacity: f32,
486 animation_enabled: bool,
487 animation_speed: f32,
488 full_content: bool,
489 brightness: f32,
490 channel_paths: &[Option<std::path::PathBuf>; 4],
491 cubemap_path: Option<&std::path::Path>,
492 ) -> Result<(), String> {
493 match (enabled, shader_path) {
494 (true, Some(path)) => {
495 let path_changed = self.custom_shader_path.as_deref() != Some(path);
497
498 if let Some(renderer) = &mut self.custom_shader_renderer
500 && !path_changed
501 {
502 renderer.set_animation_enabled(animation_enabled);
503 renderer.set_animation_speed(animation_speed);
504 renderer.set_opacity(window_opacity);
505 renderer.set_full_content_mode(full_content);
506 renderer.set_brightness(brightness);
507
508 for (i, path) in channel_paths.iter().enumerate() {
510 if let Err(e) = renderer.update_channel_texture(
511 self.cell_renderer.device(),
512 self.cell_renderer.queue(),
513 (i + 1) as u8, path.as_deref(),
515 ) {
516 log::warn!("Failed to update channel {} texture: {}", i, e);
517 }
518 }
519
520 if let Some(cubemap) = cubemap_path
522 && let Err(e) = renderer.update_cubemap(
523 self.cell_renderer.device(),
524 self.cell_renderer.queue(),
525 Some(cubemap),
526 )
527 {
528 log::warn!("Failed to update cubemap: {}", e);
529 }
530
531 return Ok(());
532 }
533
534 let shader_path_full = par_term_config::Config::shader_path(path);
535 match CustomShaderRenderer::new(
536 self.cell_renderer.device(),
537 self.cell_renderer.queue(),
538 self.cell_renderer.surface_format(),
539 &shader_path_full,
540 self.size.width,
541 self.size.height,
542 animation_enabled,
543 animation_speed,
544 window_opacity,
545 full_content,
546 channel_paths,
547 cubemap_path,
548 ) {
549 Ok(mut renderer) => {
550 renderer.update_cell_dimensions(
552 self.cell_renderer.cell_width(),
553 self.cell_renderer.cell_height(),
554 self.cell_renderer.window_padding(),
555 );
556 renderer.set_scale_factor(self.cell_renderer.scale_factor);
558 renderer.set_brightness(brightness);
560 renderer.set_keep_text_opaque(self.cell_renderer.keep_text_opaque());
562 renderer.set_background_color(
565 self.cell_renderer.solid_background_color(),
566 false,
567 );
568 log::info!(
569 "[SHADER] Custom shader enabled at runtime: {}",
570 shader_path_full.display()
571 );
572 self.custom_shader_renderer = Some(renderer);
573 self.custom_shader_path = Some(path.to_string());
574
575 self.sync_cursor_shader_background_state();
577
578 self.dirty = true;
579 Ok(())
580 }
581 Err(e) => {
582 let error_msg = format!(
583 "Failed to load shader '{}': {}",
584 shader_path_full.display(),
585 e
586 );
587 log::info!("[SHADER] ERROR: {}", error_msg);
588 Err(error_msg)
589 }
590 }
591 }
592 _ => {
593 if self.custom_shader_renderer.is_some() {
594 log::info!("[SHADER] Custom shader disabled at runtime");
595 }
596 self.custom_shader_renderer = None;
597 self.custom_shader_path = None;
598
599 self.sync_cursor_shader_background_state();
601
602 self.dirty = true;
603 Ok(())
604 }
605 }
606 }
607
608 fn sync_cursor_shader_background_state(&mut self) {
614 let Some(ref mut cursor_shader) = self.cursor_shader_renderer else {
615 return;
616 };
617
618 let has_background_shader = self.custom_shader_renderer.is_some();
619
620 if has_background_shader {
621 cursor_shader.set_background_color([0.0, 0.0, 0.0], false);
623 cursor_shader.set_background_texture(self.cell_renderer.device(), None);
624 cursor_shader.update_use_background_as_channel0(self.cell_renderer.device(), false);
625 } else {
626 cursor_shader.set_background_color(
628 self.cell_renderer.solid_background_color(),
629 self.cell_renderer.is_solid_color_background(),
630 );
631
632 let is_image_mode = self.cell_renderer.has_background_image()
633 && !self.cell_renderer.is_solid_color_background();
634 if is_image_mode {
635 let bg_texture = self.cell_renderer.get_background_as_channel_texture();
636 cursor_shader.set_background_texture(self.cell_renderer.device(), bg_texture);
637 cursor_shader.update_use_background_as_channel0(self.cell_renderer.device(), true);
638 } else {
639 cursor_shader.set_background_texture(self.cell_renderer.device(), None);
640 cursor_shader.update_use_background_as_channel0(self.cell_renderer.device(), false);
641 }
642 }
643 }
644
645 #[allow(dead_code)]
650 pub fn set_use_background_as_channel0(&mut self, use_background: bool) {
651 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
652 custom_shader
653 .update_use_background_as_channel0(self.cell_renderer.device(), use_background);
654 self.dirty = true;
655 }
656 }
657
658 pub fn sync_background_texture_to_shader(&mut self) {
664 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
665 let bg_texture = self.cell_renderer.get_background_as_channel_texture();
666 custom_shader.set_background_texture(self.cell_renderer.device(), bg_texture);
667 self.dirty = true;
668 }
669 }
670
671 #[allow(dead_code)]
680 pub fn update_background_as_channel0(&mut self, use_background: bool) {
681 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
682 let bg_texture = self.cell_renderer.get_background_as_channel_texture();
684 custom_shader.set_background_texture(self.cell_renderer.device(), bg_texture);
685
686 custom_shader
688 .update_use_background_as_channel0(self.cell_renderer.device(), use_background);
689
690 self.dirty = true;
691 }
692 }
693
694 pub fn update_background_as_channel0_with_mode(
704 &mut self,
705 use_background: bool,
706 background_mode: par_term_config::BackgroundMode,
707 color: [u8; 3],
708 ) {
709 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
710 let bg_texture = match background_mode {
712 par_term_config::BackgroundMode::Default => {
713 log::info!("update_background_as_channel0_with_mode: Default mode, no texture");
714 None
715 }
716 par_term_config::BackgroundMode::Color => {
717 log::info!(
719 "update_background_as_channel0_with_mode: Color mode, creating solid color texture RGB({},{},{})",
720 color[0],
721 color[1],
722 color[2]
723 );
724 Some(self.cell_renderer.get_solid_color_as_channel_texture(color))
725 }
726 par_term_config::BackgroundMode::Image => {
727 let tex = self.cell_renderer.get_background_as_channel_texture();
729 log::info!(
730 "update_background_as_channel0_with_mode: Image mode, texture={}",
731 if tex.is_some() { "Some" } else { "None" }
732 );
733 tex
734 }
735 };
736
737 let has_texture = bg_texture.is_some();
738 custom_shader.set_background_texture(self.cell_renderer.device(), bg_texture);
739 custom_shader
740 .update_use_background_as_channel0(self.cell_renderer.device(), use_background);
741
742 log::info!(
743 "update_background_as_channel0_with_mode: use_background={}, has_texture={}",
744 use_background,
745 has_texture
746 );
747
748 self.dirty = true;
749 }
750 }
751}