par_term_render/cell_renderer/
layout.rs1use super::{BackgroundInstance, Cell, CellRenderer, RowCacheEntry, TextInstance, pipeline};
2
3pub(crate) struct GridLayout {
5 pub(crate) cols: usize,
6 pub(crate) rows: usize,
7 pub(crate) cell_width: f32,
8 pub(crate) cell_height: f32,
9 pub(crate) window_padding: f32,
10 pub(crate) content_offset_y: f32,
13 pub(crate) content_offset_x: f32,
16 pub(crate) content_inset_bottom: f32,
19 pub(crate) content_inset_right: f32,
22 pub(crate) egui_bottom_inset: f32,
26 pub(crate) egui_right_inset: f32,
30}
31
32impl CellRenderer {
33 pub fn cell_width(&self) -> f32 {
34 self.grid.cell_width
35 }
36 pub fn cell_height(&self) -> f32 {
37 self.grid.cell_height
38 }
39 pub fn window_padding(&self) -> f32 {
40 self.grid.window_padding
41 }
42 pub fn content_offset_y(&self) -> f32 {
43 self.grid.content_offset_y
44 }
45 pub fn set_content_offset_y(&mut self, offset: f32) -> Option<(usize, usize)> {
48 if (self.grid.content_offset_y - offset).abs() > f32::EPSILON {
49 self.grid.content_offset_y = offset;
50 let size = (self.config.width, self.config.height);
51 return Some(self.resize(size.0, size.1));
52 }
53 None
54 }
55 pub fn content_offset_x(&self) -> f32 {
56 self.grid.content_offset_x
57 }
58 pub fn set_content_offset_x(&mut self, offset: f32) -> Option<(usize, usize)> {
61 if (self.grid.content_offset_x - offset).abs() > f32::EPSILON {
62 self.grid.content_offset_x = offset;
63 let size = (self.config.width, self.config.height);
64 return Some(self.resize(size.0, size.1));
65 }
66 None
67 }
68 pub fn content_inset_bottom(&self) -> f32 {
69 self.grid.content_inset_bottom
70 }
71 pub fn set_content_inset_bottom(&mut self, inset: f32) -> Option<(usize, usize)> {
74 if (self.grid.content_inset_bottom - inset).abs() > f32::EPSILON {
75 self.grid.content_inset_bottom = inset;
76 let size = (self.config.width, self.config.height);
77 return Some(self.resize(size.0, size.1));
78 }
79 None
80 }
81 pub fn content_inset_right(&self) -> f32 {
82 self.grid.content_inset_right
83 }
84 pub fn set_content_inset_right(&mut self, inset: f32) -> Option<(usize, usize)> {
87 if (self.grid.content_inset_right - inset).abs() > f32::EPSILON {
88 log::info!(
89 "[SCROLLBAR] set_content_inset_right: {:.1} -> {:.1} (physical px)",
90 self.grid.content_inset_right,
91 inset
92 );
93 self.grid.content_inset_right = inset;
94 let size = (self.config.width, self.config.height);
95 return Some(self.resize(size.0, size.1));
96 }
97 None
98 }
99 pub fn grid_size(&self) -> (usize, usize) {
100 (self.grid.cols, self.grid.rows)
101 }
102
103 pub fn resize(&mut self, width: u32, height: u32) -> (usize, usize) {
104 if width == 0 || height == 0 {
105 return (self.grid.cols, self.grid.rows);
106 }
107 let (width, height) = super::surface::clamp_surface_extent(
108 width,
109 height,
110 self.device.limits().max_texture_dimension_2d,
111 );
112 if self.config.width != width || self.config.height != height {
118 log::info!(
119 "Configuring surface {}x{} (was {}x{})",
120 width,
121 height,
122 self.config.width,
123 self.config.height
124 );
125 self.config.width = width;
126 self.config.height = height;
127 self.configure_surface();
128 } else {
129 log::debug!(
130 "Surface extent unchanged ({}x{}), skipping configure",
131 width,
132 height
133 );
134 }
135
136 let available_width = (width as f32
142 - self.grid.window_padding * 2.0
143 - self.grid.content_offset_x
144 - self.grid.content_inset_right)
145 .max(0.0);
146 let available_height = (height as f32
147 - self.grid.window_padding
148 - self.grid.content_offset_y
149 - self.grid.content_inset_bottom
150 - self.grid.egui_bottom_inset)
151 .max(0.0);
152 let new_cols = (available_width / self.grid.cell_width).max(1.0) as usize;
153 let new_rows = (available_height / self.grid.cell_height).max(1.0) as usize;
154
155 if new_cols != self.grid.cols || new_rows != self.grid.rows {
156 self.grid.cols = new_cols;
157 self.grid.rows = new_rows;
158 self.cells = vec![Cell::default(); self.grid.cols * self.grid.rows];
159 self.dirty_rows = vec![true; self.grid.rows];
160 self.row_cache = vec![None::<RowCacheEntry>; self.grid.rows];
161 self.recreate_instance_buffers();
162 }
163
164 self.update_bg_image_uniforms(None);
165 (self.grid.cols, self.grid.rows)
166 }
167
168 pub fn chrome_overhead(&self) -> (f32, f32) {
178 let chrome_x = self.grid.window_padding * 2.0
179 + self.grid.content_offset_x
180 + self.grid.content_inset_right;
181 let chrome_y = self.grid.window_padding
182 + self.grid.content_offset_y
183 + self.grid.content_inset_bottom
184 + self.grid.egui_bottom_inset;
185 (chrome_x, chrome_y)
186 }
187
188 pub(crate) fn single_grid_instance_capacity(&self) -> (usize, usize) {
191 (
192 super::render::SingleGridLayout::new(self.grid.cols, self.grid.rows).bg_instances(),
193 self.grid.cols * self.grid.rows * super::TEXT_INSTANCES_PER_CELL,
194 )
195 }
196
197 pub(crate) fn recreate_instance_buffers(&mut self) {
198 let (bg, text) = self.single_grid_instance_capacity();
199 self.allocate_instance_buffers(bg, text);
200 }
201
202 pub(crate) fn allocate_instance_buffers(&mut self, max_bg: usize, max_text: usize) {
209 if max_bg > self.buffers.max_bg_instances || max_text > self.buffers.max_text_instances {
210 let (bg_buf, text_buf) =
211 pipeline::create_instance_buffers(&self.device, max_bg, max_text);
212 self.buffers.bg_instance_buffer = bg_buf;
213 self.buffers.text_instance_buffer = text_buf;
214 }
215 self.buffers.max_bg_instances = max_bg;
216 self.buffers.max_text_instances = max_text;
217 self.buffers.actual_bg_instances = 0;
219 self.buffers.actual_text_instances = 0;
220 self.buffers.pane_bg_cursor = 0;
221 self.buffers.pane_text_cursor = 0;
222 self.buffers.overflow_reported = false;
225
226 if self.bg_instances.capacity() >= max_bg {
229 self.bg_instances.resize(max_bg, BackgroundInstance::BLANK);
230 } else {
231 self.bg_instances = vec![
232 BackgroundInstance {
233 position: [0.0, 0.0],
234 size: [0.0, 0.0],
235 color: [0.0, 0.0, 0.0, 0.0],
236 };
237 max_bg
238 ];
239 }
240 if self.text_instances.capacity() >= max_text {
241 self.text_instances.resize(
242 max_text,
243 TextInstance {
244 position: [0.0, 0.0],
245 size: [0.0, 0.0],
246 tex_offset: [0.0, 0.0],
247 tex_size: [0.0, 0.0],
248 color: [0.0, 0.0, 0.0, 0.0],
249 is_colored: 0,
250 },
251 );
252 } else {
253 self.text_instances = vec![
254 TextInstance {
255 position: [0.0, 0.0],
256 size: [0.0, 0.0],
257 tex_offset: [0.0, 0.0],
258 tex_size: [0.0, 0.0],
259 color: [0.0, 0.0, 0.0, 0.0],
260 is_colored: 0,
261 };
262 max_text
263 ];
264 }
265
266 self.scratch_row_bg.reserve(
268 self.grid
269 .cols
270 .saturating_sub(self.scratch_row_bg.capacity()),
271 );
272 self.scratch_row_text
273 .reserve((self.grid.cols * 2).saturating_sub(self.scratch_row_text.capacity()));
274 }
275
276 pub fn update_scale_factor(&mut self, scale_factor: f64) {
279 let new_scale = scale_factor as f32;
280
281 if (self.scale_factor - new_scale).abs() < f32::EPSILON {
283 return;
284 }
285
286 log::info!(
287 "Recalculating font metrics for scale factor change: {} -> {}",
288 self.scale_factor,
289 new_scale
290 );
291
292 self.scale_factor = new_scale;
293
294 let platform_dpi = if cfg!(target_os = "macos") {
296 crate::cell_renderer::MACOS_PLATFORM_DPI
297 } else {
298 crate::cell_renderer::DEFAULT_PLATFORM_DPI
299 };
300 let base_font_pixels =
301 self.font.base_font_size * platform_dpi / crate::cell_renderer::FONT_REFERENCE_DPI;
302 self.font.font_size_pixels = (base_font_pixels * new_scale).max(1.0);
303
304 let (font_ascent, font_descent, font_leading, char_advance) = {
306 let primary_font = self.font_manager.get_font(0).expect(
307 "Primary font at index 0 must exist in FontManager when updating scale factor",
308 );
309 let metrics = primary_font.metrics(&[]);
310 let scale = self.font.font_size_pixels / metrics.units_per_em as f32;
311 let glyph_id = primary_font.charmap().map('m');
312 let advance = primary_font.glyph_metrics(&[]).advance_width(glyph_id) * scale;
313 (
314 metrics.ascent * scale,
315 metrics.descent * scale,
316 metrics.leading * scale,
317 advance,
318 )
319 };
320
321 self.font.font_ascent = font_ascent;
322 self.font.font_descent = font_descent;
323 self.font.font_leading = font_leading;
324 self.font.char_advance = char_advance;
325
326 let natural_line_height = font_ascent + font_descent + font_leading;
328 self.grid.cell_height = (natural_line_height * self.font.line_spacing)
329 .max(1.0)
330 .round();
331 self.grid.cell_width = (char_advance * self.font.char_spacing).max(1.0).round();
332
333 log::info!(
334 "New cell dimensions: {}x{} (font_size_pixels: {})",
335 self.grid.cell_width,
336 self.grid.cell_height,
337 self.font.font_size_pixels
338 );
339
340 self.clear_glyph_cache();
342
343 self.dirty_rows.fill(true);
345 }
346
347 pub fn update_window_padding(&mut self, padding: f32) -> Option<(usize, usize)> {
348 if (self.grid.window_padding - padding).abs() > f32::EPSILON {
349 self.grid.window_padding = padding;
350 let size = (self.config.width, self.config.height);
351 return Some(self.resize(size.0, size.1));
352 }
353 None
354 }
355}