1use crate::cell_renderer::{Cell, CellRenderer, CellRendererConfig, PaneViewport};
2use crate::custom_shader_renderer::CustomShaderRenderer;
3use crate::graphics_renderer::GraphicsRenderer;
4use anyhow::Result;
5use winit::dpi::PhysicalSize;
6
7mod egui_render;
8pub mod graphics;
9pub mod params;
10
11mod render_passes;
12mod rendering;
13pub mod shaders;
14mod state;
15
16pub use par_term_config::SeparatorMark;
18pub use params::RendererParams;
19pub use rendering::SplitPanesRenderParams;
20
21pub fn compute_visible_separator_marks(
30 marks: &[par_term_config::ScrollbackMark],
31 scrollback_len: usize,
32 scroll_offset: usize,
33 visible_lines: usize,
34) -> Vec<SeparatorMark> {
35 let viewport_start = scrollback_len.saturating_sub(scroll_offset);
36 let viewport_end = viewport_start + visible_lines;
37
38 marks
39 .iter()
40 .filter_map(|mark| {
41 if mark.line >= viewport_start && mark.line < viewport_end {
42 let screen_row = mark.line - viewport_start;
43 Some((screen_row, mark.exit_code, mark.color))
44 } else {
45 None
46 }
47 })
48 .collect()
49}
50
51pub struct PaneRenderInfo<'a> {
53 pub viewport: PaneViewport,
55 pub cells: &'a [Cell],
57 pub grid_size: (usize, usize),
59 pub cursor_pos: Option<(usize, usize)>,
61 pub cursor_opacity: f32,
63 pub show_scrollbar: bool,
65 pub marks: Vec<par_term_config::ScrollbackMark>,
67 pub scrollback_len: usize,
69 pub scroll_offset: usize,
71 pub background: Option<par_term_config::PaneBackground>,
73 pub graphics: Vec<par_term_emu_core_rust::graphics::TerminalGraphic>,
75}
76
77#[derive(Clone, Copy, Debug)]
79pub struct DividerRenderInfo {
80 pub x: f32,
82 pub y: f32,
84 pub width: f32,
86 pub height: f32,
88 pub hovered: bool,
90}
91
92impl DividerRenderInfo {
93 pub fn from_rect(rect: &par_term_config::DividerRect, hovered: bool) -> Self {
95 Self {
96 x: rect.x,
97 y: rect.y,
98 width: rect.width,
99 height: rect.height,
100 hovered,
101 }
102 }
103}
104
105#[derive(Clone, Debug)]
107pub struct PaneTitleInfo {
108 pub x: f32,
110 pub y: f32,
112 pub width: f32,
114 pub height: f32,
116 pub title: String,
118 pub focused: bool,
120 pub text_color: [f32; 3],
122 pub bg_color: [f32; 3],
124}
125
126#[derive(Clone, Copy, Debug)]
128pub struct PaneDividerSettings {
129 pub divider_color: [f32; 3],
131 pub hover_color: [f32; 3],
133 pub show_focus_indicator: bool,
135 pub focus_color: [f32; 3],
137 pub focus_width: f32,
139 pub divider_style: par_term_config::DividerStyle,
141}
142
143impl Default for PaneDividerSettings {
144 fn default() -> Self {
145 Self {
146 divider_color: [0.3, 0.3, 0.3],
147 hover_color: [0.5, 0.6, 0.8],
148 show_focus_indicator: true,
149 focus_color: [0.4, 0.6, 1.0],
150 focus_width: 1.0,
151 divider_style: par_term_config::DividerStyle::default(),
152 }
153 }
154}
155
156pub struct Renderer {
158 pub(crate) cell_renderer: CellRenderer,
160
161 pub(crate) graphics_renderer: GraphicsRenderer,
163
164 pub(crate) sixel_graphics: Vec<crate::graphics_renderer::GraphicRenderInfo>,
167
168 pub(crate) egui_renderer: egui_wgpu::Renderer,
170
171 pub(crate) custom_shader_renderer: Option<CustomShaderRenderer>,
173 pub(crate) custom_shader_path: Option<String>,
175
176 pub(crate) cursor_shader_renderer: Option<CustomShaderRenderer>,
178 pub(crate) cursor_shader_path: Option<String>,
180
181 pub(crate) size: PhysicalSize<u32>,
183
184 pub(crate) dirty: bool,
186
187 pub(crate) last_scrollbar_state: (usize, usize, usize, usize, u32, u32, u32, u32, u32, u32),
191
192 pub(crate) cursor_shader_disabled_for_alt_screen: bool,
194
195 pub(crate) debug_text: Option<String>,
197}
198
199impl Renderer {
200 pub async fn new(params: RendererParams<'_>) -> Result<Self> {
202 let window = params.window;
203 let font_family = params.font_family;
204 let font_family_bold = params.font_family_bold;
205 let font_family_italic = params.font_family_italic;
206 let font_family_bold_italic = params.font_family_bold_italic;
207 let font_ranges = params.font_ranges;
208 let font_size = params.font_size;
209 let line_spacing = params.line_spacing;
210 let char_spacing = params.char_spacing;
211 let scrollbar_position = params.scrollbar_position;
212 let scrollbar_thumb_color = params.scrollbar_thumb_color;
213 let scrollbar_track_color = params.scrollbar_track_color;
214 let enable_text_shaping = params.enable_text_shaping;
215 let enable_ligatures = params.enable_ligatures;
216 let enable_kerning = params.enable_kerning;
217 let font_antialias = params.font_antialias;
218 let font_hinting = params.font_hinting;
219 let font_thin_strokes = params.font_thin_strokes;
220 let minimum_contrast = params.minimum_contrast;
221 let vsync_mode = params.vsync_mode;
222 let power_preference = params.power_preference;
223 let window_opacity = params.window_opacity;
224 let background_color = params.background_color;
225 let background_image_path = params.background_image_path;
226 let background_image_enabled = params.background_image_enabled;
227 let background_image_mode = params.background_image_mode;
228 let background_image_opacity = params.background_image_opacity;
229 let custom_shader_path = params.custom_shader_path;
230 let custom_shader_enabled = params.custom_shader_enabled;
231 let custom_shader_animation = params.custom_shader_animation;
232 let custom_shader_animation_speed = params.custom_shader_animation_speed;
233 let custom_shader_full_content = params.custom_shader_full_content;
234 let custom_shader_brightness = params.custom_shader_brightness;
235 let custom_shader_channel_paths = params.custom_shader_channel_paths;
236 let custom_shader_cubemap_path = params.custom_shader_cubemap_path;
237 let use_background_as_channel0 = params.use_background_as_channel0;
238 let image_scaling_mode = params.image_scaling_mode;
239 let image_preserve_aspect_ratio = params.image_preserve_aspect_ratio;
240 let cursor_shader_path = params.cursor_shader_path;
241 let cursor_shader_enabled = params.cursor_shader_enabled;
242 let cursor_shader_animation = params.cursor_shader_animation;
243 let cursor_shader_animation_speed = params.cursor_shader_animation_speed;
244
245 let size = window.inner_size();
246 let scale_factor = window.scale_factor();
247
248 let platform_dpi = if cfg!(target_os = "macos") {
251 72.0
252 } else {
253 96.0
254 };
255
256 let base_font_pixels = font_size * platform_dpi / 72.0;
258 let font_size_pixels = (base_font_pixels * scale_factor as f32).max(1.0);
259
260 let font_manager = par_term_fonts::font_manager::FontManager::new(
262 font_family,
263 font_family_bold,
264 font_family_italic,
265 font_family_bold_italic,
266 font_ranges,
267 )?;
268
269 let (font_ascent, font_descent, font_leading, char_advance) = {
270 let primary_font = font_manager
271 .get_font(0)
272 .expect("Primary font at index 0 must exist after FontManager initialization");
273 let metrics = primary_font.metrics(&[]);
274 let scale = font_size_pixels / metrics.units_per_em as f32;
275
276 let glyph_id = primary_font.charmap().map('m');
278 let advance = primary_font.glyph_metrics(&[]).advance_width(glyph_id) * scale;
279
280 (
281 metrics.ascent * scale,
282 metrics.descent * scale,
283 metrics.leading * scale,
284 advance,
285 )
286 };
287
288 let natural_line_height = font_ascent + font_descent + font_leading;
291 let char_height = (natural_line_height * line_spacing).max(1.0).round();
292
293 let scale = scale_factor as f32;
295 let window_padding = params.window_padding * scale;
296 let scrollbar_width = params.scrollbar_width * scale;
297
298 let available_width = (size.width as f32 - window_padding * 2.0 - scrollbar_width).max(0.0);
300 let available_height = (size.height as f32 - window_padding * 2.0).max(0.0);
301
302 let char_width = (char_advance * char_spacing).max(1.0).round(); let cols = (available_width / char_width).max(1.0) as usize;
305 let rows = (available_height / char_height).max(1.0) as usize;
306
307 let bg_path = if background_image_enabled {
309 background_image_path
310 } else {
311 None
312 };
313 log::info!(
314 "Renderer::new: background_image_enabled={}, path={:?}",
315 background_image_enabled,
316 bg_path
317 );
318 let cell_renderer = CellRenderer::new(
319 window.clone(),
320 CellRendererConfig {
321 font_family,
322 font_family_bold,
323 font_family_italic,
324 font_family_bold_italic,
325 font_ranges,
326 font_size,
327 cols,
328 rows,
329 window_padding,
330 line_spacing,
331 char_spacing,
332 scrollbar_position,
333 scrollbar_width,
334 scrollbar_thumb_color,
335 scrollbar_track_color,
336 enable_text_shaping,
337 enable_ligatures,
338 enable_kerning,
339 font_antialias,
340 font_hinting,
341 font_thin_strokes,
342 minimum_contrast,
343 vsync_mode,
344 power_preference,
345 window_opacity,
346 background_color,
347 background_image_path: bg_path,
348 background_image_mode,
349 background_image_opacity,
350 },
351 )
352 .await?;
353
354 let egui_renderer = egui_wgpu::Renderer::new(
356 cell_renderer.device(),
357 cell_renderer.surface_format(),
358 egui_wgpu::RendererOptions {
359 msaa_samples: 1,
360 depth_stencil_format: None,
361 dithering: false,
362 predictable_texture_filtering: false,
363 },
364 );
365
366 let graphics_renderer = GraphicsRenderer::new(
368 cell_renderer.device(),
369 cell_renderer.surface_format(),
370 cell_renderer.cell_width(),
371 cell_renderer.cell_height(),
372 cell_renderer.window_padding(),
373 image_scaling_mode,
374 image_preserve_aspect_ratio,
375 )?;
376
377 let (mut custom_shader_renderer, initial_shader_path) = shaders::init_custom_shader(
379 &cell_renderer,
380 shaders::CustomShaderInitParams {
381 size_width: size.width,
382 size_height: size.height,
383 window_padding,
384 path: custom_shader_path,
385 enabled: custom_shader_enabled,
386 animation: custom_shader_animation,
387 animation_speed: custom_shader_animation_speed,
388 window_opacity,
389 full_content: custom_shader_full_content,
390 brightness: custom_shader_brightness,
391 channel_paths: custom_shader_channel_paths,
392 cubemap_path: custom_shader_cubemap_path,
393 use_background_as_channel0,
394 },
395 );
396
397 let (mut cursor_shader_renderer, initial_cursor_shader_path) = shaders::init_cursor_shader(
399 &cell_renderer,
400 shaders::CursorShaderInitParams {
401 size_width: size.width,
402 size_height: size.height,
403 window_padding,
404 path: cursor_shader_path,
405 enabled: cursor_shader_enabled,
406 animation: cursor_shader_animation,
407 animation_speed: cursor_shader_animation_speed,
408 window_opacity,
409 },
410 );
411
412 if let Some(ref mut cs) = custom_shader_renderer {
414 cs.set_scale_factor(scale);
415 }
416 if let Some(ref mut cs) = cursor_shader_renderer {
417 cs.set_scale_factor(scale);
418 }
419
420 log::info!(
421 "[renderer] Renderer created: custom_shader_loaded={}, cursor_shader_loaded={}",
422 initial_shader_path.is_some(),
423 initial_cursor_shader_path.is_some()
424 );
425
426 Ok(Self {
427 cell_renderer,
428 graphics_renderer,
429 sixel_graphics: Vec::new(),
430 egui_renderer,
431 custom_shader_renderer,
432 custom_shader_path: initial_shader_path,
433 cursor_shader_renderer,
434 cursor_shader_path: initial_cursor_shader_path,
435 size,
436 dirty: true, last_scrollbar_state: (usize::MAX, 0, 0, 0, 0, 0, 0, 0, 0, 0), cursor_shader_disabled_for_alt_screen: false,
439 debug_text: None,
440 })
441 }
442
443 pub fn resize(&mut self, new_size: PhysicalSize<u32>) -> (usize, usize) {
445 if new_size.width > 0 && new_size.height > 0 {
446 self.size = new_size;
447 self.dirty = true; let result = self.cell_renderer.resize(new_size.width, new_size.height);
449
450 self.graphics_renderer.update_cell_dimensions(
452 self.cell_renderer.cell_width(),
453 self.cell_renderer.cell_height(),
454 self.cell_renderer.window_padding(),
455 );
456
457 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
459 custom_shader.resize(self.cell_renderer.device(), new_size.width, new_size.height);
460 custom_shader.update_cell_dimensions(
462 self.cell_renderer.cell_width(),
463 self.cell_renderer.cell_height(),
464 self.cell_renderer.window_padding(),
465 );
466 }
467
468 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
470 cursor_shader.resize(self.cell_renderer.device(), new_size.width, new_size.height);
471 cursor_shader.update_cell_dimensions(
473 self.cell_renderer.cell_width(),
474 self.cell_renderer.cell_height(),
475 self.cell_renderer.window_padding(),
476 );
477 }
478
479 return result;
480 }
481
482 self.cell_renderer.grid_size()
483 }
484
485 pub fn handle_scale_factor_change(
487 &mut self,
488 scale_factor: f64,
489 new_size: PhysicalSize<u32>,
490 ) -> (usize, usize) {
491 let old_scale = self.cell_renderer.scale_factor;
492 self.cell_renderer.update_scale_factor(scale_factor);
493 let new_scale = self.cell_renderer.scale_factor;
494
495 if old_scale > 0.0 && (old_scale - new_scale).abs() > f32::EPSILON {
497 let logical_offset_y = self.cell_renderer.content_offset_y() / old_scale;
499 let new_physical_offset_y = logical_offset_y * new_scale;
500 self.cell_renderer
501 .set_content_offset_y(new_physical_offset_y);
502 self.graphics_renderer
503 .set_content_offset_y(new_physical_offset_y);
504 if let Some(ref mut cs) = self.custom_shader_renderer {
505 cs.set_content_offset_y(new_physical_offset_y);
506 }
507 if let Some(ref mut cs) = self.cursor_shader_renderer {
508 cs.set_content_offset_y(new_physical_offset_y);
509 }
510
511 let logical_offset_x = self.cell_renderer.content_offset_x() / old_scale;
513 let new_physical_offset_x = logical_offset_x * new_scale;
514 self.cell_renderer
515 .set_content_offset_x(new_physical_offset_x);
516 self.graphics_renderer
517 .set_content_offset_x(new_physical_offset_x);
518 if let Some(ref mut cs) = self.custom_shader_renderer {
519 cs.set_content_offset_x(new_physical_offset_x);
520 }
521 if let Some(ref mut cs) = self.cursor_shader_renderer {
522 cs.set_content_offset_x(new_physical_offset_x);
523 }
524
525 let logical_inset_bottom = self.cell_renderer.content_inset_bottom() / old_scale;
527 let new_physical_inset_bottom = logical_inset_bottom * new_scale;
528 self.cell_renderer
529 .set_content_inset_bottom(new_physical_inset_bottom);
530
531 if self.cell_renderer.grid.egui_bottom_inset > 0.0 {
533 let logical_egui_bottom = self.cell_renderer.grid.egui_bottom_inset / old_scale;
534 self.cell_renderer.grid.egui_bottom_inset = logical_egui_bottom * new_scale;
535 }
536
537 if self.cell_renderer.grid.content_inset_right > 0.0 {
539 let logical_inset_right = self.cell_renderer.grid.content_inset_right / old_scale;
540 self.cell_renderer.grid.content_inset_right = logical_inset_right * new_scale;
541 }
542
543 if self.cell_renderer.grid.egui_right_inset > 0.0 {
545 let logical_egui_right = self.cell_renderer.grid.egui_right_inset / old_scale;
546 self.cell_renderer.grid.egui_right_inset = logical_egui_right * new_scale;
547 }
548
549 let logical_padding = self.cell_renderer.window_padding() / old_scale;
551 let new_physical_padding = logical_padding * new_scale;
552 self.cell_renderer
553 .update_window_padding(new_physical_padding);
554
555 if let Some(ref mut cs) = self.custom_shader_renderer {
557 cs.set_scale_factor(new_scale);
558 }
559 if let Some(ref mut cs) = self.cursor_shader_renderer {
560 cs.set_scale_factor(new_scale);
561 }
562 }
563
564 self.resize(new_size)
565 }
566}
567
568impl Renderer {
572 pub fn size(&self) -> PhysicalSize<u32> {
574 self.size
575 }
576
577 pub fn grid_size(&self) -> (usize, usize) {
579 self.cell_renderer.grid_size()
580 }
581
582 pub fn cell_width(&self) -> f32 {
584 self.cell_renderer.cell_width()
585 }
586
587 pub fn cell_height(&self) -> f32 {
589 self.cell_renderer.cell_height()
590 }
591
592 pub fn window_padding(&self) -> f32 {
594 self.cell_renderer.window_padding()
595 }
596
597 pub fn scrollbar_width(&self) -> f32 {
599 self.cell_renderer.scrollbar.width()
600 }
601
602 pub fn chrome_overhead(&self) -> (f32, f32) {
605 self.cell_renderer.chrome_overhead()
606 }
607
608 pub fn content_offset_y(&self) -> f32 {
610 self.cell_renderer.content_offset_y()
611 }
612
613 pub fn scale_factor(&self) -> f32 {
615 self.cell_renderer.scale_factor
616 }
617
618 pub fn set_content_offset_y(&mut self, logical_offset: f32) -> Option<(usize, usize)> {
624 let physical_offset = logical_offset * self.cell_renderer.scale_factor;
626 let result = self.cell_renderer.set_content_offset_y(physical_offset);
627 self.graphics_renderer.set_content_offset_y(physical_offset);
629 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
631 custom_shader.set_content_offset_y(physical_offset);
632 }
633 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
635 cursor_shader.set_content_offset_y(physical_offset);
636 }
637 if result.is_some() {
638 self.dirty = true;
639 }
640 result
641 }
642
643 pub fn content_offset_x(&self) -> f32 {
645 self.cell_renderer.content_offset_x()
646 }
647
648 pub fn set_content_offset_x(&mut self, logical_offset: f32) -> Option<(usize, usize)> {
651 let physical_offset = logical_offset * self.cell_renderer.scale_factor;
652 let result = self.cell_renderer.set_content_offset_x(physical_offset);
653 self.graphics_renderer.set_content_offset_x(physical_offset);
654 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
655 custom_shader.set_content_offset_x(physical_offset);
656 }
657 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
658 cursor_shader.set_content_offset_x(physical_offset);
659 }
660 if result.is_some() {
661 self.dirty = true;
662 }
663 result
664 }
665
666 pub fn content_inset_bottom(&self) -> f32 {
668 self.cell_renderer.content_inset_bottom()
669 }
670
671 pub fn content_inset_right(&self) -> f32 {
673 self.cell_renderer.content_inset_right()
674 }
675
676 pub fn set_content_inset_bottom(&mut self, logical_inset: f32) -> Option<(usize, usize)> {
679 let physical_inset = logical_inset * self.cell_renderer.scale_factor;
680 let result = self.cell_renderer.set_content_inset_bottom(physical_inset);
681 if result.is_some() {
682 self.dirty = true;
683 self.last_scrollbar_state = (usize::MAX, 0, 0, 0, 0, 0, 0, 0, 0, 0);
686 }
687 result
688 }
689
690 pub fn set_content_inset_right(&mut self, logical_inset: f32) -> Option<(usize, usize)> {
693 let physical_inset = logical_inset * self.cell_renderer.scale_factor;
694 let result = self.cell_renderer.set_content_inset_right(physical_inset);
695
696 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
698 custom_shader.set_content_inset_right(physical_inset);
699 }
700 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
702 cursor_shader.set_content_inset_right(physical_inset);
703 }
704
705 if result.is_some() {
706 self.dirty = true;
707 self.last_scrollbar_state = (usize::MAX, 0, 0, 0, 0, 0, 0, 0, 0, 0);
713 }
714 result
715 }
716
717 pub fn set_egui_bottom_inset(&mut self, logical_inset: f32) -> Option<(usize, usize)> {
723 let physical_inset = logical_inset * self.cell_renderer.scale_factor;
724 if (self.cell_renderer.grid.egui_bottom_inset - physical_inset).abs() > f32::EPSILON {
725 self.cell_renderer.grid.egui_bottom_inset = physical_inset;
726 let (w, h) = (
727 self.cell_renderer.config.width,
728 self.cell_renderer.config.height,
729 );
730 return Some(self.cell_renderer.resize(w, h));
731 }
732 None
733 }
734
735 pub fn set_egui_right_inset(&mut self, logical_inset: f32) {
741 let physical_inset = logical_inset * self.cell_renderer.scale_factor;
742 self.cell_renderer.grid.egui_right_inset = physical_inset;
743 }
744}