1use super::CellRenderer;
2use crate::custom_shader_renderer::textures::ChannelTexture;
3use anyhow::Result;
4
5#[allow(dead_code)]
7pub(crate) struct PaneBackgroundEntry {
8 pub(crate) texture: wgpu::Texture,
9 pub(crate) view: wgpu::TextureView,
10 pub(crate) sampler: wgpu::Sampler,
11 pub(crate) width: u32,
12 pub(crate) height: u32,
13}
14
15impl CellRenderer {
16 pub(crate) fn load_background_image(&mut self, path: &str) -> Result<()> {
17 log::info!("Loading background image from: {}", path);
18 let img = image::open(path)
19 .map_err(|e| {
20 log::error!("Failed to open background image '{}': {}", path, e);
21 e
22 })?
23 .to_rgba8();
24 log::info!("Background image loaded: {}x{}", img.width(), img.height());
25 let (width, height) = img.dimensions();
26 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
27 label: Some("bg image"),
28 size: wgpu::Extent3d {
29 width,
30 height,
31 depth_or_array_layers: 1,
32 },
33 mip_level_count: 1,
34 sample_count: 1,
35 dimension: wgpu::TextureDimension::D2,
36 format: wgpu::TextureFormat::Rgba8UnormSrgb,
37 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
38 view_formats: &[],
39 });
40 self.queue.write_texture(
41 wgpu::TexelCopyTextureInfo {
42 texture: &texture,
43 mip_level: 0,
44 origin: wgpu::Origin3d::ZERO,
45 aspect: wgpu::TextureAspect::All,
46 },
47 &img,
48 wgpu::TexelCopyBufferLayout {
49 offset: 0,
50 bytes_per_row: Some(4 * width),
51 rows_per_image: Some(height),
52 },
53 wgpu::Extent3d {
54 width,
55 height,
56 depth_or_array_layers: 1,
57 },
58 );
59
60 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
61 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
62 mag_filter: wgpu::FilterMode::Linear,
63 min_filter: wgpu::FilterMode::Linear,
64 ..Default::default()
65 });
66
67 self.bg_image_bind_group =
68 Some(self.device.create_bind_group(&wgpu::BindGroupDescriptor {
69 label: Some("bg image bind group"),
70 layout: &self.bg_image_bind_group_layout,
71 entries: &[
72 wgpu::BindGroupEntry {
73 binding: 0,
74 resource: wgpu::BindingResource::TextureView(&view),
75 },
76 wgpu::BindGroupEntry {
77 binding: 1,
78 resource: wgpu::BindingResource::Sampler(&sampler),
79 },
80 wgpu::BindGroupEntry {
81 binding: 2,
82 resource: self.bg_image_uniform_buffer.as_entire_binding(),
83 },
84 ],
85 }));
86 self.bg_image_texture = Some(texture);
87 self.bg_image_width = width;
88 self.bg_image_height = height;
89 self.bg_is_solid_color = false; self.update_bg_image_uniforms();
91 Ok(())
92 }
93
94 pub(crate) fn update_bg_image_uniforms(&mut self) {
95 let mut data = [0u8; 48];
103
104 let w = self.config.width as f32;
105 let h = self.config.height as f32;
106
107 data[0..4].copy_from_slice(&(self.bg_image_width as f32).to_le_bytes());
109 data[4..8].copy_from_slice(&(self.bg_image_height as f32).to_le_bytes());
110
111 data[8..12].copy_from_slice(&w.to_le_bytes());
113 data[12..16].copy_from_slice(&h.to_le_bytes());
114
115 data[16..20].copy_from_slice(&(self.bg_image_mode as u32).to_le_bytes());
117
118 let effective_opacity = self.bg_image_opacity * self.window_opacity;
120 data[20..24].copy_from_slice(&effective_opacity.to_le_bytes());
121
122 data[32..36].copy_from_slice(&w.to_le_bytes());
127 data[36..40].copy_from_slice(&h.to_le_bytes());
128
129 self.queue
130 .write_buffer(&self.bg_image_uniform_buffer, 0, &data);
131 }
132
133 pub fn set_background_image(
134 &mut self,
135 path: Option<&str>,
136 mode: par_term_config::BackgroundImageMode,
137 opacity: f32,
138 ) {
139 self.bg_image_mode = mode;
140 self.bg_image_opacity = opacity;
141 if let Some(p) = path {
142 log::info!("Loading background image: {}", p);
143 if let Err(e) = self.load_background_image(p) {
144 log::error!("Failed to load background image '{}': {}", p, e);
145 }
146 } else {
148 self.bg_image_texture = None;
149 self.bg_image_bind_group = None;
150 self.bg_image_width = 0;
151 self.bg_image_height = 0;
152 self.bg_is_solid_color = false;
153 }
154 self.update_bg_image_uniforms();
155 }
156
157 #[allow(dead_code)]
158 pub fn update_background_image_opacity(&mut self, opacity: f32) {
159 self.bg_image_opacity = opacity;
160 self.update_bg_image_uniforms();
161 }
162
163 #[allow(dead_code)]
164 pub fn update_background_image_opacity_only(&mut self, opacity: f32) {
165 self.bg_image_opacity = opacity;
166 self.update_bg_image_uniforms();
167 }
168
169 pub fn get_background_as_channel_texture(&self) -> Option<ChannelTexture> {
175 let texture = self.bg_image_texture.as_ref()?;
176
177 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
179 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
180 mag_filter: wgpu::FilterMode::Linear,
181 min_filter: wgpu::FilterMode::Linear,
182 address_mode_u: wgpu::AddressMode::Repeat,
183 address_mode_v: wgpu::AddressMode::Repeat,
184 address_mode_w: wgpu::AddressMode::Repeat,
185 ..Default::default()
186 });
187
188 Some(ChannelTexture::from_view(
189 view,
190 sampler,
191 self.bg_image_width,
192 self.bg_image_height,
193 ))
194 }
195
196 #[allow(dead_code)]
198 pub fn has_background_image(&self) -> bool {
199 self.bg_image_texture.is_some()
200 }
201
202 pub fn is_solid_color_background(&self) -> bool {
204 self.bg_is_solid_color
205 }
206
207 pub fn solid_background_color(&self) -> [f32; 3] {
210 self.solid_bg_color
211 }
212
213 #[allow(dead_code)]
216 pub fn get_solid_color_as_clear(&self) -> Option<wgpu::Color> {
217 if self.bg_is_solid_color {
218 Some(wgpu::Color {
219 r: self.solid_bg_color[0] as f64 * self.window_opacity as f64,
220 g: self.solid_bg_color[1] as f64 * self.window_opacity as f64,
221 b: self.solid_bg_color[2] as f64 * self.window_opacity as f64,
222 a: self.window_opacity as f64,
223 })
224 } else {
225 None
226 }
227 }
228
229 pub fn create_solid_color_texture(&mut self, color: [u8; 3]) {
235 log::info!(
236 "[BACKGROUND] create_solid_color_texture: RGB({}, {}, {}) -> normalized ({:.3}, {:.3}, {:.3})",
237 color[0],
238 color[1],
239 color[2],
240 color[0] as f32 / 255.0,
241 color[1] as f32 / 255.0,
242 color[2] as f32 / 255.0
243 );
244 let size = 4u32; let mut pixels = Vec::with_capacity((size * size * 4) as usize);
246 for _ in 0..(size * size) {
247 pixels.push(color[0]);
248 pixels.push(color[1]);
249 pixels.push(color[2]);
250 pixels.push(255); }
252
253 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
254 label: Some("bg solid color"),
255 size: wgpu::Extent3d {
256 width: size,
257 height: size,
258 depth_or_array_layers: 1,
259 },
260 mip_level_count: 1,
261 sample_count: 1,
262 dimension: wgpu::TextureDimension::D2,
263 format: wgpu::TextureFormat::Rgba8UnormSrgb,
264 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
265 view_formats: &[],
266 });
267
268 self.queue.write_texture(
269 wgpu::TexelCopyTextureInfo {
270 texture: &texture,
271 mip_level: 0,
272 origin: wgpu::Origin3d::ZERO,
273 aspect: wgpu::TextureAspect::All,
274 },
275 &pixels,
276 wgpu::TexelCopyBufferLayout {
277 offset: 0,
278 bytes_per_row: Some(4 * size),
279 rows_per_image: Some(size),
280 },
281 wgpu::Extent3d {
282 width: size,
283 height: size,
284 depth_or_array_layers: 1,
285 },
286 );
287
288 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
289 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
290 mag_filter: wgpu::FilterMode::Linear,
291 min_filter: wgpu::FilterMode::Linear,
292 ..Default::default()
293 });
294
295 self.bg_image_bind_group =
296 Some(self.device.create_bind_group(&wgpu::BindGroupDescriptor {
297 label: Some("bg solid color bind group"),
298 layout: &self.bg_image_bind_group_layout,
299 entries: &[
300 wgpu::BindGroupEntry {
301 binding: 0,
302 resource: wgpu::BindingResource::TextureView(&view),
303 },
304 wgpu::BindGroupEntry {
305 binding: 1,
306 resource: wgpu::BindingResource::Sampler(&sampler),
307 },
308 wgpu::BindGroupEntry {
309 binding: 2,
310 resource: self.bg_image_uniform_buffer.as_entire_binding(),
311 },
312 ],
313 }));
314
315 self.bg_image_texture = Some(texture);
316 self.bg_image_width = size;
317 self.bg_image_height = size;
318 self.bg_image_mode = par_term_config::BackgroundImageMode::Stretch;
320 self.bg_image_opacity = 1.0;
322 self.bg_is_solid_color = true;
324 self.solid_bg_color = [
325 color[0] as f32 / 255.0,
326 color[1] as f32 / 255.0,
327 color[2] as f32 / 255.0,
328 ];
329 self.update_bg_image_uniforms();
330 }
331
332 pub fn get_solid_color_as_channel_texture(&self, color: [u8; 3]) -> ChannelTexture {
338 log::info!(
339 "get_solid_color_as_channel_texture: RGB({},{},{})",
340 color[0],
341 color[1],
342 color[2]
343 );
344 let size = 4u32;
345 let mut pixels = Vec::with_capacity((size * size * 4) as usize);
346 for _ in 0..(size * size) {
347 pixels.push(color[0]);
348 pixels.push(color[1]);
349 pixels.push(color[2]);
350 pixels.push(255); }
352
353 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
354 label: Some("solid color channel texture"),
355 size: wgpu::Extent3d {
356 width: size,
357 height: size,
358 depth_or_array_layers: 1,
359 },
360 mip_level_count: 1,
361 sample_count: 1,
362 dimension: wgpu::TextureDimension::D2,
363 format: wgpu::TextureFormat::Rgba8UnormSrgb,
364 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
365 view_formats: &[],
366 });
367
368 self.queue.write_texture(
369 wgpu::TexelCopyTextureInfo {
370 texture: &texture,
371 mip_level: 0,
372 origin: wgpu::Origin3d::ZERO,
373 aspect: wgpu::TextureAspect::All,
374 },
375 &pixels,
376 wgpu::TexelCopyBufferLayout {
377 offset: 0,
378 bytes_per_row: Some(4 * size),
379 rows_per_image: Some(size),
380 },
381 wgpu::Extent3d {
382 width: size,
383 height: size,
384 depth_or_array_layers: 1,
385 },
386 );
387
388 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
389 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
390 mag_filter: wgpu::FilterMode::Linear,
391 min_filter: wgpu::FilterMode::Linear,
392 address_mode_u: wgpu::AddressMode::Repeat,
393 address_mode_v: wgpu::AddressMode::Repeat,
394 address_mode_w: wgpu::AddressMode::Repeat,
395 ..Default::default()
396 });
397
398 ChannelTexture::from_view_and_texture(view, sampler, size, size, texture)
399 }
400
401 pub fn set_background(
406 &mut self,
407 mode: par_term_config::BackgroundMode,
408 color: [u8; 3],
409 image_path: Option<&str>,
410 image_mode: par_term_config::BackgroundImageMode,
411 image_opacity: f32,
412 image_enabled: bool,
413 ) {
414 log::info!(
415 "[BACKGROUND] set_background: mode={:?}, color=RGB({}, {}, {}), image_path={:?}",
416 mode,
417 color[0],
418 color[1],
419 color[2],
420 image_path
421 );
422 match mode {
423 par_term_config::BackgroundMode::Default => {
424 self.bg_image_texture = None;
426 self.bg_image_bind_group = None;
427 self.bg_image_width = 0;
428 self.bg_image_height = 0;
429 self.bg_is_solid_color = false;
430 }
431 par_term_config::BackgroundMode::Color => {
432 self.create_solid_color_texture(color);
434 }
435 par_term_config::BackgroundMode::Image => {
436 if image_enabled {
437 self.set_background_image(image_path, image_mode, image_opacity);
439 } else {
440 self.bg_image_texture = None;
442 self.bg_image_bind_group = None;
443 self.bg_image_width = 0;
444 self.bg_image_height = 0;
445 self.bg_is_solid_color = false;
446 }
447 }
448 }
449 }
450
451 pub(crate) fn load_pane_background(&mut self, path: &str) -> Result<bool> {
454 if self.pane_bg_cache.contains_key(path) {
455 return Ok(false);
456 }
457
458 let expanded = if let Some(rest) = path.strip_prefix("~/") {
460 if let Some(home) = dirs::home_dir() {
461 home.join(rest).to_string_lossy().to_string()
462 } else {
463 path.to_string()
464 }
465 } else {
466 path.to_string()
467 };
468
469 log::info!("Loading per-pane background image: {}", expanded);
470 let img = image::open(&expanded)
471 .map_err(|e| {
472 log::error!("Failed to open pane background image '{}': {}", path, e);
473 e
474 })?
475 .to_rgba8();
476
477 let (width, height) = img.dimensions();
478 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
479 label: Some("pane bg image"),
480 size: wgpu::Extent3d {
481 width,
482 height,
483 depth_or_array_layers: 1,
484 },
485 mip_level_count: 1,
486 sample_count: 1,
487 dimension: wgpu::TextureDimension::D2,
488 format: wgpu::TextureFormat::Rgba8UnormSrgb,
489 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
490 view_formats: &[],
491 });
492
493 self.queue.write_texture(
494 wgpu::TexelCopyTextureInfo {
495 texture: &texture,
496 mip_level: 0,
497 origin: wgpu::Origin3d::ZERO,
498 aspect: wgpu::TextureAspect::All,
499 },
500 &img,
501 wgpu::TexelCopyBufferLayout {
502 offset: 0,
503 bytes_per_row: Some(4 * width),
504 rows_per_image: Some(height),
505 },
506 wgpu::Extent3d {
507 width,
508 height,
509 depth_or_array_layers: 1,
510 },
511 );
512
513 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
514 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
515 mag_filter: wgpu::FilterMode::Linear,
516 min_filter: wgpu::FilterMode::Linear,
517 ..Default::default()
518 });
519
520 self.pane_bg_cache.insert(
521 path.to_string(),
522 super::background::PaneBackgroundEntry {
523 texture,
524 view,
525 sampler,
526 width,
527 height,
528 },
529 );
530
531 Ok(true)
532 }
533
534 #[allow(dead_code)]
536 pub(crate) fn evict_pane_background(&mut self, path: &str) {
537 self.pane_bg_cache.remove(path);
538 }
539
540 #[allow(dead_code)]
542 pub(crate) fn clear_pane_bg_cache(&mut self) {
543 self.pane_bg_cache.clear();
544 }
545
546 #[allow(clippy::too_many_arguments)]
550 pub(crate) fn create_pane_bg_bind_group(
551 &self,
552 entry: &super::background::PaneBackgroundEntry,
553 pane_x: f32,
554 pane_y: f32,
555 pane_width: f32,
556 pane_height: f32,
557 mode: par_term_config::BackgroundImageMode,
558 opacity: f32,
559 ) -> (wgpu::BindGroup, wgpu::Buffer) {
560 let mut data = [0u8; 48];
568 data[0..4].copy_from_slice(&(entry.width as f32).to_le_bytes());
570 data[4..8].copy_from_slice(&(entry.height as f32).to_le_bytes());
571 data[8..12].copy_from_slice(&pane_width.to_le_bytes());
573 data[12..16].copy_from_slice(&pane_height.to_le_bytes());
574 data[16..20].copy_from_slice(&(mode as u32).to_le_bytes());
576 let effective_opacity = opacity * self.window_opacity;
578 data[20..24].copy_from_slice(&effective_opacity.to_le_bytes());
579 data[24..28].copy_from_slice(&pane_x.to_le_bytes());
581 data[28..32].copy_from_slice(&pane_y.to_le_bytes());
582 let surface_w = self.config.width as f32;
584 let surface_h = self.config.height as f32;
585 data[32..36].copy_from_slice(&surface_w.to_le_bytes());
586 data[36..40].copy_from_slice(&surface_h.to_le_bytes());
587
588 let uniform_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
589 label: Some("pane bg uniform buffer"),
590 size: 48,
591 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
592 mapped_at_creation: false,
593 });
594 self.queue.write_buffer(&uniform_buffer, 0, &data);
595
596 let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
597 label: Some("pane bg bind group"),
598 layout: &self.bg_image_bind_group_layout,
599 entries: &[
600 wgpu::BindGroupEntry {
601 binding: 0,
602 resource: wgpu::BindingResource::TextureView(&entry.view),
603 },
604 wgpu::BindGroupEntry {
605 binding: 1,
606 resource: wgpu::BindingResource::Sampler(&entry.sampler),
607 },
608 wgpu::BindGroupEntry {
609 binding: 2,
610 resource: uniform_buffer.as_entire_binding(),
611 },
612 ],
613 });
614
615 (bind_group, uniform_buffer)
616 }
617}