1use crate::cell_renderer::{Cell, CellRenderer, CellRendererConfig, PaneViewport};
13use crate::custom_shader_renderer::CustomShaderRenderer;
14use crate::graphics_renderer::GraphicsRenderer;
15use anyhow::Result;
16use winit::dpi::PhysicalSize;
17
18mod egui_render;
19pub mod graphics;
20pub mod params;
21
22mod render_passes;
23mod rendering;
24pub mod shaders;
25mod state;
26
27pub use par_term_config::SeparatorMark;
29pub use params::RendererParams;
30pub use rendering::SplitPanesRenderParams;
31
32pub fn compute_visible_separator_marks(
40 marks: &[par_term_config::ScrollbackMark],
41 scrollback_len: usize,
42 scroll_offset: usize,
43 visible_lines: usize,
44) -> Vec<SeparatorMark> {
45 let mut out = Vec::new();
46 fill_visible_separator_marks(
47 &mut out,
48 marks,
49 scrollback_len,
50 scroll_offset,
51 visible_lines,
52 );
53 out
54}
55
56pub(crate) fn fill_visible_separator_marks(
62 out: &mut Vec<SeparatorMark>,
63 marks: &[par_term_config::ScrollbackMark],
64 scrollback_len: usize,
65 scroll_offset: usize,
66 visible_lines: usize,
67) {
68 out.clear();
69 let viewport_start = scrollback_len.saturating_sub(scroll_offset);
70 let viewport_end = viewport_start + visible_lines;
71 for mark in marks {
72 if mark.line >= viewport_start && mark.line < viewport_end {
73 let screen_row = mark.line - viewport_start;
74 out.push((screen_row, mark.exit_code, mark.color));
75 }
76 }
77}
78
79pub struct PaneRenderInfo<'a> {
81 pub viewport: PaneViewport,
83 pub cells: &'a [Cell],
85 pub grid_size: (usize, usize),
87 pub cursor_pos: Option<(usize, usize)>,
89 pub cursor_opacity: f32,
91 pub show_scrollbar: bool,
93 pub marks: Vec<par_term_config::ScrollbackMark>,
95 pub scrollback_len: usize,
97 pub scroll_offset: usize,
99 pub background: Option<par_term_config::PaneBackground>,
101 pub graphics: Vec<par_term_emu_core_rust::graphics::TerminalGraphic>,
103 pub virtual_placements: Vec<par_term_emu_core_rust::graphics::TerminalGraphic>,
107}
108
109#[derive(Clone, Copy, Debug)]
111pub struct DividerRenderInfo {
112 pub x: f32,
114 pub y: f32,
116 pub width: f32,
118 pub height: f32,
120 pub hovered: bool,
122}
123
124impl DividerRenderInfo {
125 pub fn from_rect(rect: &par_term_config::DividerRect, hovered: bool) -> Self {
127 Self {
128 x: rect.x,
129 y: rect.y,
130 width: rect.width,
131 height: rect.height,
132 hovered,
133 }
134 }
135}
136
137#[derive(Clone, Debug)]
139pub struct PaneTitleInfo {
140 pub x: f32,
142 pub y: f32,
144 pub width: f32,
146 pub height: f32,
148 pub title: String,
150 pub focused: bool,
152 pub text_color: [f32; 3],
154 pub bg_color: [f32; 3],
156}
157
158#[derive(Clone, Copy, Debug)]
160pub struct PaneDividerSettings {
161 pub divider_color: [f32; 3],
163 pub hover_color: [f32; 3],
165 pub show_focus_indicator: bool,
167 pub focus_color: [f32; 3],
169 pub focus_width: f32,
171 pub divider_style: par_term_config::DividerStyle,
173}
174
175impl Default for PaneDividerSettings {
176 fn default() -> Self {
177 Self {
178 divider_color: [0.3, 0.3, 0.3],
179 hover_color: [0.5, 0.6, 0.8],
180 show_focus_indicator: true,
181 focus_color: [0.4, 0.6, 1.0],
182 focus_width: 1.0,
183 divider_style: par_term_config::DividerStyle::default(),
184 }
185 }
186}
187
188pub struct Renderer {
190 pub(crate) cell_renderer: CellRenderer,
192
193 pub(crate) graphics_renderer: GraphicsRenderer,
195
196 pub(crate) sixel_graphics: Vec<crate::graphics_renderer::GraphicRenderInfo>,
199
200 pub(crate) egui_renderer: egui_wgpu::Renderer,
202
203 pub(crate) custom_shader_renderer: Option<CustomShaderRenderer>,
205 pub(crate) custom_shader_path: Option<String>,
207
208 pub(crate) cursor_shader_renderer: Option<CustomShaderRenderer>,
210 pub(crate) cursor_shader_path: Option<String>,
212
213 pub(crate) size: PhysicalSize<u32>,
215
216 pub(crate) dirty: bool,
218
219 pub(crate) last_scrollbar_state: (usize, usize, usize, usize, u32, u32, u32, u32, u32, u32),
223
224 pub(crate) cursor_shader_disabled_for_alt_screen: bool,
226
227 pub(crate) debug_text: Option<String>,
229
230 pub(crate) scratch_divider_instances: Vec<crate::cell_renderer::BackgroundInstance>,
233}
234
235impl Renderer {
236 pub async fn new(params: RendererParams<'_>) -> Result<Self> {
238 let window = params.window;
239 let font_family = params.font_family;
240 let font_family_bold = params.font_family_bold;
241 let font_family_italic = params.font_family_italic;
242 let font_family_bold_italic = params.font_family_bold_italic;
243 let font_ranges = params.font_ranges;
244 let font_size = params.font_size;
245 let line_spacing = params.line_spacing;
246 let char_spacing = params.char_spacing;
247 let scrollbar_position = params.scrollbar_position;
248 let scrollbar_thumb_color = params.scrollbar_thumb_color;
249 let scrollbar_track_color = params.scrollbar_track_color;
250 let enable_text_shaping = params.enable_text_shaping;
251 let enable_ligatures = params.enable_ligatures;
252 let enable_kerning = params.enable_kerning;
253 let font_antialias = params.font_antialias;
254 let font_hinting = params.font_hinting;
255 let font_thin_strokes = params.font_thin_strokes;
256 let minimum_contrast = params.minimum_contrast;
257 let vsync_mode = params.vsync_mode;
258 let power_preference = params.power_preference;
259 let window_opacity = params.window_opacity;
260 let background_color = params.background_color;
261 let background_image_path = params.background_image_path;
262 let background_image_enabled = params.background_image_enabled;
263 let background_image_mode = params.background_image_mode;
264 let background_image_opacity = params.background_image_opacity;
265 let custom_shader_path = params.custom_shader_path;
266 let custom_shader_enabled = params.custom_shader_enabled;
267 let custom_shader_animation = params.custom_shader_animation;
268 let custom_shader_animation_speed = params.custom_shader_animation_speed;
269 let custom_shader_full_content = params.custom_shader_full_content;
270 let custom_shader_brightness = params.custom_shader_brightness;
271 let custom_shader_channel_paths = params.custom_shader_channel_paths;
272 let custom_shader_cubemap_path = params.custom_shader_cubemap_path;
273 let custom_shader_custom_uniforms = params.custom_shader_custom_uniforms;
274 let use_background_as_channel0 = params.use_background_as_channel0;
275 let background_channel0_blend_mode = params.background_channel0_blend_mode;
276 let custom_shader_auto_dim_under_text = params.custom_shader_auto_dim_under_text;
277 let custom_shader_auto_dim_strength = params.custom_shader_auto_dim_strength;
278 let image_scaling_mode = params.image_scaling_mode;
279 let image_preserve_aspect_ratio = params.image_preserve_aspect_ratio;
280 let cursor_shader_path = params.cursor_shader_path;
281 let cursor_shader_enabled = params.cursor_shader_enabled;
282 let cursor_shader_animation = params.cursor_shader_animation;
283 let cursor_shader_animation_speed = params.cursor_shader_animation_speed;
284
285 let size = window.inner_size();
286 let scale_factor = window.scale_factor();
287
288 let platform_dpi = if cfg!(target_os = "macos") {
291 72.0
292 } else {
293 96.0
294 };
295
296 let base_font_pixels = font_size * platform_dpi / 72.0;
298 let font_size_pixels = (base_font_pixels * scale_factor as f32).max(1.0);
299
300 let font_manager = par_term_fonts::font_manager::FontManager::new(
302 font_family,
303 font_family_bold,
304 font_family_italic,
305 font_family_bold_italic,
306 font_ranges,
307 )?;
308
309 let (font_ascent, font_descent, font_leading, char_advance) = {
310 let primary_font = font_manager
311 .get_font(0)
312 .expect("Primary font at index 0 must exist after FontManager initialization");
313 let metrics = primary_font.metrics(&[]);
314 let scale = font_size_pixels / metrics.units_per_em as f32;
315
316 let glyph_id = primary_font.charmap().map('m');
318 let advance = primary_font.glyph_metrics(&[]).advance_width(glyph_id) * scale;
319
320 (
321 metrics.ascent * scale,
322 metrics.descent * scale,
323 metrics.leading * scale,
324 advance,
325 )
326 };
327
328 let natural_line_height = font_ascent + font_descent + font_leading;
331 let char_height = (natural_line_height * line_spacing).max(1.0).round();
332
333 let scale = scale_factor as f32;
335 let window_padding = params.window_padding * scale;
336 let scrollbar_width = params.scrollbar_width * scale;
337
338 let available_width = (size.width as f32 - window_padding * 2.0 - scrollbar_width).max(0.0);
340 let available_height = (size.height as f32 - window_padding * 2.0).max(0.0);
341
342 let char_width = (char_advance * char_spacing).max(1.0).round(); let cols = (available_width / char_width).max(1.0) as usize;
345 let rows = (available_height / char_height).max(1.0) as usize;
346
347 let bg_path = if background_image_enabled {
349 background_image_path
350 } else {
351 None
352 };
353 log::info!(
354 "Renderer::new: background_image_enabled={}, path={:?}",
355 background_image_enabled,
356 bg_path
357 );
358 let cell_renderer = CellRenderer::new(
359 window.clone(),
360 CellRendererConfig {
361 font_family,
362 font_family_bold,
363 font_family_italic,
364 font_family_bold_italic,
365 font_ranges,
366 font_size,
367 cols,
368 rows,
369 window_padding,
370 line_spacing,
371 char_spacing,
372 scrollbar_position,
373 scrollbar_width,
374 scrollbar_thumb_color,
375 scrollbar_track_color,
376 enable_text_shaping,
377 enable_ligatures,
378 enable_kerning,
379 font_antialias,
380 font_hinting,
381 font_thin_strokes,
382 minimum_contrast,
383 vsync_mode,
384 power_preference,
385 window_opacity,
386 background_color,
387 background_image_path: bg_path,
388 background_image_mode,
389 background_image_opacity,
390 },
391 )
392 .await?;
393
394 let egui_renderer = egui_wgpu::Renderer::new(
396 cell_renderer.device(),
397 cell_renderer.surface_format(),
398 egui_wgpu::RendererOptions {
399 msaa_samples: 1,
400 depth_stencil_format: None,
401 dithering: false,
402 predictable_texture_filtering: false,
403 },
404 );
405
406 let graphics_renderer = GraphicsRenderer::new(
408 cell_renderer.device(),
409 cell_renderer.surface_format(),
410 cell_renderer.cell_width(),
411 cell_renderer.cell_height(),
412 cell_renderer.window_padding(),
413 image_scaling_mode,
414 image_preserve_aspect_ratio,
415 )?;
416
417 let (mut custom_shader_renderer, initial_shader_path) = shaders::init_custom_shader(
419 &cell_renderer,
420 shaders::CustomShaderInitParams {
421 size_width: size.width,
422 size_height: size.height,
423 window_padding,
424 path: custom_shader_path,
425 enabled: custom_shader_enabled,
426 animation: custom_shader_animation,
427 animation_speed: custom_shader_animation_speed,
428 window_opacity,
429 full_content: custom_shader_full_content,
430 brightness: custom_shader_brightness,
431 channel_paths: custom_shader_channel_paths,
432 cubemap_path: custom_shader_cubemap_path,
433 custom_uniforms: custom_shader_custom_uniforms,
434 use_background_as_channel0,
435 background_channel0_blend_mode,
436 auto_dim_under_text: custom_shader_auto_dim_under_text,
437 auto_dim_strength: custom_shader_auto_dim_strength,
438 },
439 );
440
441 let (mut cursor_shader_renderer, initial_cursor_shader_path) = shaders::init_cursor_shader(
443 &cell_renderer,
444 shaders::CursorShaderInitParams {
445 size_width: size.width,
446 size_height: size.height,
447 window_padding,
448 path: cursor_shader_path,
449 enabled: cursor_shader_enabled,
450 animation: cursor_shader_animation,
451 animation_speed: cursor_shader_animation_speed,
452 window_opacity,
453 },
454 );
455
456 if let Some(ref mut cs) = custom_shader_renderer {
458 cs.set_scale_factor(scale);
459 }
460 if let Some(ref mut cs) = cursor_shader_renderer {
461 cs.set_scale_factor(scale);
462 }
463
464 log::info!(
465 "[renderer] Renderer created: custom_shader_loaded={}, cursor_shader_loaded={}",
466 initial_shader_path.is_some(),
467 initial_cursor_shader_path.is_some()
468 );
469
470 Ok(Self {
471 cell_renderer,
472 graphics_renderer,
473 sixel_graphics: Vec::new(),
474 egui_renderer,
475 custom_shader_renderer,
476 custom_shader_path: initial_shader_path,
477 cursor_shader_renderer,
478 cursor_shader_path: initial_cursor_shader_path,
479 size,
480 dirty: true, last_scrollbar_state: (usize::MAX, 0, 0, 0, 0, 0, 0, 0, 0, 0), cursor_shader_disabled_for_alt_screen: false,
483 debug_text: None,
484 scratch_divider_instances: Vec::new(),
485 })
486 }
487
488 pub fn resize(&mut self, new_size: PhysicalSize<u32>) -> (usize, usize) {
490 if new_size.width > 0 && new_size.height > 0 {
491 self.size = new_size;
492 self.dirty = true; let result = self.cell_renderer.resize(new_size.width, new_size.height);
494
495 self.graphics_renderer.update_cell_dimensions(
497 self.cell_renderer.cell_width(),
498 self.cell_renderer.cell_height(),
499 self.cell_renderer.window_padding(),
500 );
501
502 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
504 custom_shader.resize(self.cell_renderer.device(), new_size.width, new_size.height);
505 custom_shader.update_cell_dimensions(
507 self.cell_renderer.cell_width(),
508 self.cell_renderer.cell_height(),
509 self.cell_renderer.window_padding(),
510 );
511 }
512
513 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
515 cursor_shader.resize(self.cell_renderer.device(), new_size.width, new_size.height);
516 cursor_shader.update_cell_dimensions(
518 self.cell_renderer.cell_width(),
519 self.cell_renderer.cell_height(),
520 self.cell_renderer.window_padding(),
521 );
522 }
523
524 return result;
525 }
526
527 self.cell_renderer.grid_size()
528 }
529
530 pub fn handle_scale_factor_change(
532 &mut self,
533 scale_factor: f64,
534 new_size: PhysicalSize<u32>,
535 ) -> (usize, usize) {
536 let old_scale = self.cell_renderer.scale_factor;
537 self.cell_renderer.update_scale_factor(scale_factor);
538 let new_scale = self.cell_renderer.scale_factor;
539
540 if old_scale > 0.0 && (old_scale - new_scale).abs() > f32::EPSILON {
542 let logical_offset_y = self.cell_renderer.content_offset_y() / old_scale;
544 let new_physical_offset_y = logical_offset_y * new_scale;
545 self.cell_renderer
546 .set_content_offset_y(new_physical_offset_y);
547 self.graphics_renderer
548 .set_content_offset_y(new_physical_offset_y);
549 if let Some(ref mut cs) = self.custom_shader_renderer {
550 cs.set_content_offset_y(new_physical_offset_y);
551 }
552 if let Some(ref mut cs) = self.cursor_shader_renderer {
553 cs.set_content_offset_y(new_physical_offset_y);
554 }
555
556 let logical_offset_x = self.cell_renderer.content_offset_x() / old_scale;
558 let new_physical_offset_x = logical_offset_x * new_scale;
559 self.cell_renderer
560 .set_content_offset_x(new_physical_offset_x);
561 self.graphics_renderer
562 .set_content_offset_x(new_physical_offset_x);
563 if let Some(ref mut cs) = self.custom_shader_renderer {
564 cs.set_content_offset_x(new_physical_offset_x);
565 }
566 if let Some(ref mut cs) = self.cursor_shader_renderer {
567 cs.set_content_offset_x(new_physical_offset_x);
568 }
569
570 let logical_inset_bottom = self.cell_renderer.content_inset_bottom() / old_scale;
572 let new_physical_inset_bottom = logical_inset_bottom * new_scale;
573 self.cell_renderer
574 .set_content_inset_bottom(new_physical_inset_bottom);
575
576 if self.cell_renderer.grid.egui_bottom_inset > 0.0 {
578 let logical_egui_bottom = self.cell_renderer.grid.egui_bottom_inset / old_scale;
579 self.cell_renderer.grid.egui_bottom_inset = logical_egui_bottom * new_scale;
580 }
581
582 if self.cell_renderer.grid.content_inset_right > 0.0 {
584 let logical_inset_right = self.cell_renderer.grid.content_inset_right / old_scale;
585 self.cell_renderer.grid.content_inset_right = logical_inset_right * new_scale;
586 }
587
588 if self.cell_renderer.grid.egui_right_inset > 0.0 {
590 let logical_egui_right = self.cell_renderer.grid.egui_right_inset / old_scale;
591 self.cell_renderer.grid.egui_right_inset = logical_egui_right * new_scale;
592 }
593
594 let logical_padding = self.cell_renderer.window_padding() / old_scale;
596 let new_physical_padding = logical_padding * new_scale;
597 self.cell_renderer
598 .update_window_padding(new_physical_padding);
599
600 let logical_scrollbar = self.cell_renderer.scrollbar.width() / old_scale;
602 let new_physical_scrollbar = logical_scrollbar * new_scale;
603 self.cell_renderer.scrollbar.update_appearance(
604 new_physical_scrollbar,
605 self.cell_renderer.scrollbar.thumb_color(),
606 self.cell_renderer.scrollbar.track_color(),
607 );
608
609 if let Some(ref mut cs) = self.custom_shader_renderer {
611 cs.set_scale_factor(new_scale);
612 }
613 if let Some(ref mut cs) = self.cursor_shader_renderer {
614 cs.set_scale_factor(new_scale);
615 }
616 }
617
618 self.resize(new_size)
619 }
620}
621
622impl Renderer {
626 pub fn size(&self) -> PhysicalSize<u32> {
628 self.size
629 }
630
631 pub fn grid_size(&self) -> (usize, usize) {
633 self.cell_renderer.grid_size()
634 }
635
636 pub fn cell_width(&self) -> f32 {
638 self.cell_renderer.cell_width()
639 }
640
641 pub fn cell_height(&self) -> f32 {
643 self.cell_renderer.cell_height()
644 }
645
646 pub fn window_padding(&self) -> f32 {
648 self.cell_renderer.window_padding()
649 }
650
651 pub fn scrollbar_width(&self) -> f32 {
653 self.cell_renderer.scrollbar.width()
654 }
655
656 pub fn chrome_overhead(&self) -> (f32, f32) {
659 self.cell_renderer.chrome_overhead()
660 }
661
662 pub fn content_offset_y(&self) -> f32 {
664 self.cell_renderer.content_offset_y()
665 }
666
667 pub fn scale_factor(&self) -> f32 {
669 self.cell_renderer.scale_factor
670 }
671
672 pub fn set_content_offset_y(&mut self, logical_offset: f32) -> Option<(usize, usize)> {
678 let physical_offset = logical_offset * self.cell_renderer.scale_factor;
680 let result = self.cell_renderer.set_content_offset_y(physical_offset);
681 self.graphics_renderer.set_content_offset_y(physical_offset);
683 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
685 custom_shader.set_content_offset_y(physical_offset);
686 }
687 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
689 cursor_shader.set_content_offset_y(physical_offset);
690 }
691 if result.is_some() {
692 self.dirty = true;
693 }
694 result
695 }
696
697 pub fn content_offset_x(&self) -> f32 {
699 self.cell_renderer.content_offset_x()
700 }
701
702 pub fn set_content_offset_x(&mut self, logical_offset: f32) -> Option<(usize, usize)> {
705 let physical_offset = logical_offset * self.cell_renderer.scale_factor;
706 let result = self.cell_renderer.set_content_offset_x(physical_offset);
707 self.graphics_renderer.set_content_offset_x(physical_offset);
708 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
709 custom_shader.set_content_offset_x(physical_offset);
710 }
711 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
712 cursor_shader.set_content_offset_x(physical_offset);
713 }
714 if result.is_some() {
715 self.dirty = true;
716 }
717 result
718 }
719
720 pub fn content_inset_bottom(&self) -> f32 {
722 self.cell_renderer.content_inset_bottom()
723 }
724
725 pub fn content_inset_right(&self) -> f32 {
727 self.cell_renderer.content_inset_right()
728 }
729
730 pub fn set_content_inset_bottom(&mut self, logical_inset: f32) -> Option<(usize, usize)> {
733 let physical_inset = logical_inset * self.cell_renderer.scale_factor;
734 let result = self.cell_renderer.set_content_inset_bottom(physical_inset);
735 if result.is_some() {
736 self.dirty = true;
737 self.last_scrollbar_state = (usize::MAX, 0, 0, 0, 0, 0, 0, 0, 0, 0);
740 }
741 result
742 }
743
744 pub fn set_content_inset_right(&mut self, logical_inset: f32) -> Option<(usize, usize)> {
747 let physical_inset = logical_inset * self.cell_renderer.scale_factor;
748 let result = self.cell_renderer.set_content_inset_right(physical_inset);
749
750 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
752 custom_shader.set_content_inset_right(physical_inset);
753 }
754 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
756 cursor_shader.set_content_inset_right(physical_inset);
757 }
758
759 if result.is_some() {
760 self.dirty = true;
761 self.last_scrollbar_state = (usize::MAX, 0, 0, 0, 0, 0, 0, 0, 0, 0);
767 }
768 result
769 }
770
771 pub fn set_egui_bottom_inset(&mut self, logical_inset: f32) -> Option<(usize, usize)> {
777 let physical_inset = logical_inset * self.cell_renderer.scale_factor;
778 if (self.cell_renderer.grid.egui_bottom_inset - physical_inset).abs() > f32::EPSILON {
779 self.cell_renderer.grid.egui_bottom_inset = physical_inset;
780 let (w, h) = (
781 self.cell_renderer.config.width,
782 self.cell_renderer.config.height,
783 );
784 return Some(self.cell_renderer.resize(w, h));
785 }
786 None
787 }
788
789 pub fn set_egui_right_inset(&mut self, logical_inset: f32) {
795 let physical_inset = logical_inset * self.cell_renderer.scale_factor;
796 self.cell_renderer.grid.egui_right_inset = physical_inset;
797 }
798}