Skip to main content

par_term_render/renderer/
layout.rs

1use winit::dpi::PhysicalSize;
2
3use super::Renderer;
4
5// Layout and sizing accessors — simple getters/setters for grid geometry, padding,
6// and content offsets. Kept as the sibling of resize.rs since all of these deal
7// with the spatial layout of the renderer.
8impl Renderer {
9    /// Get the current size
10    pub fn size(&self) -> PhysicalSize<u32> {
11        self.size
12    }
13
14    /// Get the current grid dimensions (columns, rows)
15    pub fn grid_size(&self) -> (usize, usize) {
16        self.cell_renderer.grid_size()
17    }
18
19    /// Get cell width in pixels
20    pub fn cell_width(&self) -> f32 {
21        self.cell_renderer.cell_width()
22    }
23
24    /// Get cell height in pixels
25    pub fn cell_height(&self) -> f32 {
26        self.cell_renderer.cell_height()
27    }
28
29    /// Get window padding in physical pixels (scaled by DPI)
30    pub fn window_padding(&self) -> f32 {
31        self.cell_renderer.window_padding()
32    }
33
34    /// Get the scrollbar width in physical pixels
35    pub fn scrollbar_width(&self) -> f32 {
36        self.cell_renderer.scrollbar.width()
37    }
38
39    /// Returns total non-terminal pixel overhead as (horizontal_px, vertical_px).
40    /// See `CellRenderer::chrome_overhead` for details.
41    pub fn chrome_overhead(&self) -> (f32, f32) {
42        self.cell_renderer.chrome_overhead()
43    }
44
45    /// Get the vertical content offset in physical pixels (e.g., tab bar height scaled by DPI)
46    pub fn content_offset_y(&self) -> f32 {
47        self.cell_renderer.content_offset_y()
48    }
49
50    /// Get the display scale factor (e.g., 2.0 on Retina displays)
51    pub fn scale_factor(&self) -> f32 {
52        self.cell_renderer.scale_factor
53    }
54
55    /// Set the vertical content offset (e.g., tab bar height) in logical pixels.
56    /// The offset is scaled by the display scale factor to physical pixels internally,
57    /// since the cell renderer works in physical pixel coordinates while egui (tab bar)
58    /// uses logical pixels.
59    /// Returns Some((cols, rows)) if grid size changed, None otherwise.
60    pub fn set_content_offset_y(&mut self, logical_offset: f32) -> Option<(usize, usize)> {
61        // Scale from logical pixels (egui/config) to physical pixels (wgpu surface)
62        let physical_offset = logical_offset * self.cell_renderer.scale_factor;
63        let result = self.cell_renderer.set_content_offset_y(physical_offset);
64        // Always update graphics renderer offset, even if grid size didn't change
65        self.graphics_renderer.set_content_offset_y(physical_offset);
66        // Update custom shader renderer content offset
67        if let Some(ref mut custom_shader) = self.custom_shader_renderer {
68            custom_shader.set_content_offset_y(physical_offset);
69        }
70        // Update cursor shader renderer content offset
71        if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
72            cursor_shader.set_content_offset_y(physical_offset);
73        }
74        if result.is_some() {
75            self.dirty = true;
76        }
77        result
78    }
79
80    /// Get the horizontal content offset in physical pixels
81    pub fn content_offset_x(&self) -> f32 {
82        self.cell_renderer.content_offset_x()
83    }
84
85    /// Set the horizontal content offset (e.g., tab bar on left) in logical pixels.
86    /// Returns Some((cols, rows)) if grid size changed, None otherwise.
87    pub fn set_content_offset_x(&mut self, logical_offset: f32) -> Option<(usize, usize)> {
88        let physical_offset = logical_offset * self.cell_renderer.scale_factor;
89        let result = self.cell_renderer.set_content_offset_x(physical_offset);
90        self.graphics_renderer.set_content_offset_x(physical_offset);
91        if let Some(ref mut custom_shader) = self.custom_shader_renderer {
92            custom_shader.set_content_offset_x(physical_offset);
93        }
94        if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
95            cursor_shader.set_content_offset_x(physical_offset);
96        }
97        if result.is_some() {
98            self.dirty = true;
99        }
100        result
101    }
102
103    /// Get the bottom content inset in physical pixels
104    pub fn content_inset_bottom(&self) -> f32 {
105        self.cell_renderer.content_inset_bottom()
106    }
107
108    /// Get the right content inset in physical pixels
109    pub fn content_inset_right(&self) -> f32 {
110        self.cell_renderer.content_inset_right()
111    }
112
113    /// Set the bottom content inset (e.g., tab bar at bottom) in logical pixels.
114    /// Returns Some((cols, rows)) if grid size changed, None otherwise.
115    pub fn set_content_inset_bottom(&mut self, logical_inset: f32) -> Option<(usize, usize)> {
116        let physical_inset = logical_inset * self.cell_renderer.scale_factor;
117        let result = self.cell_renderer.set_content_inset_bottom(physical_inset);
118        if result.is_some() {
119            self.dirty = true;
120            // Invalidate the scrollbar cache — the track height depends on
121            // the bottom inset, so the scrollbar must be repositioned.
122            self.last_scrollbar_state = (usize::MAX, 0, 0, 0, 0, 0, 0, 0, 0, 0);
123        }
124        result
125    }
126
127    /// Set the right content inset (e.g., AI Inspector panel) in logical pixels.
128    /// Returns Some((cols, rows)) if grid size changed, None otherwise.
129    pub fn set_content_inset_right(&mut self, logical_inset: f32) -> Option<(usize, usize)> {
130        let physical_inset = logical_inset * self.cell_renderer.scale_factor;
131        let result = self.cell_renderer.set_content_inset_right(physical_inset);
132
133        // Also update custom shader renderer to exclude panel area from effects
134        if let Some(ref mut custom_shader) = self.custom_shader_renderer {
135            custom_shader.set_content_inset_right(physical_inset);
136        }
137        // Also update cursor shader renderer
138        if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
139            cursor_shader.set_content_inset_right(physical_inset);
140        }
141
142        if result.is_some() {
143            self.dirty = true;
144            // Invalidate the scrollbar cache so the next update_scrollbar()
145            // repositions the scrollbar at the new right inset. Without this,
146            // the cache guard sees the same (scroll_offset, visible_lines,
147            // total_lines) tuple and skips the GPU upload, leaving the
148            // scrollbar stuck at the old position.
149            self.last_scrollbar_state = (usize::MAX, 0, 0, 0, 0, 0, 0, 0, 0, 0);
150        }
151        result
152    }
153
154    /// Set the additional bottom inset from egui panels (status bar, tmux bar).
155    ///
156    /// This inset reduces the terminal grid height so content does not render
157    /// behind the status bar. Also affects scrollbar bounds.
158    /// Returns `Some((cols, rows))` if the grid was resized.
159    pub fn set_egui_bottom_inset(&mut self, logical_inset: f32) -> Option<(usize, usize)> {
160        let physical_inset = logical_inset * self.cell_renderer.scale_factor;
161        if (self.cell_renderer.grid.egui_bottom_inset - physical_inset).abs() > f32::EPSILON {
162            self.cell_renderer.grid.egui_bottom_inset = physical_inset;
163            let (w, h) = (
164                self.cell_renderer.config.width,
165                self.cell_renderer.config.height,
166            );
167            return Some(self.cell_renderer.resize(w, h));
168        }
169        None
170    }
171
172    /// Set the additional right inset from egui panels (AI Inspector).
173    ///
174    /// This inset is added to `content_inset_right` for scrollbar bounds only.
175    /// egui panels already claim space before wgpu rendering, so this doesn't
176    /// affect the terminal grid sizing.
177    pub fn set_egui_right_inset(&mut self, logical_inset: f32) {
178        let physical_inset = logical_inset * self.cell_renderer.scale_factor;
179        if (self.cell_renderer.grid.egui_right_inset - physical_inset).abs() <= f32::EPSILON {
180            return;
181        }
182        self.cell_renderer.grid.egui_right_inset = physical_inset;
183        // `update_scrollbar` adds this inset to `content_inset_right`, but its
184        // cached tuple does not contain it, so the next call would skip the
185        // upload and strand the scrollbar under the panel. Same reasoning as
186        // `set_content_inset_right`.
187        self.last_scrollbar_state = (usize::MAX, 0, 0, 0, 0, 0, 0, 0, 0, 0);
188        self.dirty = true;
189    }
190}