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}
104
105#[derive(Clone, Copy, Debug)]
107pub struct DividerRenderInfo {
108 pub x: f32,
110 pub y: f32,
112 pub width: f32,
114 pub height: f32,
116 pub hovered: bool,
118}
119
120impl DividerRenderInfo {
121 pub fn from_rect(rect: &par_term_config::DividerRect, hovered: bool) -> Self {
123 Self {
124 x: rect.x,
125 y: rect.y,
126 width: rect.width,
127 height: rect.height,
128 hovered,
129 }
130 }
131}
132
133#[derive(Clone, Debug)]
135pub struct PaneTitleInfo {
136 pub x: f32,
138 pub y: f32,
140 pub width: f32,
142 pub height: f32,
144 pub title: String,
146 pub focused: bool,
148 pub text_color: [f32; 3],
150 pub bg_color: [f32; 3],
152}
153
154#[derive(Clone, Copy, Debug)]
156pub struct PaneDividerSettings {
157 pub divider_color: [f32; 3],
159 pub hover_color: [f32; 3],
161 pub show_focus_indicator: bool,
163 pub focus_color: [f32; 3],
165 pub focus_width: f32,
167 pub divider_style: par_term_config::DividerStyle,
169}
170
171impl Default for PaneDividerSettings {
172 fn default() -> Self {
173 Self {
174 divider_color: [0.3, 0.3, 0.3],
175 hover_color: [0.5, 0.6, 0.8],
176 show_focus_indicator: true,
177 focus_color: [0.4, 0.6, 1.0],
178 focus_width: 1.0,
179 divider_style: par_term_config::DividerStyle::default(),
180 }
181 }
182}
183
184pub struct Renderer {
186 pub(crate) cell_renderer: CellRenderer,
188
189 pub(crate) graphics_renderer: GraphicsRenderer,
191
192 pub(crate) sixel_graphics: Vec<crate::graphics_renderer::GraphicRenderInfo>,
195
196 pub(crate) egui_renderer: egui_wgpu::Renderer,
198
199 pub(crate) custom_shader_renderer: Option<CustomShaderRenderer>,
201 pub(crate) custom_shader_path: Option<String>,
203
204 pub(crate) cursor_shader_renderer: Option<CustomShaderRenderer>,
206 pub(crate) cursor_shader_path: Option<String>,
208
209 pub(crate) size: PhysicalSize<u32>,
211
212 pub(crate) dirty: bool,
214
215 pub(crate) last_scrollbar_state: (usize, usize, usize, usize, u32, u32, u32, u32, u32, u32),
219
220 pub(crate) cursor_shader_disabled_for_alt_screen: bool,
222
223 pub(crate) debug_text: Option<String>,
225
226 pub(crate) scratch_divider_instances: Vec<crate::cell_renderer::BackgroundInstance>,
229}
230
231impl Renderer {
232 pub async fn new(params: RendererParams<'_>) -> Result<Self> {
234 let window = params.window;
235 let font_family = params.font_family;
236 let font_family_bold = params.font_family_bold;
237 let font_family_italic = params.font_family_italic;
238 let font_family_bold_italic = params.font_family_bold_italic;
239 let font_ranges = params.font_ranges;
240 let font_size = params.font_size;
241 let line_spacing = params.line_spacing;
242 let char_spacing = params.char_spacing;
243 let scrollbar_position = params.scrollbar_position;
244 let scrollbar_thumb_color = params.scrollbar_thumb_color;
245 let scrollbar_track_color = params.scrollbar_track_color;
246 let enable_text_shaping = params.enable_text_shaping;
247 let enable_ligatures = params.enable_ligatures;
248 let enable_kerning = params.enable_kerning;
249 let font_antialias = params.font_antialias;
250 let font_hinting = params.font_hinting;
251 let font_thin_strokes = params.font_thin_strokes;
252 let minimum_contrast = params.minimum_contrast;
253 let vsync_mode = params.vsync_mode;
254 let power_preference = params.power_preference;
255 let window_opacity = params.window_opacity;
256 let background_color = params.background_color;
257 let background_image_path = params.background_image_path;
258 let background_image_enabled = params.background_image_enabled;
259 let background_image_mode = params.background_image_mode;
260 let background_image_opacity = params.background_image_opacity;
261 let custom_shader_path = params.custom_shader_path;
262 let custom_shader_enabled = params.custom_shader_enabled;
263 let custom_shader_animation = params.custom_shader_animation;
264 let custom_shader_animation_speed = params.custom_shader_animation_speed;
265 let custom_shader_full_content = params.custom_shader_full_content;
266 let custom_shader_brightness = params.custom_shader_brightness;
267 let custom_shader_channel_paths = params.custom_shader_channel_paths;
268 let custom_shader_cubemap_path = params.custom_shader_cubemap_path;
269 let use_background_as_channel0 = params.use_background_as_channel0;
270 let image_scaling_mode = params.image_scaling_mode;
271 let image_preserve_aspect_ratio = params.image_preserve_aspect_ratio;
272 let cursor_shader_path = params.cursor_shader_path;
273 let cursor_shader_enabled = params.cursor_shader_enabled;
274 let cursor_shader_animation = params.cursor_shader_animation;
275 let cursor_shader_animation_speed = params.cursor_shader_animation_speed;
276
277 let size = window.inner_size();
278 let scale_factor = window.scale_factor();
279
280 let platform_dpi = if cfg!(target_os = "macos") {
283 72.0
284 } else {
285 96.0
286 };
287
288 let base_font_pixels = font_size * platform_dpi / 72.0;
290 let font_size_pixels = (base_font_pixels * scale_factor as f32).max(1.0);
291
292 let font_manager = par_term_fonts::font_manager::FontManager::new(
294 font_family,
295 font_family_bold,
296 font_family_italic,
297 font_family_bold_italic,
298 font_ranges,
299 )?;
300
301 let (font_ascent, font_descent, font_leading, char_advance) = {
302 let primary_font = font_manager
303 .get_font(0)
304 .expect("Primary font at index 0 must exist after FontManager initialization");
305 let metrics = primary_font.metrics(&[]);
306 let scale = font_size_pixels / metrics.units_per_em as f32;
307
308 let glyph_id = primary_font.charmap().map('m');
310 let advance = primary_font.glyph_metrics(&[]).advance_width(glyph_id) * scale;
311
312 (
313 metrics.ascent * scale,
314 metrics.descent * scale,
315 metrics.leading * scale,
316 advance,
317 )
318 };
319
320 let natural_line_height = font_ascent + font_descent + font_leading;
323 let char_height = (natural_line_height * line_spacing).max(1.0).round();
324
325 let scale = scale_factor as f32;
327 let window_padding = params.window_padding * scale;
328 let scrollbar_width = params.scrollbar_width * scale;
329
330 let available_width = (size.width as f32 - window_padding * 2.0 - scrollbar_width).max(0.0);
332 let available_height = (size.height as f32 - window_padding * 2.0).max(0.0);
333
334 let char_width = (char_advance * char_spacing).max(1.0).round(); let cols = (available_width / char_width).max(1.0) as usize;
337 let rows = (available_height / char_height).max(1.0) as usize;
338
339 let bg_path = if background_image_enabled {
341 background_image_path
342 } else {
343 None
344 };
345 log::info!(
346 "Renderer::new: background_image_enabled={}, path={:?}",
347 background_image_enabled,
348 bg_path
349 );
350 let cell_renderer = CellRenderer::new(
351 window.clone(),
352 CellRendererConfig {
353 font_family,
354 font_family_bold,
355 font_family_italic,
356 font_family_bold_italic,
357 font_ranges,
358 font_size,
359 cols,
360 rows,
361 window_padding,
362 line_spacing,
363 char_spacing,
364 scrollbar_position,
365 scrollbar_width,
366 scrollbar_thumb_color,
367 scrollbar_track_color,
368 enable_text_shaping,
369 enable_ligatures,
370 enable_kerning,
371 font_antialias,
372 font_hinting,
373 font_thin_strokes,
374 minimum_contrast,
375 vsync_mode,
376 power_preference,
377 window_opacity,
378 background_color,
379 background_image_path: bg_path,
380 background_image_mode,
381 background_image_opacity,
382 },
383 )
384 .await?;
385
386 let egui_renderer = egui_wgpu::Renderer::new(
388 cell_renderer.device(),
389 cell_renderer.surface_format(),
390 egui_wgpu::RendererOptions {
391 msaa_samples: 1,
392 depth_stencil_format: None,
393 dithering: false,
394 predictable_texture_filtering: false,
395 },
396 );
397
398 let graphics_renderer = GraphicsRenderer::new(
400 cell_renderer.device(),
401 cell_renderer.surface_format(),
402 cell_renderer.cell_width(),
403 cell_renderer.cell_height(),
404 cell_renderer.window_padding(),
405 image_scaling_mode,
406 image_preserve_aspect_ratio,
407 )?;
408
409 let (mut custom_shader_renderer, initial_shader_path) = shaders::init_custom_shader(
411 &cell_renderer,
412 shaders::CustomShaderInitParams {
413 size_width: size.width,
414 size_height: size.height,
415 window_padding,
416 path: custom_shader_path,
417 enabled: custom_shader_enabled,
418 animation: custom_shader_animation,
419 animation_speed: custom_shader_animation_speed,
420 window_opacity,
421 full_content: custom_shader_full_content,
422 brightness: custom_shader_brightness,
423 channel_paths: custom_shader_channel_paths,
424 cubemap_path: custom_shader_cubemap_path,
425 use_background_as_channel0,
426 },
427 );
428
429 let (mut cursor_shader_renderer, initial_cursor_shader_path) = shaders::init_cursor_shader(
431 &cell_renderer,
432 shaders::CursorShaderInitParams {
433 size_width: size.width,
434 size_height: size.height,
435 window_padding,
436 path: cursor_shader_path,
437 enabled: cursor_shader_enabled,
438 animation: cursor_shader_animation,
439 animation_speed: cursor_shader_animation_speed,
440 window_opacity,
441 },
442 );
443
444 if let Some(ref mut cs) = custom_shader_renderer {
446 cs.set_scale_factor(scale);
447 }
448 if let Some(ref mut cs) = cursor_shader_renderer {
449 cs.set_scale_factor(scale);
450 }
451
452 log::info!(
453 "[renderer] Renderer created: custom_shader_loaded={}, cursor_shader_loaded={}",
454 initial_shader_path.is_some(),
455 initial_cursor_shader_path.is_some()
456 );
457
458 Ok(Self {
459 cell_renderer,
460 graphics_renderer,
461 sixel_graphics: Vec::new(),
462 egui_renderer,
463 custom_shader_renderer,
464 custom_shader_path: initial_shader_path,
465 cursor_shader_renderer,
466 cursor_shader_path: initial_cursor_shader_path,
467 size,
468 dirty: true, last_scrollbar_state: (usize::MAX, 0, 0, 0, 0, 0, 0, 0, 0, 0), cursor_shader_disabled_for_alt_screen: false,
471 debug_text: None,
472 scratch_divider_instances: Vec::new(),
473 })
474 }
475
476 pub fn resize(&mut self, new_size: PhysicalSize<u32>) -> (usize, usize) {
478 if new_size.width > 0 && new_size.height > 0 {
479 self.size = new_size;
480 self.dirty = true; let result = self.cell_renderer.resize(new_size.width, new_size.height);
482
483 self.graphics_renderer.update_cell_dimensions(
485 self.cell_renderer.cell_width(),
486 self.cell_renderer.cell_height(),
487 self.cell_renderer.window_padding(),
488 );
489
490 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
492 custom_shader.resize(self.cell_renderer.device(), new_size.width, new_size.height);
493 custom_shader.update_cell_dimensions(
495 self.cell_renderer.cell_width(),
496 self.cell_renderer.cell_height(),
497 self.cell_renderer.window_padding(),
498 );
499 }
500
501 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
503 cursor_shader.resize(self.cell_renderer.device(), new_size.width, new_size.height);
504 cursor_shader.update_cell_dimensions(
506 self.cell_renderer.cell_width(),
507 self.cell_renderer.cell_height(),
508 self.cell_renderer.window_padding(),
509 );
510 }
511
512 return result;
513 }
514
515 self.cell_renderer.grid_size()
516 }
517
518 pub fn handle_scale_factor_change(
520 &mut self,
521 scale_factor: f64,
522 new_size: PhysicalSize<u32>,
523 ) -> (usize, usize) {
524 let old_scale = self.cell_renderer.scale_factor;
525 self.cell_renderer.update_scale_factor(scale_factor);
526 let new_scale = self.cell_renderer.scale_factor;
527
528 if old_scale > 0.0 && (old_scale - new_scale).abs() > f32::EPSILON {
530 let logical_offset_y = self.cell_renderer.content_offset_y() / old_scale;
532 let new_physical_offset_y = logical_offset_y * new_scale;
533 self.cell_renderer
534 .set_content_offset_y(new_physical_offset_y);
535 self.graphics_renderer
536 .set_content_offset_y(new_physical_offset_y);
537 if let Some(ref mut cs) = self.custom_shader_renderer {
538 cs.set_content_offset_y(new_physical_offset_y);
539 }
540 if let Some(ref mut cs) = self.cursor_shader_renderer {
541 cs.set_content_offset_y(new_physical_offset_y);
542 }
543
544 let logical_offset_x = self.cell_renderer.content_offset_x() / old_scale;
546 let new_physical_offset_x = logical_offset_x * new_scale;
547 self.cell_renderer
548 .set_content_offset_x(new_physical_offset_x);
549 self.graphics_renderer
550 .set_content_offset_x(new_physical_offset_x);
551 if let Some(ref mut cs) = self.custom_shader_renderer {
552 cs.set_content_offset_x(new_physical_offset_x);
553 }
554 if let Some(ref mut cs) = self.cursor_shader_renderer {
555 cs.set_content_offset_x(new_physical_offset_x);
556 }
557
558 let logical_inset_bottom = self.cell_renderer.content_inset_bottom() / old_scale;
560 let new_physical_inset_bottom = logical_inset_bottom * new_scale;
561 self.cell_renderer
562 .set_content_inset_bottom(new_physical_inset_bottom);
563
564 if self.cell_renderer.grid.egui_bottom_inset > 0.0 {
566 let logical_egui_bottom = self.cell_renderer.grid.egui_bottom_inset / old_scale;
567 self.cell_renderer.grid.egui_bottom_inset = logical_egui_bottom * new_scale;
568 }
569
570 if self.cell_renderer.grid.content_inset_right > 0.0 {
572 let logical_inset_right = self.cell_renderer.grid.content_inset_right / old_scale;
573 self.cell_renderer.grid.content_inset_right = logical_inset_right * new_scale;
574 }
575
576 if self.cell_renderer.grid.egui_right_inset > 0.0 {
578 let logical_egui_right = self.cell_renderer.grid.egui_right_inset / old_scale;
579 self.cell_renderer.grid.egui_right_inset = logical_egui_right * new_scale;
580 }
581
582 let logical_padding = self.cell_renderer.window_padding() / old_scale;
584 let new_physical_padding = logical_padding * new_scale;
585 self.cell_renderer
586 .update_window_padding(new_physical_padding);
587
588 if let Some(ref mut cs) = self.custom_shader_renderer {
590 cs.set_scale_factor(new_scale);
591 }
592 if let Some(ref mut cs) = self.cursor_shader_renderer {
593 cs.set_scale_factor(new_scale);
594 }
595 }
596
597 self.resize(new_size)
598 }
599}
600
601impl Renderer {
605 pub fn size(&self) -> PhysicalSize<u32> {
607 self.size
608 }
609
610 pub fn grid_size(&self) -> (usize, usize) {
612 self.cell_renderer.grid_size()
613 }
614
615 pub fn cell_width(&self) -> f32 {
617 self.cell_renderer.cell_width()
618 }
619
620 pub fn cell_height(&self) -> f32 {
622 self.cell_renderer.cell_height()
623 }
624
625 pub fn window_padding(&self) -> f32 {
627 self.cell_renderer.window_padding()
628 }
629
630 pub fn scrollbar_width(&self) -> f32 {
632 self.cell_renderer.scrollbar.width()
633 }
634
635 pub fn chrome_overhead(&self) -> (f32, f32) {
638 self.cell_renderer.chrome_overhead()
639 }
640
641 pub fn content_offset_y(&self) -> f32 {
643 self.cell_renderer.content_offset_y()
644 }
645
646 pub fn scale_factor(&self) -> f32 {
648 self.cell_renderer.scale_factor
649 }
650
651 pub fn set_content_offset_y(&mut self, logical_offset: f32) -> Option<(usize, usize)> {
657 let physical_offset = logical_offset * self.cell_renderer.scale_factor;
659 let result = self.cell_renderer.set_content_offset_y(physical_offset);
660 self.graphics_renderer.set_content_offset_y(physical_offset);
662 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
664 custom_shader.set_content_offset_y(physical_offset);
665 }
666 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
668 cursor_shader.set_content_offset_y(physical_offset);
669 }
670 if result.is_some() {
671 self.dirty = true;
672 }
673 result
674 }
675
676 pub fn content_offset_x(&self) -> f32 {
678 self.cell_renderer.content_offset_x()
679 }
680
681 pub fn set_content_offset_x(&mut self, logical_offset: f32) -> Option<(usize, usize)> {
684 let physical_offset = logical_offset * self.cell_renderer.scale_factor;
685 let result = self.cell_renderer.set_content_offset_x(physical_offset);
686 self.graphics_renderer.set_content_offset_x(physical_offset);
687 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
688 custom_shader.set_content_offset_x(physical_offset);
689 }
690 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
691 cursor_shader.set_content_offset_x(physical_offset);
692 }
693 if result.is_some() {
694 self.dirty = true;
695 }
696 result
697 }
698
699 pub fn content_inset_bottom(&self) -> f32 {
701 self.cell_renderer.content_inset_bottom()
702 }
703
704 pub fn content_inset_right(&self) -> f32 {
706 self.cell_renderer.content_inset_right()
707 }
708
709 pub fn set_content_inset_bottom(&mut self, logical_inset: f32) -> Option<(usize, usize)> {
712 let physical_inset = logical_inset * self.cell_renderer.scale_factor;
713 let result = self.cell_renderer.set_content_inset_bottom(physical_inset);
714 if result.is_some() {
715 self.dirty = true;
716 self.last_scrollbar_state = (usize::MAX, 0, 0, 0, 0, 0, 0, 0, 0, 0);
719 }
720 result
721 }
722
723 pub fn set_content_inset_right(&mut self, logical_inset: f32) -> Option<(usize, usize)> {
726 let physical_inset = logical_inset * self.cell_renderer.scale_factor;
727 let result = self.cell_renderer.set_content_inset_right(physical_inset);
728
729 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
731 custom_shader.set_content_inset_right(physical_inset);
732 }
733 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
735 cursor_shader.set_content_inset_right(physical_inset);
736 }
737
738 if result.is_some() {
739 self.dirty = true;
740 self.last_scrollbar_state = (usize::MAX, 0, 0, 0, 0, 0, 0, 0, 0, 0);
746 }
747 result
748 }
749
750 pub fn set_egui_bottom_inset(&mut self, logical_inset: f32) -> Option<(usize, usize)> {
756 let physical_inset = logical_inset * self.cell_renderer.scale_factor;
757 if (self.cell_renderer.grid.egui_bottom_inset - physical_inset).abs() > f32::EPSILON {
758 self.cell_renderer.grid.egui_bottom_inset = physical_inset;
759 let (w, h) = (
760 self.cell_renderer.config.width,
761 self.cell_renderer.config.height,
762 );
763 return Some(self.cell_renderer.resize(w, h));
764 }
765 None
766 }
767
768 pub fn set_egui_right_inset(&mut self, logical_inset: f32) {
774 let physical_inset = logical_inset * self.cell_renderer.scale_factor;
775 self.cell_renderer.grid.egui_right_inset = physical_inset;
776 }
777}