1use crate::cell_renderer::{Cell, CellRenderer, PaneViewport};
2use crate::custom_shader_renderer::CustomShaderRenderer;
3use crate::graphics_renderer::GraphicsRenderer;
4use anyhow::Result;
5use std::sync::Arc;
6use winit::dpi::PhysicalSize;
7use winit::window::Window;
8
9pub mod graphics;
10pub mod shaders;
11
12pub use par_term_config::SeparatorMark;
14
15pub fn compute_visible_separator_marks(
24 marks: &[par_term_config::ScrollbackMark],
25 scrollback_len: usize,
26 scroll_offset: usize,
27 visible_lines: usize,
28) -> Vec<SeparatorMark> {
29 let viewport_start = scrollback_len.saturating_sub(scroll_offset);
30 let viewport_end = viewport_start + visible_lines;
31
32 marks
33 .iter()
34 .filter_map(|mark| {
35 if mark.line >= viewport_start && mark.line < viewport_end {
36 let screen_row = mark.line - viewport_start;
37 Some((screen_row, mark.exit_code, mark.color))
38 } else {
39 None
40 }
41 })
42 .collect()
43}
44
45pub struct PaneRenderInfo<'a> {
47 pub viewport: PaneViewport,
49 pub cells: &'a [Cell],
51 pub grid_size: (usize, usize),
53 pub cursor_pos: Option<(usize, usize)>,
55 pub cursor_opacity: f32,
57 pub show_scrollbar: bool,
59 pub marks: Vec<par_term_config::ScrollbackMark>,
61 pub scrollback_len: usize,
63 pub scroll_offset: usize,
65 pub background: Option<par_term_config::PaneBackground>,
67 pub graphics: Vec<par_term_emu_core_rust::graphics::TerminalGraphic>,
69}
70
71#[derive(Clone, Copy, Debug)]
73pub struct DividerRenderInfo {
74 pub x: f32,
76 pub y: f32,
78 pub width: f32,
80 pub height: f32,
82 pub hovered: bool,
84}
85
86impl DividerRenderInfo {
87 pub fn from_rect(rect: &par_term_config::DividerRect, hovered: bool) -> Self {
89 Self {
90 x: rect.x,
91 y: rect.y,
92 width: rect.width,
93 height: rect.height,
94 hovered,
95 }
96 }
97}
98
99#[derive(Clone, Debug)]
101pub struct PaneTitleInfo {
102 pub x: f32,
104 pub y: f32,
106 pub width: f32,
108 pub height: f32,
110 pub title: String,
112 pub focused: bool,
114 pub text_color: [f32; 3],
116 pub bg_color: [f32; 3],
118}
119
120#[derive(Clone, Copy, Debug)]
122pub struct PaneDividerSettings {
123 pub divider_color: [f32; 3],
125 pub hover_color: [f32; 3],
127 pub show_focus_indicator: bool,
129 pub focus_color: [f32; 3],
131 pub focus_width: f32,
133 pub divider_style: par_term_config::DividerStyle,
135}
136
137impl Default for PaneDividerSettings {
138 fn default() -> Self {
139 Self {
140 divider_color: [0.3, 0.3, 0.3],
141 hover_color: [0.5, 0.6, 0.8],
142 show_focus_indicator: true,
143 focus_color: [0.4, 0.6, 1.0],
144 focus_width: 2.0,
145 divider_style: par_term_config::DividerStyle::default(),
146 }
147 }
148}
149
150pub struct Renderer {
152 pub(crate) cell_renderer: CellRenderer,
154
155 pub(crate) graphics_renderer: GraphicsRenderer,
157
158 pub(crate) sixel_graphics: Vec<(u64, isize, usize, usize, usize, f32, usize)>,
161
162 pub(crate) egui_renderer: egui_wgpu::Renderer,
164
165 pub(crate) custom_shader_renderer: Option<CustomShaderRenderer>,
167 pub(crate) custom_shader_path: Option<String>,
169
170 pub(crate) cursor_shader_renderer: Option<CustomShaderRenderer>,
172 pub(crate) cursor_shader_path: Option<String>,
174
175 pub(crate) size: PhysicalSize<u32>,
177
178 pub(crate) dirty: bool,
180
181 pub(crate) last_scrollbar_state: (usize, usize, usize),
183
184 pub(crate) cursor_shader_disabled_for_alt_screen: bool,
186
187 #[allow(dead_code)]
189 #[allow(dead_code)]
190 pub(crate) debug_text: Option<String>,
191}
192
193impl Renderer {
194 #[allow(clippy::too_many_arguments)]
196 pub async fn new(
197 window: Arc<Window>,
198 font_family: Option<&str>,
199 font_family_bold: Option<&str>,
200 font_family_italic: Option<&str>,
201 font_family_bold_italic: Option<&str>,
202 font_ranges: &[par_term_config::FontRange],
203 font_size: f32,
204 window_padding: f32,
205 line_spacing: f32,
206 char_spacing: f32,
207 scrollbar_position: &str,
208 scrollbar_width: f32,
209 scrollbar_thumb_color: [f32; 4],
210 scrollbar_track_color: [f32; 4],
211 enable_text_shaping: bool,
212 enable_ligatures: bool,
213 enable_kerning: bool,
214 font_antialias: bool,
215 font_hinting: bool,
216 font_thin_strokes: par_term_config::ThinStrokesMode,
217 minimum_contrast: f32,
218 vsync_mode: par_term_config::VsyncMode,
219 power_preference: par_term_config::PowerPreference,
220 window_opacity: f32,
221 background_color: [u8; 3],
222 background_image_path: Option<&str>,
223 background_image_enabled: bool,
224 background_image_mode: par_term_config::BackgroundImageMode,
225 background_image_opacity: f32,
226 custom_shader_path: Option<&str>,
227 custom_shader_enabled: bool,
228 custom_shader_animation: bool,
229 custom_shader_animation_speed: f32,
230 custom_shader_full_content: bool,
231 custom_shader_brightness: f32,
232 custom_shader_channel_paths: &[Option<std::path::PathBuf>; 4],
234 custom_shader_cubemap_path: Option<&std::path::Path>,
236 use_background_as_channel0: bool,
238 image_scaling_mode: par_term_config::ImageScalingMode,
240 image_preserve_aspect_ratio: bool,
242 cursor_shader_path: Option<&str>,
244 cursor_shader_enabled: bool,
245 cursor_shader_animation: bool,
246 cursor_shader_animation_speed: f32,
247 ) -> Result<Self> {
248 let size = window.inner_size();
249 let scale_factor = window.scale_factor();
250
251 let platform_dpi = if cfg!(target_os = "macos") {
254 72.0
255 } else {
256 96.0
257 };
258
259 let base_font_pixels = font_size * platform_dpi / 72.0;
261 let font_size_pixels = (base_font_pixels * scale_factor as f32).max(1.0);
262
263 let font_manager = par_term_fonts::font_manager::FontManager::new(
265 font_family,
266 font_family_bold,
267 font_family_italic,
268 font_family_bold_italic,
269 font_ranges,
270 )?;
271
272 let (font_ascent, font_descent, font_leading, char_advance) = {
273 let primary_font = font_manager.get_font(0).unwrap();
274 let metrics = primary_font.metrics(&[]);
275 let scale = font_size_pixels / metrics.units_per_em as f32;
276
277 let glyph_id = primary_font.charmap().map('m');
279 let advance = primary_font.glyph_metrics(&[]).advance_width(glyph_id) * scale;
280
281 (
282 metrics.ascent * scale,
283 metrics.descent * scale,
284 metrics.leading * scale,
285 advance,
286 )
287 };
288
289 let natural_line_height = font_ascent + font_descent + font_leading;
292 let char_height = (natural_line_height * line_spacing).max(1.0);
293
294 let scale = scale_factor as f32;
296 let window_padding = window_padding * scale;
297 let scrollbar_width = scrollbar_width * scale;
298
299 let available_width = (size.width as f32 - window_padding * 2.0 - scrollbar_width).max(0.0);
301 let available_height = (size.height as f32 - window_padding * 2.0).max(0.0);
302
303 let char_width = (char_advance * char_spacing).max(1.0); let cols = (available_width / char_width).max(1.0) as usize;
306 let rows = (available_height / char_height).max(1.0) as usize;
307
308 let cell_renderer = CellRenderer::new(
310 window.clone(),
311 font_family,
312 font_family_bold,
313 font_family_italic,
314 font_family_bold_italic,
315 font_ranges,
316 font_size,
317 cols,
318 rows,
319 window_padding,
320 line_spacing,
321 char_spacing,
322 scrollbar_position,
323 scrollbar_width,
324 scrollbar_thumb_color,
325 scrollbar_track_color,
326 enable_text_shaping,
327 enable_ligatures,
328 enable_kerning,
329 font_antialias,
330 font_hinting,
331 font_thin_strokes,
332 minimum_contrast,
333 vsync_mode,
334 power_preference,
335 window_opacity,
336 background_color,
337 {
338 let bg_path = if background_image_enabled {
339 background_image_path
340 } else {
341 None
342 };
343 log::info!(
344 "Renderer::new: background_image_enabled={}, path={:?}",
345 background_image_enabled,
346 bg_path
347 );
348 bg_path
349 },
350 background_image_mode,
351 background_image_opacity,
352 )
353 .await?;
354
355 let egui_renderer = egui_wgpu::Renderer::new(
357 cell_renderer.device(),
358 cell_renderer.surface_format(),
359 egui_wgpu::RendererOptions {
360 msaa_samples: 1,
361 depth_stencil_format: None,
362 dithering: false,
363 predictable_texture_filtering: false,
364 },
365 );
366
367 let graphics_renderer = GraphicsRenderer::new(
369 cell_renderer.device(),
370 cell_renderer.surface_format(),
371 cell_renderer.cell_width(),
372 cell_renderer.cell_height(),
373 cell_renderer.window_padding(),
374 image_scaling_mode,
375 image_preserve_aspect_ratio,
376 )?;
377
378 let (mut custom_shader_renderer, initial_shader_path) = shaders::init_custom_shader(
380 &cell_renderer,
381 size.width,
382 size.height,
383 window_padding,
384 custom_shader_path,
385 custom_shader_enabled,
386 custom_shader_animation,
387 custom_shader_animation_speed,
388 window_opacity,
389 custom_shader_full_content,
390 custom_shader_brightness,
391 custom_shader_channel_paths,
392 custom_shader_cubemap_path,
393 use_background_as_channel0,
394 );
395
396 let (mut cursor_shader_renderer, initial_cursor_shader_path) = shaders::init_cursor_shader(
398 &cell_renderer,
399 size.width,
400 size.height,
401 window_padding,
402 cursor_shader_path,
403 cursor_shader_enabled,
404 cursor_shader_animation,
405 cursor_shader_animation_speed,
406 window_opacity,
407 );
408
409 if let Some(ref mut cs) = custom_shader_renderer {
411 cs.set_scale_factor(scale);
412 }
413 if let Some(ref mut cs) = cursor_shader_renderer {
414 cs.set_scale_factor(scale);
415 }
416
417 log::info!(
418 "[renderer] Renderer created: custom_shader_loaded={}, cursor_shader_loaded={}",
419 initial_shader_path.is_some(),
420 initial_cursor_shader_path.is_some()
421 );
422
423 Ok(Self {
424 cell_renderer,
425 graphics_renderer,
426 sixel_graphics: Vec::new(),
427 egui_renderer,
428 custom_shader_renderer,
429 custom_shader_path: initial_shader_path,
430 cursor_shader_renderer,
431 cursor_shader_path: initial_cursor_shader_path,
432 size,
433 dirty: true, last_scrollbar_state: (usize::MAX, 0, 0), cursor_shader_disabled_for_alt_screen: false,
436 debug_text: None,
437 })
438 }
439
440 pub fn resize(&mut self, new_size: PhysicalSize<u32>) -> (usize, usize) {
442 if new_size.width > 0 && new_size.height > 0 {
443 self.size = new_size;
444 self.dirty = true; let result = self.cell_renderer.resize(new_size.width, new_size.height);
446
447 self.graphics_renderer.update_cell_dimensions(
449 self.cell_renderer.cell_width(),
450 self.cell_renderer.cell_height(),
451 self.cell_renderer.window_padding(),
452 );
453
454 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
456 custom_shader.resize(self.cell_renderer.device(), new_size.width, new_size.height);
457 custom_shader.update_cell_dimensions(
459 self.cell_renderer.cell_width(),
460 self.cell_renderer.cell_height(),
461 self.cell_renderer.window_padding(),
462 );
463 }
464
465 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
467 cursor_shader.resize(self.cell_renderer.device(), new_size.width, new_size.height);
468 cursor_shader.update_cell_dimensions(
470 self.cell_renderer.cell_width(),
471 self.cell_renderer.cell_height(),
472 self.cell_renderer.window_padding(),
473 );
474 }
475
476 return result;
477 }
478
479 self.cell_renderer.grid_size()
480 }
481
482 pub fn handle_scale_factor_change(
484 &mut self,
485 scale_factor: f64,
486 new_size: PhysicalSize<u32>,
487 ) -> (usize, usize) {
488 let old_scale = self.cell_renderer.scale_factor;
489 self.cell_renderer.update_scale_factor(scale_factor);
490 let new_scale = self.cell_renderer.scale_factor;
491
492 if old_scale > 0.0 && (old_scale - new_scale).abs() > f32::EPSILON {
494 let logical_offset_y = self.cell_renderer.content_offset_y() / old_scale;
496 let new_physical_offset_y = logical_offset_y * new_scale;
497 self.cell_renderer
498 .set_content_offset_y(new_physical_offset_y);
499 self.graphics_renderer
500 .set_content_offset_y(new_physical_offset_y);
501 if let Some(ref mut cs) = self.custom_shader_renderer {
502 cs.set_content_offset_y(new_physical_offset_y);
503 }
504 if let Some(ref mut cs) = self.cursor_shader_renderer {
505 cs.set_content_offset_y(new_physical_offset_y);
506 }
507
508 let logical_offset_x = self.cell_renderer.content_offset_x() / old_scale;
510 let new_physical_offset_x = logical_offset_x * new_scale;
511 self.cell_renderer
512 .set_content_offset_x(new_physical_offset_x);
513 self.graphics_renderer
514 .set_content_offset_x(new_physical_offset_x);
515 if let Some(ref mut cs) = self.custom_shader_renderer {
516 cs.set_content_offset_x(new_physical_offset_x);
517 }
518 if let Some(ref mut cs) = self.cursor_shader_renderer {
519 cs.set_content_offset_x(new_physical_offset_x);
520 }
521
522 let logical_inset_bottom = self.cell_renderer.content_inset_bottom() / old_scale;
524 let new_physical_inset_bottom = logical_inset_bottom * new_scale;
525 self.cell_renderer
526 .set_content_inset_bottom(new_physical_inset_bottom);
527
528 if self.cell_renderer.egui_bottom_inset > 0.0 {
530 let logical_egui_bottom = self.cell_renderer.egui_bottom_inset / old_scale;
531 self.cell_renderer.egui_bottom_inset = logical_egui_bottom * new_scale;
532 }
533
534 if self.cell_renderer.content_inset_right > 0.0 {
536 let logical_inset_right = self.cell_renderer.content_inset_right / old_scale;
537 self.cell_renderer.content_inset_right = logical_inset_right * new_scale;
538 }
539
540 if self.cell_renderer.egui_right_inset > 0.0 {
542 let logical_egui_right = self.cell_renderer.egui_right_inset / old_scale;
543 self.cell_renderer.egui_right_inset = logical_egui_right * new_scale;
544 }
545
546 let logical_padding = self.cell_renderer.window_padding() / old_scale;
548 let new_physical_padding = logical_padding * new_scale;
549 self.cell_renderer
550 .update_window_padding(new_physical_padding);
551
552 if let Some(ref mut cs) = self.custom_shader_renderer {
554 cs.set_scale_factor(new_scale);
555 }
556 if let Some(ref mut cs) = self.cursor_shader_renderer {
557 cs.set_scale_factor(new_scale);
558 }
559 }
560
561 self.resize(new_size)
562 }
563
564 pub fn update_cells(&mut self, cells: &[Cell]) {
566 if self.cell_renderer.update_cells(cells) {
567 self.dirty = true;
568 }
569 }
570
571 pub fn clear_all_cells(&mut self) {
574 self.cell_renderer.clear_all_cells();
575 self.dirty = true;
576 }
577
578 pub fn update_cursor(
580 &mut self,
581 position: (usize, usize),
582 opacity: f32,
583 style: par_term_emu_core_rust::cursor::CursorStyle,
584 ) {
585 if self.cell_renderer.update_cursor(position, opacity, style) {
586 self.dirty = true;
587 }
588 }
589
590 pub fn clear_cursor(&mut self) {
592 if self.cell_renderer.clear_cursor() {
593 self.dirty = true;
594 }
595 }
596
597 pub fn update_scrollbar(
605 &mut self,
606 scroll_offset: usize,
607 visible_lines: usize,
608 total_lines: usize,
609 marks: &[par_term_config::ScrollbackMark],
610 ) {
611 let new_state = (scroll_offset, visible_lines, total_lines);
612 if new_state == self.last_scrollbar_state {
613 return;
614 }
615 self.last_scrollbar_state = new_state;
616 self.cell_renderer
617 .update_scrollbar(scroll_offset, visible_lines, total_lines, marks);
618 self.dirty = true;
619 }
620
621 pub fn set_visual_bell_intensity(&mut self, intensity: f32) {
626 self.cell_renderer.set_visual_bell_intensity(intensity);
627 if intensity > 0.0 {
628 self.dirty = true; }
630 }
631
632 pub fn update_opacity(&mut self, opacity: f32) {
634 self.cell_renderer.update_opacity(opacity);
635
636 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
638 custom_shader.set_opacity(opacity);
639 }
640
641 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
643 cursor_shader.set_opacity(opacity);
644 }
645
646 self.dirty = true;
647 }
648
649 pub fn update_cursor_color(&mut self, color: [u8; 3]) {
651 self.cell_renderer.update_cursor_color(color);
652 self.dirty = true;
653 }
654
655 pub fn update_cursor_text_color(&mut self, color: Option<[u8; 3]>) {
657 self.cell_renderer.update_cursor_text_color(color);
658 self.dirty = true;
659 }
660
661 pub fn set_cursor_hidden_for_shader(&mut self, hidden: bool) {
663 if self.cell_renderer.set_cursor_hidden_for_shader(hidden) {
664 self.dirty = true;
665 }
666 }
667
668 pub fn set_focused(&mut self, focused: bool) {
670 if self.cell_renderer.set_focused(focused) {
671 self.dirty = true;
672 }
673 }
674
675 pub fn update_cursor_guide(&mut self, enabled: bool, color: [u8; 4]) {
677 self.cell_renderer.update_cursor_guide(enabled, color);
678 self.dirty = true;
679 }
680
681 pub fn update_cursor_shadow(
684 &mut self,
685 enabled: bool,
686 color: [u8; 4],
687 offset: [f32; 2],
688 blur: f32,
689 ) {
690 let scale = self.cell_renderer.scale_factor;
691 let physical_offset = [offset[0] * scale, offset[1] * scale];
692 let physical_blur = blur * scale;
693 self.cell_renderer
694 .update_cursor_shadow(enabled, color, physical_offset, physical_blur);
695 self.dirty = true;
696 }
697
698 pub fn update_cursor_boost(&mut self, intensity: f32, color: [u8; 3]) {
700 self.cell_renderer.update_cursor_boost(intensity, color);
701 self.dirty = true;
702 }
703
704 pub fn update_unfocused_cursor_style(&mut self, style: par_term_config::UnfocusedCursorStyle) {
706 self.cell_renderer.update_unfocused_cursor_style(style);
707 self.dirty = true;
708 }
709
710 pub fn update_command_separator(
713 &mut self,
714 enabled: bool,
715 logical_thickness: f32,
716 opacity: f32,
717 exit_color: bool,
718 color: [u8; 3],
719 ) {
720 let physical_thickness = logical_thickness * self.cell_renderer.scale_factor;
721 self.cell_renderer.update_command_separator(
722 enabled,
723 physical_thickness,
724 opacity,
725 exit_color,
726 color,
727 );
728 self.dirty = true;
729 }
730
731 pub fn set_separator_marks(&mut self, marks: Vec<SeparatorMark>) {
733 if self.cell_renderer.set_separator_marks(marks) {
734 self.dirty = true;
735 }
736 }
737
738 pub fn set_gutter_indicators(&mut self, indicators: Vec<(usize, [f32; 4])>) {
740 self.cell_renderer.set_gutter_indicators(indicators);
741 self.dirty = true;
742 }
743
744 pub fn set_transparency_affects_only_default_background(&mut self, value: bool) {
747 self.cell_renderer
748 .set_transparency_affects_only_default_background(value);
749 self.dirty = true;
750 }
751
752 pub fn set_keep_text_opaque(&mut self, value: bool) {
755 self.cell_renderer.set_keep_text_opaque(value);
756
757 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
759 custom_shader.set_keep_text_opaque(value);
760 }
761
762 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
764 cursor_shader.set_keep_text_opaque(value);
765 }
766
767 self.dirty = true;
768 }
769
770 pub fn set_link_underline_style(&mut self, style: par_term_config::LinkUnderlineStyle) {
771 self.cell_renderer.set_link_underline_style(style);
772 self.dirty = true;
773 }
774
775 pub fn set_cursor_shader_disabled_for_alt_screen(&mut self, disabled: bool) {
780 if self.cursor_shader_disabled_for_alt_screen != disabled {
781 log::debug!("[cursor-shader] Alt-screen disable set to {}", disabled);
782 self.cursor_shader_disabled_for_alt_screen = disabled;
783 } else {
784 self.cursor_shader_disabled_for_alt_screen = disabled;
785 }
786 }
787
788 #[allow(dead_code)]
792 pub fn update_window_padding(&mut self, logical_padding: f32) -> Option<(usize, usize)> {
793 let physical_padding = logical_padding * self.cell_renderer.scale_factor;
794 let result = self.cell_renderer.update_window_padding(physical_padding);
795 self.graphics_renderer.update_cell_dimensions(
797 self.cell_renderer.cell_width(),
798 self.cell_renderer.cell_height(),
799 physical_padding,
800 );
801 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
803 custom_shader.update_cell_dimensions(
804 self.cell_renderer.cell_width(),
805 self.cell_renderer.cell_height(),
806 physical_padding,
807 );
808 }
809 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
811 cursor_shader.update_cell_dimensions(
812 self.cell_renderer.cell_width(),
813 self.cell_renderer.cell_height(),
814 physical_padding,
815 );
816 }
817 self.dirty = true;
818 result
819 }
820
821 #[allow(dead_code)]
823 pub fn set_background_image_enabled(
824 &mut self,
825 enabled: bool,
826 path: Option<&str>,
827 mode: par_term_config::BackgroundImageMode,
828 opacity: f32,
829 ) {
830 let path = if enabled { path } else { None };
831 self.cell_renderer.set_background_image(path, mode, opacity);
832
833 self.sync_background_texture_to_shader();
835
836 self.dirty = true;
837 }
838
839 pub fn set_background(
843 &mut self,
844 mode: par_term_config::BackgroundMode,
845 color: [u8; 3],
846 image_path: Option<&str>,
847 image_mode: par_term_config::BackgroundImageMode,
848 image_opacity: f32,
849 image_enabled: bool,
850 ) {
851 self.cell_renderer.set_background(
852 mode,
853 color,
854 image_path,
855 image_mode,
856 image_opacity,
857 image_enabled,
858 );
859
860 self.sync_background_texture_to_shader();
862
863 let is_solid_color = matches!(mode, par_term_config::BackgroundMode::Color);
865 let is_image_mode = matches!(mode, par_term_config::BackgroundMode::Image);
866 let normalized_color = [
867 color[0] as f32 / 255.0,
868 color[1] as f32 / 255.0,
869 color[2] as f32 / 255.0,
870 ];
871
872 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
874 let has_background_shader = self.custom_shader_renderer.is_some();
877
878 if has_background_shader {
879 cursor_shader.set_background_color([0.0, 0.0, 0.0], false);
881 cursor_shader.set_background_texture(self.cell_renderer.device(), None);
882 cursor_shader.update_use_background_as_channel0(self.cell_renderer.device(), false);
883 } else {
884 cursor_shader.set_background_color(normalized_color, is_solid_color);
885
886 if is_image_mode && image_enabled {
888 let bg_texture = self.cell_renderer.get_background_as_channel_texture();
889 cursor_shader.set_background_texture(self.cell_renderer.device(), bg_texture);
890 cursor_shader
891 .update_use_background_as_channel0(self.cell_renderer.device(), true);
892 } else {
893 cursor_shader.set_background_texture(self.cell_renderer.device(), None);
895 cursor_shader
896 .update_use_background_as_channel0(self.cell_renderer.device(), false);
897 }
898 }
899 }
900
901 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
906 custom_shader.set_background_color(normalized_color, false);
907 }
908
909 self.dirty = true;
910 }
911
912 pub fn update_scrollbar_appearance(
915 &mut self,
916 logical_width: f32,
917 thumb_color: [f32; 4],
918 track_color: [f32; 4],
919 ) {
920 let physical_width = logical_width * self.cell_renderer.scale_factor;
921 self.cell_renderer
922 .update_scrollbar_appearance(physical_width, thumb_color, track_color);
923 self.dirty = true;
924 }
925
926 #[allow(dead_code)]
928 pub fn update_scrollbar_position(&mut self, position: &str) {
929 self.cell_renderer.update_scrollbar_position(position);
930 self.dirty = true;
931 }
932
933 #[allow(dead_code)]
935 pub fn update_background_image_opacity(&mut self, opacity: f32) {
936 self.cell_renderer.update_background_image_opacity(opacity);
937 self.dirty = true;
938 }
939
940 pub fn load_pane_background(&mut self, path: &str) -> anyhow::Result<bool> {
943 self.cell_renderer.load_pane_background(path)
944 }
945
946 pub fn update_image_scaling_mode(&mut self, scaling_mode: par_term_config::ImageScalingMode) {
951 self.graphics_renderer
952 .update_scaling_mode(self.cell_renderer.device(), scaling_mode);
953 self.dirty = true;
954 }
955
956 pub fn update_image_preserve_aspect_ratio(&mut self, preserve: bool) {
958 self.graphics_renderer.set_preserve_aspect_ratio(preserve);
959 self.dirty = true;
960 }
961
962 pub fn needs_continuous_render(&self) -> bool {
967 let custom_needs = self
968 .custom_shader_renderer
969 .as_ref()
970 .is_some_and(|r| r.animation_enabled() || r.cursor_needs_animation());
971 let cursor_needs = self
972 .cursor_shader_renderer
973 .as_ref()
974 .is_some_and(|r| r.animation_enabled() || r.cursor_needs_animation());
975 custom_needs || cursor_needs
976 }
977
978 pub fn render(
981 &mut self,
982 egui_data: Option<(egui::FullOutput, &egui::Context)>,
983 force_egui_opaque: bool,
984 show_scrollbar: bool,
985 pane_background: Option<&par_term_config::PaneBackground>,
986 ) -> Result<bool> {
987 let force_render = self.needs_continuous_render();
989
990 if !self.dirty && !force_render {
993 if let Some((egui_output, egui_ctx)) = egui_data {
994 let surface_texture = self.cell_renderer.render(show_scrollbar, pane_background)?;
995 self.cell_renderer
996 .render_overlays(&surface_texture, show_scrollbar)?;
997 self.render_egui(&surface_texture, egui_output, egui_ctx, force_egui_opaque)?;
998 surface_texture.present();
999 return Ok(true);
1000 }
1001 return Ok(false);
1002 }
1003
1004 let has_custom_shader = self.custom_shader_renderer.is_some();
1006 let use_cursor_shader =
1008 self.cursor_shader_renderer.is_some() && !self.cursor_shader_disabled_for_alt_screen;
1009
1010 let t1 = std::time::Instant::now();
1012 let surface_texture = if has_custom_shader {
1013 self.cell_renderer.render_to_texture(
1022 self.custom_shader_renderer
1023 .as_ref()
1024 .unwrap()
1025 .intermediate_texture_view(),
1026 true, )?
1028 } else if use_cursor_shader {
1029 self.cell_renderer.render_to_texture(
1033 self.cursor_shader_renderer
1034 .as_ref()
1035 .unwrap()
1036 .intermediate_texture_view(),
1037 true, )?
1039 } else {
1040 self.cell_renderer.render(show_scrollbar, pane_background)?
1043 };
1044 let cell_render_time = t1.elapsed();
1045
1046 let t_custom = std::time::Instant::now();
1048 let custom_shader_time = if let Some(ref mut custom_shader) = self.custom_shader_renderer {
1049 if use_cursor_shader {
1050 custom_shader.render(
1053 self.cell_renderer.device(),
1054 self.cell_renderer.queue(),
1055 self.cursor_shader_renderer
1056 .as_ref()
1057 .unwrap()
1058 .intermediate_texture_view(),
1059 false, )?;
1061 } else {
1062 let surface_view = surface_texture
1065 .texture
1066 .create_view(&wgpu::TextureViewDescriptor::default());
1067 custom_shader.render(
1068 self.cell_renderer.device(),
1069 self.cell_renderer.queue(),
1070 &surface_view,
1071 true, )?;
1073 }
1074 t_custom.elapsed()
1075 } else {
1076 std::time::Duration::ZERO
1077 };
1078
1079 let t_cursor = std::time::Instant::now();
1081 let cursor_shader_time = if use_cursor_shader {
1082 log::trace!("Rendering cursor shader");
1083 let cursor_shader = self.cursor_shader_renderer.as_mut().unwrap();
1084 let surface_view = surface_texture
1085 .texture
1086 .create_view(&wgpu::TextureViewDescriptor::default());
1087
1088 cursor_shader.render(
1089 self.cell_renderer.device(),
1090 self.cell_renderer.queue(),
1091 &surface_view,
1092 true, )?;
1094 t_cursor.elapsed()
1095 } else {
1096 if self.cursor_shader_disabled_for_alt_screen {
1097 log::trace!("Skipping cursor shader - alt screen active");
1098 }
1099 std::time::Duration::ZERO
1100 };
1101
1102 let t2 = std::time::Instant::now();
1104 if !self.sixel_graphics.is_empty() {
1105 self.render_sixel_graphics(&surface_texture)?;
1106 }
1107 let sixel_render_time = t2.elapsed();
1108
1109 self.cell_renderer
1113 .render_overlays(&surface_texture, show_scrollbar)?;
1114
1115 let t3 = std::time::Instant::now();
1117 if let Some((egui_output, egui_ctx)) = egui_data {
1118 self.render_egui(&surface_texture, egui_output, egui_ctx, force_egui_opaque)?;
1119 }
1120 let egui_render_time = t3.elapsed();
1121
1122 let t4 = std::time::Instant::now();
1124 surface_texture.present();
1125 let present_time = t4.elapsed();
1126
1127 let total = cell_render_time
1129 + custom_shader_time
1130 + cursor_shader_time
1131 + sixel_render_time
1132 + egui_render_time
1133 + present_time;
1134 if present_time.as_millis() > 10 || total.as_millis() > 10 {
1135 log::info!(
1136 "[RENDER] RENDER_BREAKDOWN: CellRender={:.2}ms BgShader={:.2}ms CursorShader={:.2}ms Sixel={:.2}ms Egui={:.2}ms PRESENT={:.2}ms Total={:.2}ms",
1137 cell_render_time.as_secs_f64() * 1000.0,
1138 custom_shader_time.as_secs_f64() * 1000.0,
1139 cursor_shader_time.as_secs_f64() * 1000.0,
1140 sixel_render_time.as_secs_f64() * 1000.0,
1141 egui_render_time.as_secs_f64() * 1000.0,
1142 present_time.as_secs_f64() * 1000.0,
1143 total.as_secs_f64() * 1000.0
1144 );
1145 }
1146
1147 self.dirty = false;
1149
1150 Ok(true)
1151 }
1152
1153 #[allow(dead_code)]
1166 pub fn render_panes(
1167 &mut self,
1168 panes: &[PaneRenderInfo<'_>],
1169 egui_data: Option<(egui::FullOutput, &egui::Context)>,
1170 force_egui_opaque: bool,
1171 ) -> Result<bool> {
1172 let force_render = self.needs_continuous_render();
1174 if !self.dirty && !force_render && egui_data.is_none() {
1175 return Ok(false);
1176 }
1177
1178 let surface_texture = self.cell_renderer.surface.get_current_texture()?;
1180 let surface_view = surface_texture
1181 .texture
1182 .create_view(&wgpu::TextureViewDescriptor::default());
1183
1184 {
1186 let mut encoder = self.cell_renderer.device().create_command_encoder(
1187 &wgpu::CommandEncoderDescriptor {
1188 label: Some("pane clear encoder"),
1189 },
1190 );
1191
1192 let opacity = self.cell_renderer.window_opacity as f64;
1193 let clear_color = if self.cell_renderer.bg_is_solid_color {
1194 wgpu::Color {
1195 r: self.cell_renderer.solid_bg_color[0] as f64 * opacity,
1196 g: self.cell_renderer.solid_bg_color[1] as f64 * opacity,
1197 b: self.cell_renderer.solid_bg_color[2] as f64 * opacity,
1198 a: opacity,
1199 }
1200 } else {
1201 wgpu::Color {
1202 r: self.cell_renderer.background_color[0] as f64 * opacity,
1203 g: self.cell_renderer.background_color[1] as f64 * opacity,
1204 b: self.cell_renderer.background_color[2] as f64 * opacity,
1205 a: opacity,
1206 }
1207 };
1208
1209 {
1210 let _clear_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1211 label: Some("surface clear pass"),
1212 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1213 view: &surface_view,
1214 resolve_target: None,
1215 ops: wgpu::Operations {
1216 load: wgpu::LoadOp::Clear(clear_color),
1217 store: wgpu::StoreOp::Store,
1218 },
1219 depth_slice: None,
1220 })],
1221 depth_stencil_attachment: None,
1222 timestamp_writes: None,
1223 occlusion_query_set: None,
1224 });
1225 }
1226
1227 self.cell_renderer
1228 .queue()
1229 .submit(std::iter::once(encoder.finish()));
1230 }
1231
1232 let has_background_image = self
1234 .cell_renderer
1235 .render_background_only(&surface_view, false)?;
1236
1237 for pane in panes {
1239 let separator_marks = compute_visible_separator_marks(
1240 &pane.marks,
1241 pane.scrollback_len,
1242 pane.scroll_offset,
1243 pane.grid_size.1,
1244 );
1245 self.cell_renderer.render_pane_to_view(
1246 &surface_view,
1247 &pane.viewport,
1248 pane.cells,
1249 pane.grid_size.0,
1250 pane.grid_size.1,
1251 pane.cursor_pos,
1252 pane.cursor_opacity,
1253 pane.show_scrollbar,
1254 false, has_background_image, &separator_marks,
1257 pane.background.as_ref(),
1258 )?;
1259 }
1260
1261 if let Some((egui_output, egui_ctx)) = egui_data {
1263 self.render_egui(&surface_texture, egui_output, egui_ctx, force_egui_opaque)?;
1264 }
1265
1266 surface_texture.present();
1268
1269 self.dirty = false;
1270 Ok(true)
1271 }
1272
1273 #[allow(dead_code, clippy::too_many_arguments)]
1295 pub fn render_split_panes(
1296 &mut self,
1297 panes: &[PaneRenderInfo<'_>],
1298 dividers: &[DividerRenderInfo],
1299 pane_titles: &[PaneTitleInfo],
1300 focused_viewport: Option<&PaneViewport>,
1301 divider_settings: &PaneDividerSettings,
1302 egui_data: Option<(egui::FullOutput, &egui::Context)>,
1303 force_egui_opaque: bool,
1304 ) -> Result<bool> {
1305 let force_render = self.needs_continuous_render();
1307 if !self.dirty && !force_render && egui_data.is_none() {
1308 return Ok(false);
1309 }
1310
1311 let has_custom_shader = self.custom_shader_renderer.is_some();
1312
1313 for pane in panes.iter() {
1315 if let Some(ref bg) = pane.background
1316 && let Some(ref path) = bg.image_path
1317 && let Err(e) = self.cell_renderer.load_pane_background(path)
1318 {
1319 log::error!("Failed to load pane background '{}': {}", path, e);
1320 }
1321 }
1322
1323 let surface_texture = self.cell_renderer.surface.get_current_texture()?;
1325 let surface_view = surface_texture
1326 .texture
1327 .create_view(&wgpu::TextureViewDescriptor::default());
1328
1329 let opacity = self.cell_renderer.window_opacity as f64;
1331 let clear_color = if self.cell_renderer.bg_is_solid_color {
1332 wgpu::Color {
1333 r: self.cell_renderer.solid_bg_color[0] as f64 * opacity,
1334 g: self.cell_renderer.solid_bg_color[1] as f64 * opacity,
1335 b: self.cell_renderer.solid_bg_color[2] as f64 * opacity,
1336 a: opacity,
1337 }
1338 } else {
1339 wgpu::Color {
1340 r: self.cell_renderer.background_color[0] as f64 * opacity,
1341 g: self.cell_renderer.background_color[1] as f64 * opacity,
1342 b: self.cell_renderer.background_color[2] as f64 * opacity,
1343 a: opacity,
1344 }
1345 };
1346
1347 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
1350 custom_shader.clear_intermediate_texture(
1353 self.cell_renderer.device(),
1354 self.cell_renderer.queue(),
1355 );
1356
1357 custom_shader.render_with_clear_color(
1360 self.cell_renderer.device(),
1361 self.cell_renderer.queue(),
1362 &surface_view,
1363 false, clear_color,
1365 )?;
1366 } else {
1367 let mut encoder = self.cell_renderer.device().create_command_encoder(
1369 &wgpu::CommandEncoderDescriptor {
1370 label: Some("split pane clear encoder"),
1371 },
1372 );
1373
1374 {
1375 let _clear_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1376 label: Some("surface clear pass"),
1377 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1378 view: &surface_view,
1379 resolve_target: None,
1380 ops: wgpu::Operations {
1381 load: wgpu::LoadOp::Clear(clear_color),
1382 store: wgpu::StoreOp::Store,
1383 },
1384 depth_slice: None,
1385 })],
1386 depth_stencil_attachment: None,
1387 timestamp_writes: None,
1388 occlusion_query_set: None,
1389 });
1390 }
1391
1392 self.cell_renderer
1393 .queue()
1394 .submit(std::iter::once(encoder.finish()));
1395 }
1396
1397 let any_pane_has_background = panes.iter().any(|p| p.background.is_some());
1402 let has_background_image = if !has_custom_shader && !any_pane_has_background {
1403 self.cell_renderer
1404 .render_background_only(&surface_view, false)?
1405 } else {
1406 false
1407 };
1408
1409 for pane in panes.iter() {
1415 if pane.viewport.focused && pane.show_scrollbar {
1416 let total_lines = pane.scrollback_len + pane.grid_size.1;
1417 let new_state = (pane.scroll_offset, pane.grid_size.1, total_lines);
1418 if new_state != self.last_scrollbar_state {
1419 self.last_scrollbar_state = new_state;
1420 self.cell_renderer.update_scrollbar_for_pane(
1421 pane.scroll_offset,
1422 pane.grid_size.1,
1423 total_lines,
1424 &pane.marks,
1425 &pane.viewport,
1426 );
1427 }
1428 break;
1429 }
1430 }
1431
1432 for pane in panes {
1434 let separator_marks = compute_visible_separator_marks(
1435 &pane.marks,
1436 pane.scrollback_len,
1437 pane.scroll_offset,
1438 pane.grid_size.1,
1439 );
1440 self.cell_renderer.render_pane_to_view(
1441 &surface_view,
1442 &pane.viewport,
1443 pane.cells,
1444 pane.grid_size.0,
1445 pane.grid_size.1,
1446 pane.cursor_pos,
1447 pane.cursor_opacity,
1448 pane.show_scrollbar,
1449 false, has_background_image || has_custom_shader, &separator_marks,
1452 pane.background.as_ref(),
1453 )?;
1454 }
1455
1456 for pane in panes {
1458 if !pane.graphics.is_empty() {
1459 self.render_pane_sixel_graphics(
1460 &surface_view,
1461 &pane.viewport,
1462 &pane.graphics,
1463 pane.scroll_offset,
1464 pane.scrollback_len,
1465 pane.grid_size.1,
1466 )?;
1467 }
1468 }
1469
1470 if !dividers.is_empty() {
1472 self.render_dividers(&surface_view, dividers, divider_settings)?;
1473 }
1474
1475 if !pane_titles.is_empty() {
1477 self.render_pane_titles(&surface_view, pane_titles)?;
1478 }
1479
1480 if panes.len() > 1
1482 && let Some(viewport) = focused_viewport
1483 {
1484 self.render_focus_indicator(&surface_view, viewport, divider_settings)?;
1485 }
1486
1487 if let Some((egui_output, egui_ctx)) = egui_data {
1489 self.render_egui(&surface_texture, egui_output, egui_ctx, force_egui_opaque)?;
1490 }
1491
1492 surface_texture.present();
1494
1495 self.dirty = false;
1496 Ok(true)
1497 }
1498
1499 #[allow(dead_code)]
1508 pub fn render_dividers(
1509 &mut self,
1510 surface_view: &wgpu::TextureView,
1511 dividers: &[DividerRenderInfo],
1512 settings: &PaneDividerSettings,
1513 ) -> Result<()> {
1514 if dividers.is_empty() {
1515 return Ok(());
1516 }
1517
1518 let mut instances = Vec::with_capacity(dividers.len() * 3); let w = self.size.width as f32;
1523 let h = self.size.height as f32;
1524
1525 for divider in dividers {
1526 let color = if divider.hovered {
1527 settings.hover_color
1528 } else {
1529 settings.divider_color
1530 };
1531
1532 use par_term_config::DividerStyle;
1533 match settings.divider_style {
1534 DividerStyle::Solid => {
1535 let x_ndc = divider.x / w * 2.0 - 1.0;
1536 let y_ndc = 1.0 - (divider.y / h * 2.0);
1537 let w_ndc = divider.width / w * 2.0;
1538 let h_ndc = divider.height / h * 2.0;
1539
1540 instances.push(crate::cell_renderer::types::BackgroundInstance {
1541 position: [x_ndc, y_ndc],
1542 size: [w_ndc, h_ndc],
1543 color: [color[0], color[1], color[2], 1.0],
1544 });
1545 }
1546 DividerStyle::Double => {
1547 let is_horizontal = divider.width > divider.height;
1549 let thickness = if is_horizontal {
1550 divider.height
1551 } else {
1552 divider.width
1553 };
1554
1555 if thickness >= 4.0 {
1556 if is_horizontal {
1558 instances.push(crate::cell_renderer::types::BackgroundInstance {
1560 position: [divider.x / w * 2.0 - 1.0, 1.0 - (divider.y / h * 2.0)],
1561 size: [divider.width / w * 2.0, 1.0 / h * 2.0],
1562 color: [color[0], color[1], color[2], 1.0],
1563 });
1564 let bottom_y = divider.y + divider.height - 1.0;
1566 instances.push(crate::cell_renderer::types::BackgroundInstance {
1567 position: [divider.x / w * 2.0 - 1.0, 1.0 - (bottom_y / h * 2.0)],
1568 size: [divider.width / w * 2.0, 1.0 / h * 2.0],
1569 color: [color[0], color[1], color[2], 1.0],
1570 });
1571 } else {
1572 instances.push(crate::cell_renderer::types::BackgroundInstance {
1574 position: [divider.x / w * 2.0 - 1.0, 1.0 - (divider.y / h * 2.0)],
1575 size: [1.0 / w * 2.0, divider.height / h * 2.0],
1576 color: [color[0], color[1], color[2], 1.0],
1577 });
1578 let right_x = divider.x + divider.width - 1.0;
1580 instances.push(crate::cell_renderer::types::BackgroundInstance {
1581 position: [right_x / w * 2.0 - 1.0, 1.0 - (divider.y / h * 2.0)],
1582 size: [1.0 / w * 2.0, divider.height / h * 2.0],
1583 color: [color[0], color[1], color[2], 1.0],
1584 });
1585 }
1586 } else {
1587 if is_horizontal {
1590 let center_y = divider.y + (divider.height - 1.0) / 2.0;
1591 instances.push(crate::cell_renderer::types::BackgroundInstance {
1592 position: [divider.x / w * 2.0 - 1.0, 1.0 - (center_y / h * 2.0)],
1593 size: [divider.width / w * 2.0, 1.0 / h * 2.0],
1594 color: [color[0], color[1], color[2], 1.0],
1595 });
1596 } else {
1597 let center_x = divider.x + (divider.width - 1.0) / 2.0;
1598 instances.push(crate::cell_renderer::types::BackgroundInstance {
1599 position: [center_x / w * 2.0 - 1.0, 1.0 - (divider.y / h * 2.0)],
1600 size: [1.0 / w * 2.0, divider.height / h * 2.0],
1601 color: [color[0], color[1], color[2], 1.0],
1602 });
1603 }
1604 }
1605 }
1606 DividerStyle::Dashed => {
1607 let is_horizontal = divider.width > divider.height;
1609 let dash_len: f32 = 6.0;
1610 let gap_len: f32 = 4.0;
1611
1612 if is_horizontal {
1613 let mut x = divider.x;
1614 while x < divider.x + divider.width {
1615 let seg_w = dash_len.min(divider.x + divider.width - x);
1616 instances.push(crate::cell_renderer::types::BackgroundInstance {
1617 position: [x / w * 2.0 - 1.0, 1.0 - (divider.y / h * 2.0)],
1618 size: [seg_w / w * 2.0, divider.height / h * 2.0],
1619 color: [color[0], color[1], color[2], 1.0],
1620 });
1621 x += dash_len + gap_len;
1622 }
1623 } else {
1624 let mut y = divider.y;
1625 while y < divider.y + divider.height {
1626 let seg_h = dash_len.min(divider.y + divider.height - y);
1627 instances.push(crate::cell_renderer::types::BackgroundInstance {
1628 position: [divider.x / w * 2.0 - 1.0, 1.0 - (y / h * 2.0)],
1629 size: [divider.width / w * 2.0, seg_h / h * 2.0],
1630 color: [color[0], color[1], color[2], 1.0],
1631 });
1632 y += dash_len + gap_len;
1633 }
1634 }
1635 }
1636 DividerStyle::Shadow => {
1637 let is_horizontal = divider.width > divider.height;
1640 let thickness = if is_horizontal {
1641 divider.height
1642 } else {
1643 divider.width
1644 };
1645
1646 let highlight = [
1648 (color[0] + 0.3).min(1.0),
1649 (color[1] + 0.3).min(1.0),
1650 (color[2] + 0.3).min(1.0),
1651 1.0,
1652 ];
1653 let shadow = [(color[0] * 0.3), (color[1] * 0.3), (color[2] * 0.3), 1.0];
1655
1656 if thickness >= 3.0 {
1657 let edge = 1.0_f32;
1659 if is_horizontal {
1660 instances.push(crate::cell_renderer::types::BackgroundInstance {
1662 position: [divider.x / w * 2.0 - 1.0, 1.0 - (divider.y / h * 2.0)],
1663 size: [divider.width / w * 2.0, edge / h * 2.0],
1664 color: highlight,
1665 });
1666 let body_y = divider.y + edge;
1668 let body_h = divider.height - edge * 2.0;
1669 if body_h > 0.0 {
1670 instances.push(crate::cell_renderer::types::BackgroundInstance {
1671 position: [divider.x / w * 2.0 - 1.0, 1.0 - (body_y / h * 2.0)],
1672 size: [divider.width / w * 2.0, body_h / h * 2.0],
1673 color: [color[0], color[1], color[2], 1.0],
1674 });
1675 }
1676 let shadow_y = divider.y + divider.height - edge;
1678 instances.push(crate::cell_renderer::types::BackgroundInstance {
1679 position: [divider.x / w * 2.0 - 1.0, 1.0 - (shadow_y / h * 2.0)],
1680 size: [divider.width / w * 2.0, edge / h * 2.0],
1681 color: shadow,
1682 });
1683 } else {
1684 instances.push(crate::cell_renderer::types::BackgroundInstance {
1686 position: [divider.x / w * 2.0 - 1.0, 1.0 - (divider.y / h * 2.0)],
1687 size: [edge / w * 2.0, divider.height / h * 2.0],
1688 color: highlight,
1689 });
1690 let body_x = divider.x + edge;
1692 let body_w = divider.width - edge * 2.0;
1693 if body_w > 0.0 {
1694 instances.push(crate::cell_renderer::types::BackgroundInstance {
1695 position: [body_x / w * 2.0 - 1.0, 1.0 - (divider.y / h * 2.0)],
1696 size: [body_w / w * 2.0, divider.height / h * 2.0],
1697 color: [color[0], color[1], color[2], 1.0],
1698 });
1699 }
1700 let shadow_x = divider.x + divider.width - edge;
1702 instances.push(crate::cell_renderer::types::BackgroundInstance {
1703 position: [shadow_x / w * 2.0 - 1.0, 1.0 - (divider.y / h * 2.0)],
1704 size: [edge / w * 2.0, divider.height / h * 2.0],
1705 color: shadow,
1706 });
1707 }
1708 } else {
1709 if is_horizontal {
1711 let half = (divider.height / 2.0).max(1.0);
1712 instances.push(crate::cell_renderer::types::BackgroundInstance {
1713 position: [divider.x / w * 2.0 - 1.0, 1.0 - (divider.y / h * 2.0)],
1714 size: [divider.width / w * 2.0, half / h * 2.0],
1715 color: highlight,
1716 });
1717 let bottom_y = divider.y + half;
1718 let bottom_h = divider.height - half;
1719 if bottom_h > 0.0 {
1720 instances.push(crate::cell_renderer::types::BackgroundInstance {
1721 position: [
1722 divider.x / w * 2.0 - 1.0,
1723 1.0 - (bottom_y / h * 2.0),
1724 ],
1725 size: [divider.width / w * 2.0, bottom_h / h * 2.0],
1726 color: shadow,
1727 });
1728 }
1729 } else {
1730 let half = (divider.width / 2.0).max(1.0);
1731 instances.push(crate::cell_renderer::types::BackgroundInstance {
1732 position: [divider.x / w * 2.0 - 1.0, 1.0 - (divider.y / h * 2.0)],
1733 size: [half / w * 2.0, divider.height / h * 2.0],
1734 color: highlight,
1735 });
1736 let right_x = divider.x + half;
1737 let right_w = divider.width - half;
1738 if right_w > 0.0 {
1739 instances.push(crate::cell_renderer::types::BackgroundInstance {
1740 position: [
1741 right_x / w * 2.0 - 1.0,
1742 1.0 - (divider.y / h * 2.0),
1743 ],
1744 size: [right_w / w * 2.0, divider.height / h * 2.0],
1745 color: shadow,
1746 });
1747 }
1748 }
1749 }
1750 }
1751 }
1752 }
1753
1754 self.cell_renderer.queue().write_buffer(
1756 &self.cell_renderer.bg_instance_buffer,
1757 0,
1758 bytemuck::cast_slice(&instances),
1759 );
1760
1761 let mut encoder =
1763 self.cell_renderer
1764 .device()
1765 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1766 label: Some("divider render encoder"),
1767 });
1768
1769 {
1770 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1771 label: Some("divider render pass"),
1772 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1773 view: surface_view,
1774 resolve_target: None,
1775 ops: wgpu::Operations {
1776 load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store,
1778 },
1779 depth_slice: None,
1780 })],
1781 depth_stencil_attachment: None,
1782 timestamp_writes: None,
1783 occlusion_query_set: None,
1784 });
1785
1786 render_pass.set_pipeline(&self.cell_renderer.bg_pipeline);
1787 render_pass.set_vertex_buffer(0, self.cell_renderer.vertex_buffer.slice(..));
1788 render_pass.set_vertex_buffer(1, self.cell_renderer.bg_instance_buffer.slice(..));
1789 render_pass.draw(0..4, 0..instances.len() as u32);
1790 }
1791
1792 self.cell_renderer
1793 .queue()
1794 .submit(std::iter::once(encoder.finish()));
1795 Ok(())
1796 }
1797
1798 #[allow(dead_code)]
1807 pub fn render_focus_indicator(
1808 &mut self,
1809 surface_view: &wgpu::TextureView,
1810 viewport: &PaneViewport,
1811 settings: &PaneDividerSettings,
1812 ) -> Result<()> {
1813 if !settings.show_focus_indicator {
1814 return Ok(());
1815 }
1816
1817 let border_w = settings.focus_width;
1818 let color = [
1819 settings.focus_color[0],
1820 settings.focus_color[1],
1821 settings.focus_color[2],
1822 1.0,
1823 ];
1824
1825 let instances = vec![
1827 crate::cell_renderer::types::BackgroundInstance {
1829 position: [
1830 viewport.x / self.size.width as f32 * 2.0 - 1.0,
1831 1.0 - (viewport.y / self.size.height as f32 * 2.0),
1832 ],
1833 size: [
1834 viewport.width / self.size.width as f32 * 2.0,
1835 border_w / self.size.height as f32 * 2.0,
1836 ],
1837 color,
1838 },
1839 crate::cell_renderer::types::BackgroundInstance {
1841 position: [
1842 viewport.x / self.size.width as f32 * 2.0 - 1.0,
1843 1.0 - ((viewport.y + viewport.height - border_w) / self.size.height as f32
1844 * 2.0),
1845 ],
1846 size: [
1847 viewport.width / self.size.width as f32 * 2.0,
1848 border_w / self.size.height as f32 * 2.0,
1849 ],
1850 color,
1851 },
1852 crate::cell_renderer::types::BackgroundInstance {
1854 position: [
1855 viewport.x / self.size.width as f32 * 2.0 - 1.0,
1856 1.0 - ((viewport.y + border_w) / self.size.height as f32 * 2.0),
1857 ],
1858 size: [
1859 border_w / self.size.width as f32 * 2.0,
1860 (viewport.height - border_w * 2.0) / self.size.height as f32 * 2.0,
1861 ],
1862 color,
1863 },
1864 crate::cell_renderer::types::BackgroundInstance {
1866 position: [
1867 (viewport.x + viewport.width - border_w) / self.size.width as f32 * 2.0 - 1.0,
1868 1.0 - ((viewport.y + border_w) / self.size.height as f32 * 2.0),
1869 ],
1870 size: [
1871 border_w / self.size.width as f32 * 2.0,
1872 (viewport.height - border_w * 2.0) / self.size.height as f32 * 2.0,
1873 ],
1874 color,
1875 },
1876 ];
1877
1878 self.cell_renderer.queue().write_buffer(
1880 &self.cell_renderer.bg_instance_buffer,
1881 0,
1882 bytemuck::cast_slice(&instances),
1883 );
1884
1885 let mut encoder =
1887 self.cell_renderer
1888 .device()
1889 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1890 label: Some("focus indicator encoder"),
1891 });
1892
1893 {
1894 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1895 label: Some("focus indicator pass"),
1896 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1897 view: surface_view,
1898 resolve_target: None,
1899 ops: wgpu::Operations {
1900 load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store,
1902 },
1903 depth_slice: None,
1904 })],
1905 depth_stencil_attachment: None,
1906 timestamp_writes: None,
1907 occlusion_query_set: None,
1908 });
1909
1910 render_pass.set_pipeline(&self.cell_renderer.bg_pipeline);
1911 render_pass.set_vertex_buffer(0, self.cell_renderer.vertex_buffer.slice(..));
1912 render_pass.set_vertex_buffer(1, self.cell_renderer.bg_instance_buffer.slice(..));
1913 render_pass.draw(0..4, 0..instances.len() as u32);
1914 }
1915
1916 self.cell_renderer
1917 .queue()
1918 .submit(std::iter::once(encoder.finish()));
1919 Ok(())
1920 }
1921
1922 #[allow(dead_code)]
1927 pub fn render_pane_titles(
1928 &mut self,
1929 surface_view: &wgpu::TextureView,
1930 titles: &[PaneTitleInfo],
1931 ) -> Result<()> {
1932 if titles.is_empty() {
1933 return Ok(());
1934 }
1935
1936 let width = self.size.width as f32;
1937 let height = self.size.height as f32;
1938
1939 let mut bg_instances = Vec::with_capacity(titles.len());
1941 for title in titles {
1942 let x_ndc = title.x / width * 2.0 - 1.0;
1943 let y_ndc = 1.0 - (title.y / height * 2.0);
1944 let w_ndc = title.width / width * 2.0;
1945 let h_ndc = title.height / height * 2.0;
1946
1947 let brightness = if title.focused { 1.0 } else { 0.7 };
1950
1951 bg_instances.push(crate::cell_renderer::types::BackgroundInstance {
1952 position: [x_ndc, y_ndc],
1953 size: [w_ndc, h_ndc],
1954 color: [
1955 title.bg_color[0] * brightness,
1956 title.bg_color[1] * brightness,
1957 title.bg_color[2] * brightness,
1958 1.0, ],
1960 });
1961 }
1962
1963 self.cell_renderer.queue().write_buffer(
1965 &self.cell_renderer.bg_instance_buffer,
1966 0,
1967 bytemuck::cast_slice(&bg_instances),
1968 );
1969
1970 let mut encoder =
1972 self.cell_renderer
1973 .device()
1974 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1975 label: Some("pane title bg encoder"),
1976 });
1977
1978 {
1979 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1980 label: Some("pane title bg pass"),
1981 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1982 view: surface_view,
1983 resolve_target: None,
1984 ops: wgpu::Operations {
1985 load: wgpu::LoadOp::Load,
1986 store: wgpu::StoreOp::Store,
1987 },
1988 depth_slice: None,
1989 })],
1990 depth_stencil_attachment: None,
1991 timestamp_writes: None,
1992 occlusion_query_set: None,
1993 });
1994
1995 render_pass.set_pipeline(&self.cell_renderer.bg_pipeline);
1996 render_pass.set_vertex_buffer(0, self.cell_renderer.vertex_buffer.slice(..));
1997 render_pass.set_vertex_buffer(1, self.cell_renderer.bg_instance_buffer.slice(..));
1998 render_pass.draw(0..4, 0..bg_instances.len() as u32);
1999 }
2000
2001 self.cell_renderer
2002 .queue()
2003 .submit(std::iter::once(encoder.finish()));
2004
2005 let mut text_instances = Vec::new();
2007 let baseline_y = self.cell_renderer.font_ascent;
2008
2009 for title in titles {
2010 let title_text = &title.title;
2011 if title_text.is_empty() {
2012 continue;
2013 }
2014
2015 let padding_x = 8.0;
2017 let mut x_pos = title.x + padding_x;
2018 let y_base = title.y + (title.height - self.cell_renderer.cell_height) / 2.0;
2019
2020 let text_color = [
2021 title.text_color[0],
2022 title.text_color[1],
2023 title.text_color[2],
2024 if title.focused { 1.0 } else { 0.8 },
2025 ];
2026
2027 let max_chars =
2029 ((title.width - padding_x * 2.0) / self.cell_renderer.cell_width) as usize;
2030 let display_text: String = if title_text.len() > max_chars && max_chars > 3 {
2031 let truncated: String = title_text.chars().take(max_chars - 1).collect();
2032 format!("{}\u{2026}", truncated) } else {
2034 title_text.clone()
2035 };
2036
2037 for ch in display_text.chars() {
2038 if x_pos >= title.x + title.width - padding_x {
2039 break;
2040 }
2041
2042 if let Some((font_idx, glyph_id)) =
2043 self.cell_renderer.font_manager.find_glyph(ch, false, false)
2044 {
2045 let cache_key = ((font_idx as u64) << 32) | (glyph_id as u64);
2046 let force_monochrome = crate::cell_renderer::atlas::should_render_as_symbol(ch);
2048 let info = if self.cell_renderer.glyph_cache.contains_key(&cache_key) {
2049 self.cell_renderer.lru_remove(cache_key);
2050 self.cell_renderer.lru_push_front(cache_key);
2051 self.cell_renderer
2052 .glyph_cache
2053 .get(&cache_key)
2054 .unwrap()
2055 .clone()
2056 } else if let Some(raster) =
2057 self.cell_renderer
2058 .rasterize_glyph(font_idx, glyph_id, force_monochrome)
2059 {
2060 let info = self.cell_renderer.upload_glyph(cache_key, &raster);
2061 self.cell_renderer
2062 .glyph_cache
2063 .insert(cache_key, info.clone());
2064 self.cell_renderer.lru_push_front(cache_key);
2065 info
2066 } else {
2067 x_pos += self.cell_renderer.cell_width;
2068 continue;
2069 };
2070
2071 let glyph_left = x_pos + info.bearing_x;
2072 let glyph_top = y_base + (baseline_y - info.bearing_y);
2073
2074 text_instances.push(crate::cell_renderer::types::TextInstance {
2075 position: [
2076 glyph_left / width * 2.0 - 1.0,
2077 1.0 - (glyph_top / height * 2.0),
2078 ],
2079 size: [
2080 info.width as f32 / width * 2.0,
2081 info.height as f32 / height * 2.0,
2082 ],
2083 tex_offset: [info.x as f32 / 2048.0, info.y as f32 / 2048.0],
2084 tex_size: [info.width as f32 / 2048.0, info.height as f32 / 2048.0],
2085 color: text_color,
2086 is_colored: if info.is_colored { 1 } else { 0 },
2087 });
2088 }
2089
2090 x_pos += self.cell_renderer.cell_width;
2091 }
2092 }
2093
2094 if text_instances.is_empty() {
2095 return Ok(());
2096 }
2097
2098 self.cell_renderer.queue().write_buffer(
2100 &self.cell_renderer.text_instance_buffer,
2101 0,
2102 bytemuck::cast_slice(&text_instances),
2103 );
2104
2105 let mut encoder =
2107 self.cell_renderer
2108 .device()
2109 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
2110 label: Some("pane title text encoder"),
2111 });
2112
2113 {
2114 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2115 label: Some("pane title text pass"),
2116 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2117 view: surface_view,
2118 resolve_target: None,
2119 ops: wgpu::Operations {
2120 load: wgpu::LoadOp::Load,
2121 store: wgpu::StoreOp::Store,
2122 },
2123 depth_slice: None,
2124 })],
2125 depth_stencil_attachment: None,
2126 timestamp_writes: None,
2127 occlusion_query_set: None,
2128 });
2129
2130 render_pass.set_pipeline(&self.cell_renderer.text_pipeline);
2131 render_pass.set_bind_group(0, &self.cell_renderer.text_bind_group, &[]);
2132 render_pass.set_vertex_buffer(0, self.cell_renderer.vertex_buffer.slice(..));
2133 render_pass.set_vertex_buffer(1, self.cell_renderer.text_instance_buffer.slice(..));
2134 render_pass.draw(0..4, 0..text_instances.len() as u32);
2135 }
2136
2137 self.cell_renderer
2138 .queue()
2139 .submit(std::iter::once(encoder.finish()));
2140
2141 Ok(())
2142 }
2143
2144 fn render_egui(
2146 &mut self,
2147 surface_texture: &wgpu::SurfaceTexture,
2148 egui_output: egui::FullOutput,
2149 egui_ctx: &egui::Context,
2150 force_opaque: bool,
2151 ) -> Result<()> {
2152 use wgpu::TextureViewDescriptor;
2153
2154 let view = surface_texture
2156 .texture
2157 .create_view(&TextureViewDescriptor::default());
2158
2159 let mut encoder =
2161 self.cell_renderer
2162 .device()
2163 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
2164 label: Some("egui encoder"),
2165 });
2166
2167 let screen_descriptor = egui_wgpu::ScreenDescriptor {
2169 size_in_pixels: [self.size.width, self.size.height],
2170 pixels_per_point: egui_output.pixels_per_point,
2171 };
2172
2173 for (id, image_delta) in &egui_output.textures_delta.set {
2175 self.egui_renderer.update_texture(
2176 self.cell_renderer.device(),
2177 self.cell_renderer.queue(),
2178 *id,
2179 image_delta,
2180 );
2181 }
2182
2183 let mut paint_jobs = egui_ctx.tessellate(egui_output.shapes, egui_output.pixels_per_point);
2185
2186 if force_opaque {
2188 for job in paint_jobs.iter_mut() {
2189 match &mut job.primitive {
2190 egui::epaint::Primitive::Mesh(mesh) => {
2191 for v in mesh.vertices.iter_mut() {
2192 v.color[3] = 255;
2193 }
2194 }
2195 egui::epaint::Primitive::Callback(_) => {}
2196 }
2197 }
2198 }
2199
2200 self.egui_renderer.update_buffers(
2202 self.cell_renderer.device(),
2203 self.cell_renderer.queue(),
2204 &mut encoder,
2205 &paint_jobs,
2206 &screen_descriptor,
2207 );
2208
2209 {
2211 let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2212 label: Some("egui render pass"),
2213 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2214 view: &view,
2215 resolve_target: None,
2216 ops: wgpu::Operations {
2217 load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store,
2219 },
2220 depth_slice: None,
2221 })],
2222 depth_stencil_attachment: None,
2223 timestamp_writes: None,
2224 occlusion_query_set: None,
2225 });
2226
2227 let mut render_pass = render_pass.forget_lifetime();
2229
2230 self.egui_renderer
2231 .render(&mut render_pass, &paint_jobs, &screen_descriptor);
2232 } self.cell_renderer
2236 .queue()
2237 .submit(std::iter::once(encoder.finish()));
2238
2239 for id in &egui_output.textures_delta.free {
2241 self.egui_renderer.free_texture(id);
2242 }
2243
2244 Ok(())
2245 }
2246
2247 pub fn size(&self) -> PhysicalSize<u32> {
2249 self.size
2250 }
2251
2252 pub fn grid_size(&self) -> (usize, usize) {
2254 self.cell_renderer.grid_size()
2255 }
2256
2257 pub fn cell_width(&self) -> f32 {
2259 self.cell_renderer.cell_width()
2260 }
2261
2262 pub fn cell_height(&self) -> f32 {
2264 self.cell_renderer.cell_height()
2265 }
2266
2267 pub fn window_padding(&self) -> f32 {
2269 self.cell_renderer.window_padding()
2270 }
2271
2272 pub fn content_offset_y(&self) -> f32 {
2274 self.cell_renderer.content_offset_y()
2275 }
2276
2277 pub fn scale_factor(&self) -> f32 {
2279 self.cell_renderer.scale_factor
2280 }
2281
2282 pub fn set_content_offset_y(&mut self, logical_offset: f32) -> Option<(usize, usize)> {
2288 let physical_offset = logical_offset * self.cell_renderer.scale_factor;
2290 let result = self.cell_renderer.set_content_offset_y(physical_offset);
2291 self.graphics_renderer.set_content_offset_y(physical_offset);
2293 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
2295 custom_shader.set_content_offset_y(physical_offset);
2296 }
2297 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
2299 cursor_shader.set_content_offset_y(physical_offset);
2300 }
2301 if result.is_some() {
2302 self.dirty = true;
2303 }
2304 result
2305 }
2306
2307 pub fn content_offset_x(&self) -> f32 {
2309 self.cell_renderer.content_offset_x()
2310 }
2311
2312 pub fn set_content_offset_x(&mut self, logical_offset: f32) -> Option<(usize, usize)> {
2315 let physical_offset = logical_offset * self.cell_renderer.scale_factor;
2316 let result = self.cell_renderer.set_content_offset_x(physical_offset);
2317 self.graphics_renderer.set_content_offset_x(physical_offset);
2318 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
2319 custom_shader.set_content_offset_x(physical_offset);
2320 }
2321 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
2322 cursor_shader.set_content_offset_x(physical_offset);
2323 }
2324 if result.is_some() {
2325 self.dirty = true;
2326 }
2327 result
2328 }
2329
2330 pub fn content_inset_bottom(&self) -> f32 {
2332 self.cell_renderer.content_inset_bottom()
2333 }
2334
2335 pub fn content_inset_right(&self) -> f32 {
2337 self.cell_renderer.content_inset_right()
2338 }
2339
2340 pub fn set_content_inset_bottom(&mut self, logical_inset: f32) -> Option<(usize, usize)> {
2343 let physical_inset = logical_inset * self.cell_renderer.scale_factor;
2344 let result = self.cell_renderer.set_content_inset_bottom(physical_inset);
2345 if result.is_some() {
2346 self.dirty = true;
2347 self.last_scrollbar_state = (usize::MAX, 0, 0);
2350 }
2351 result
2352 }
2353
2354 pub fn set_content_inset_right(&mut self, logical_inset: f32) -> Option<(usize, usize)> {
2357 let physical_inset = logical_inset * self.cell_renderer.scale_factor;
2358 let result = self.cell_renderer.set_content_inset_right(physical_inset);
2359
2360 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
2362 custom_shader.set_content_inset_right(physical_inset);
2363 }
2364 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
2366 cursor_shader.set_content_inset_right(physical_inset);
2367 }
2368
2369 if result.is_some() {
2370 self.dirty = true;
2371 self.last_scrollbar_state = (usize::MAX, 0, 0);
2377 }
2378 result
2379 }
2380
2381 pub fn set_egui_bottom_inset(&mut self, logical_inset: f32) -> Option<(usize, usize)> {
2387 let physical_inset = logical_inset * self.cell_renderer.scale_factor;
2388 if (self.cell_renderer.egui_bottom_inset - physical_inset).abs() > f32::EPSILON {
2389 self.cell_renderer.egui_bottom_inset = physical_inset;
2390 let (w, h) = (
2391 self.cell_renderer.config.width,
2392 self.cell_renderer.config.height,
2393 );
2394 return Some(self.cell_renderer.resize(w, h));
2395 }
2396 None
2397 }
2398
2399 pub fn set_egui_right_inset(&mut self, logical_inset: f32) {
2405 let physical_inset = logical_inset * self.cell_renderer.scale_factor;
2406 self.cell_renderer.egui_right_inset = physical_inset;
2407 }
2408
2409 pub fn scrollbar_contains_point(&self, x: f32, y: f32) -> bool {
2415 self.cell_renderer.scrollbar_contains_point(x, y)
2416 }
2417
2418 pub fn scrollbar_thumb_bounds(&self) -> Option<(f32, f32)> {
2420 self.cell_renderer.scrollbar_thumb_bounds()
2421 }
2422
2423 pub fn scrollbar_track_contains_x(&self, x: f32) -> bool {
2425 self.cell_renderer.scrollbar_track_contains_x(x)
2426 }
2427
2428 pub fn scrollbar_mouse_y_to_scroll_offset(&self, mouse_y: f32) -> Option<usize> {
2436 self.cell_renderer
2437 .scrollbar_mouse_y_to_scroll_offset(mouse_y)
2438 }
2439
2440 pub fn scrollbar_mark_at_position(
2450 &self,
2451 mouse_x: f32,
2452 mouse_y: f32,
2453 tolerance: f32,
2454 ) -> Option<&par_term_config::ScrollbackMark> {
2455 self.cell_renderer
2456 .scrollbar_mark_at_position(mouse_x, mouse_y, tolerance)
2457 }
2458
2459 #[allow(dead_code)]
2461 pub fn is_dirty(&self) -> bool {
2462 self.dirty
2463 }
2464
2465 #[allow(dead_code)]
2467 pub fn mark_dirty(&mut self) {
2468 self.dirty = true;
2469 }
2470
2471 #[allow(dead_code)]
2473 #[allow(dead_code)]
2474 pub fn render_debug_overlay(&mut self, text: &str) {
2475 self.debug_text = Some(text.to_string());
2476 self.dirty = true; }
2478
2479 pub fn reconfigure_surface(&mut self) {
2482 self.cell_renderer.reconfigure_surface();
2483 self.dirty = true;
2484 }
2485
2486 pub fn is_vsync_mode_supported(&self, mode: par_term_config::VsyncMode) -> bool {
2488 self.cell_renderer.is_vsync_mode_supported(mode)
2489 }
2490
2491 pub fn update_vsync_mode(
2494 &mut self,
2495 mode: par_term_config::VsyncMode,
2496 ) -> (par_term_config::VsyncMode, bool) {
2497 let result = self.cell_renderer.update_vsync_mode(mode);
2498 if result.1 {
2499 self.dirty = true;
2500 }
2501 result
2502 }
2503
2504 #[allow(dead_code)]
2506 pub fn current_vsync_mode(&self) -> par_term_config::VsyncMode {
2507 self.cell_renderer.current_vsync_mode()
2508 }
2509
2510 pub fn clear_glyph_cache(&mut self) {
2513 self.cell_renderer.clear_glyph_cache();
2514 self.dirty = true;
2515 }
2516
2517 pub fn update_font_antialias(&mut self, enabled: bool) -> bool {
2520 let changed = self.cell_renderer.update_font_antialias(enabled);
2521 if changed {
2522 self.dirty = true;
2523 }
2524 changed
2525 }
2526
2527 pub fn update_font_hinting(&mut self, enabled: bool) -> bool {
2530 let changed = self.cell_renderer.update_font_hinting(enabled);
2531 if changed {
2532 self.dirty = true;
2533 }
2534 changed
2535 }
2536
2537 pub fn update_font_thin_strokes(&mut self, mode: par_term_config::ThinStrokesMode) -> bool {
2540 let changed = self.cell_renderer.update_font_thin_strokes(mode);
2541 if changed {
2542 self.dirty = true;
2543 }
2544 changed
2545 }
2546
2547 pub fn update_minimum_contrast(&mut self, ratio: f32) -> bool {
2550 let changed = self.cell_renderer.update_minimum_contrast(ratio);
2551 if changed {
2552 self.dirty = true;
2553 }
2554 changed
2555 }
2556
2557 pub fn pause_shader_animations(&mut self) {
2560 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
2561 custom_shader.set_animation_enabled(false);
2562 }
2563 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
2564 cursor_shader.set_animation_enabled(false);
2565 }
2566 log::info!("[SHADER] Shader animations paused");
2567 }
2568
2569 pub fn resume_shader_animations(
2572 &mut self,
2573 custom_shader_animation: bool,
2574 cursor_shader_animation: bool,
2575 ) {
2576 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
2577 custom_shader.set_animation_enabled(custom_shader_animation);
2578 }
2579 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
2580 cursor_shader.set_animation_enabled(cursor_shader_animation);
2581 }
2582 self.dirty = true;
2583 log::info!(
2584 "[SHADER] Shader animations resumed (custom: {}, cursor: {})",
2585 custom_shader_animation,
2586 cursor_shader_animation
2587 );
2588 }
2589
2590 pub fn take_screenshot(&mut self) -> Result<image::RgbaImage> {
2595 log::info!(
2596 "take_screenshot: Starting screenshot capture ({}x{})",
2597 self.size.width,
2598 self.size.height
2599 );
2600
2601 let width = self.size.width;
2602 let height = self.size.height;
2603 let format = self.cell_renderer.surface_format();
2605 log::info!("take_screenshot: Using texture format {:?}", format);
2606
2607 let screenshot_texture =
2609 self.cell_renderer
2610 .device()
2611 .create_texture(&wgpu::TextureDescriptor {
2612 label: Some("screenshot texture"),
2613 size: wgpu::Extent3d {
2614 width,
2615 height,
2616 depth_or_array_layers: 1,
2617 },
2618 mip_level_count: 1,
2619 sample_count: 1,
2620 dimension: wgpu::TextureDimension::D2,
2621 format,
2622 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
2623 view_formats: &[],
2624 });
2625
2626 let screenshot_view =
2627 screenshot_texture.create_view(&wgpu::TextureViewDescriptor::default());
2628
2629 log::info!("take_screenshot: Rendering composited frame...");
2631
2632 let has_custom_shader = self.custom_shader_renderer.is_some();
2634 let use_cursor_shader =
2635 self.cursor_shader_renderer.is_some() && !self.cursor_shader_disabled_for_alt_screen;
2636
2637 if has_custom_shader {
2638 let intermediate_view = self
2640 .custom_shader_renderer
2641 .as_ref()
2642 .unwrap()
2643 .intermediate_texture_view()
2644 .clone();
2645 self.cell_renderer
2646 .render_to_texture(&intermediate_view, true)?;
2647
2648 if use_cursor_shader {
2649 let cursor_intermediate = self
2651 .cursor_shader_renderer
2652 .as_ref()
2653 .unwrap()
2654 .intermediate_texture_view()
2655 .clone();
2656 self.custom_shader_renderer.as_mut().unwrap().render(
2657 self.cell_renderer.device(),
2658 self.cell_renderer.queue(),
2659 &cursor_intermediate,
2660 false,
2661 )?;
2662 self.cursor_shader_renderer.as_mut().unwrap().render(
2664 self.cell_renderer.device(),
2665 self.cell_renderer.queue(),
2666 &screenshot_view,
2667 true,
2668 )?;
2669 } else {
2670 self.custom_shader_renderer.as_mut().unwrap().render(
2672 self.cell_renderer.device(),
2673 self.cell_renderer.queue(),
2674 &screenshot_view,
2675 true,
2676 )?;
2677 }
2678 } else if use_cursor_shader {
2679 let cursor_intermediate = self
2681 .cursor_shader_renderer
2682 .as_ref()
2683 .unwrap()
2684 .intermediate_texture_view()
2685 .clone();
2686 self.cell_renderer
2687 .render_to_texture(&cursor_intermediate, true)?;
2688 self.cursor_shader_renderer.as_mut().unwrap().render(
2690 self.cell_renderer.device(),
2691 self.cell_renderer.queue(),
2692 &screenshot_view,
2693 true,
2694 )?;
2695 } else {
2696 self.cell_renderer.render_to_view(&screenshot_view)?;
2698 }
2699
2700 log::info!("take_screenshot: Render complete");
2701
2702 let device = self.cell_renderer.device();
2704 let queue = self.cell_renderer.queue();
2705
2706 let bytes_per_pixel = 4u32;
2708 let unpadded_bytes_per_row = width * bytes_per_pixel;
2709 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
2711 let padded_bytes_per_row = unpadded_bytes_per_row.div_ceil(align) * align;
2712 let buffer_size = (padded_bytes_per_row * height) as u64;
2713
2714 let output_buffer = device.create_buffer(&wgpu::BufferDescriptor {
2715 label: Some("screenshot buffer"),
2716 size: buffer_size,
2717 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
2718 mapped_at_creation: false,
2719 });
2720
2721 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
2723 label: Some("screenshot encoder"),
2724 });
2725
2726 encoder.copy_texture_to_buffer(
2727 wgpu::TexelCopyTextureInfo {
2728 texture: &screenshot_texture,
2729 mip_level: 0,
2730 origin: wgpu::Origin3d::ZERO,
2731 aspect: wgpu::TextureAspect::All,
2732 },
2733 wgpu::TexelCopyBufferInfo {
2734 buffer: &output_buffer,
2735 layout: wgpu::TexelCopyBufferLayout {
2736 offset: 0,
2737 bytes_per_row: Some(padded_bytes_per_row),
2738 rows_per_image: Some(height),
2739 },
2740 },
2741 wgpu::Extent3d {
2742 width,
2743 height,
2744 depth_or_array_layers: 1,
2745 },
2746 );
2747
2748 queue.submit(std::iter::once(encoder.finish()));
2749 log::info!("take_screenshot: Texture copy submitted");
2750
2751 let buffer_slice = output_buffer.slice(..);
2753 let (tx, rx) = std::sync::mpsc::channel();
2754 buffer_slice.map_async(wgpu::MapMode::Read, move |result| {
2755 let _ = tx.send(result);
2756 });
2757
2758 log::info!("take_screenshot: Waiting for GPU...");
2760 let _ = device.poll(wgpu::PollType::wait_indefinitely());
2761 log::info!("take_screenshot: GPU poll complete, waiting for buffer map...");
2762 rx.recv()
2763 .map_err(|e| anyhow::anyhow!("Failed to receive map result: {}", e))?
2764 .map_err(|e| anyhow::anyhow!("Failed to map buffer: {:?}", e))?;
2765 log::info!("take_screenshot: Buffer mapped successfully");
2766
2767 let data = buffer_slice.get_mapped_range();
2769 let mut pixels = Vec::with_capacity((width * height * 4) as usize);
2770
2771 let is_bgra = matches!(
2773 format,
2774 wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Bgra8UnormSrgb
2775 );
2776
2777 for y in 0..height {
2779 let row_start = (y * padded_bytes_per_row) as usize;
2780 let row_end = row_start + (width * bytes_per_pixel) as usize;
2781 let row = &data[row_start..row_end];
2782
2783 if is_bgra {
2784 for chunk in row.chunks(4) {
2786 pixels.push(chunk[2]); pixels.push(chunk[1]); pixels.push(chunk[0]); pixels.push(chunk[3]); }
2791 } else {
2792 pixels.extend_from_slice(row);
2794 }
2795 }
2796
2797 drop(data);
2798 output_buffer.unmap();
2799
2800 image::RgbaImage::from_raw(width, height, pixels)
2802 .ok_or_else(|| anyhow::anyhow!("Failed to create image from pixel data"))
2803 }
2804}