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];
104
105 let w = self.config.width as f32;
106 let h = self.config.height as f32;
107
108 data[0..4].copy_from_slice(&(self.bg_image_width as f32).to_le_bytes());
110 data[4..8].copy_from_slice(&(self.bg_image_height as f32).to_le_bytes());
111
112 data[8..12].copy_from_slice(&w.to_le_bytes());
114 data[12..16].copy_from_slice(&h.to_le_bytes());
115
116 data[16..20].copy_from_slice(&(self.bg_image_mode as u32).to_le_bytes());
118
119 let effective_opacity = self.bg_image_opacity * self.window_opacity;
121 data[20..24].copy_from_slice(&effective_opacity.to_le_bytes());
122
123 data[32..36].copy_from_slice(&w.to_le_bytes());
128 data[36..40].copy_from_slice(&h.to_le_bytes());
129
130 self.queue
134 .write_buffer(&self.bg_image_uniform_buffer, 0, &data);
135 }
136
137 pub fn set_background_image(
138 &mut self,
139 path: Option<&str>,
140 mode: par_term_config::BackgroundImageMode,
141 opacity: f32,
142 ) {
143 self.bg_image_mode = mode;
144 self.bg_image_opacity = opacity;
145 if let Some(p) = path {
146 log::info!("Loading background image: {}", p);
147 if let Err(e) = self.load_background_image(p) {
148 log::error!("Failed to load background image '{}': {}", p, e);
149 }
150 } else {
152 self.bg_image_texture = None;
153 self.bg_image_bind_group = None;
154 self.bg_image_width = 0;
155 self.bg_image_height = 0;
156 self.bg_is_solid_color = false;
157 }
158 self.update_bg_image_uniforms();
159 }
160
161 #[allow(dead_code)]
162 pub fn update_background_image_opacity(&mut self, opacity: f32) {
163 self.bg_image_opacity = opacity;
164 self.update_bg_image_uniforms();
165 }
166
167 #[allow(dead_code)]
168 pub fn update_background_image_opacity_only(&mut self, opacity: f32) {
169 self.bg_image_opacity = opacity;
170 self.update_bg_image_uniforms();
171 }
172
173 pub fn get_background_as_channel_texture(&self) -> Option<ChannelTexture> {
179 let texture = self.bg_image_texture.as_ref()?;
180
181 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
183 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
184 mag_filter: wgpu::FilterMode::Linear,
185 min_filter: wgpu::FilterMode::Linear,
186 address_mode_u: wgpu::AddressMode::Repeat,
187 address_mode_v: wgpu::AddressMode::Repeat,
188 address_mode_w: wgpu::AddressMode::Repeat,
189 ..Default::default()
190 });
191
192 Some(ChannelTexture::from_view(
193 view,
194 sampler,
195 self.bg_image_width,
196 self.bg_image_height,
197 ))
198 }
199
200 #[allow(dead_code)]
202 pub fn has_background_image(&self) -> bool {
203 self.bg_image_texture.is_some()
204 }
205
206 pub fn is_solid_color_background(&self) -> bool {
208 self.bg_is_solid_color
209 }
210
211 pub fn solid_background_color(&self) -> [f32; 3] {
214 self.solid_bg_color
215 }
216
217 #[allow(dead_code)]
220 pub fn get_solid_color_as_clear(&self) -> Option<wgpu::Color> {
221 if self.bg_is_solid_color {
222 Some(wgpu::Color {
223 r: self.solid_bg_color[0] as f64 * self.window_opacity as f64,
224 g: self.solid_bg_color[1] as f64 * self.window_opacity as f64,
225 b: self.solid_bg_color[2] as f64 * self.window_opacity as f64,
226 a: self.window_opacity as f64,
227 })
228 } else {
229 None
230 }
231 }
232
233 pub fn create_solid_color_texture(&mut self, color: [u8; 3]) {
239 log::info!(
240 "[BACKGROUND] create_solid_color_texture: RGB({}, {}, {}) -> normalized ({:.3}, {:.3}, {:.3})",
241 color[0],
242 color[1],
243 color[2],
244 color[0] as f32 / 255.0,
245 color[1] as f32 / 255.0,
246 color[2] as f32 / 255.0
247 );
248 let size = 4u32; let mut pixels = Vec::with_capacity((size * size * 4) as usize);
250 for _ in 0..(size * size) {
251 pixels.push(color[0]);
252 pixels.push(color[1]);
253 pixels.push(color[2]);
254 pixels.push(255); }
256
257 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
258 label: Some("bg solid color"),
259 size: wgpu::Extent3d {
260 width: size,
261 height: size,
262 depth_or_array_layers: 1,
263 },
264 mip_level_count: 1,
265 sample_count: 1,
266 dimension: wgpu::TextureDimension::D2,
267 format: wgpu::TextureFormat::Rgba8UnormSrgb,
268 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
269 view_formats: &[],
270 });
271
272 self.queue.write_texture(
273 wgpu::TexelCopyTextureInfo {
274 texture: &texture,
275 mip_level: 0,
276 origin: wgpu::Origin3d::ZERO,
277 aspect: wgpu::TextureAspect::All,
278 },
279 &pixels,
280 wgpu::TexelCopyBufferLayout {
281 offset: 0,
282 bytes_per_row: Some(4 * size),
283 rows_per_image: Some(size),
284 },
285 wgpu::Extent3d {
286 width: size,
287 height: size,
288 depth_or_array_layers: 1,
289 },
290 );
291
292 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
293 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
294 mag_filter: wgpu::FilterMode::Linear,
295 min_filter: wgpu::FilterMode::Linear,
296 ..Default::default()
297 });
298
299 self.bg_image_bind_group =
300 Some(self.device.create_bind_group(&wgpu::BindGroupDescriptor {
301 label: Some("bg solid color bind group"),
302 layout: &self.bg_image_bind_group_layout,
303 entries: &[
304 wgpu::BindGroupEntry {
305 binding: 0,
306 resource: wgpu::BindingResource::TextureView(&view),
307 },
308 wgpu::BindGroupEntry {
309 binding: 1,
310 resource: wgpu::BindingResource::Sampler(&sampler),
311 },
312 wgpu::BindGroupEntry {
313 binding: 2,
314 resource: self.bg_image_uniform_buffer.as_entire_binding(),
315 },
316 ],
317 }));
318
319 self.bg_image_texture = Some(texture);
320 self.bg_image_width = size;
321 self.bg_image_height = size;
322 self.bg_image_mode = par_term_config::BackgroundImageMode::Stretch;
324 self.bg_image_opacity = 1.0;
326 self.bg_is_solid_color = true;
328 self.solid_bg_color = [
329 color[0] as f32 / 255.0,
330 color[1] as f32 / 255.0,
331 color[2] as f32 / 255.0,
332 ];
333 self.update_bg_image_uniforms();
334 }
335
336 pub fn get_solid_color_as_channel_texture(&self, color: [u8; 3]) -> ChannelTexture {
342 log::info!(
343 "get_solid_color_as_channel_texture: RGB({},{},{})",
344 color[0],
345 color[1],
346 color[2]
347 );
348 let size = 4u32;
349 let mut pixels = Vec::with_capacity((size * size * 4) as usize);
350 for _ in 0..(size * size) {
351 pixels.push(color[0]);
352 pixels.push(color[1]);
353 pixels.push(color[2]);
354 pixels.push(255); }
356
357 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
358 label: Some("solid color channel texture"),
359 size: wgpu::Extent3d {
360 width: size,
361 height: size,
362 depth_or_array_layers: 1,
363 },
364 mip_level_count: 1,
365 sample_count: 1,
366 dimension: wgpu::TextureDimension::D2,
367 format: wgpu::TextureFormat::Rgba8UnormSrgb,
368 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
369 view_formats: &[],
370 });
371
372 self.queue.write_texture(
373 wgpu::TexelCopyTextureInfo {
374 texture: &texture,
375 mip_level: 0,
376 origin: wgpu::Origin3d::ZERO,
377 aspect: wgpu::TextureAspect::All,
378 },
379 &pixels,
380 wgpu::TexelCopyBufferLayout {
381 offset: 0,
382 bytes_per_row: Some(4 * size),
383 rows_per_image: Some(size),
384 },
385 wgpu::Extent3d {
386 width: size,
387 height: size,
388 depth_or_array_layers: 1,
389 },
390 );
391
392 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
393 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
394 mag_filter: wgpu::FilterMode::Linear,
395 min_filter: wgpu::FilterMode::Linear,
396 address_mode_u: wgpu::AddressMode::Repeat,
397 address_mode_v: wgpu::AddressMode::Repeat,
398 address_mode_w: wgpu::AddressMode::Repeat,
399 ..Default::default()
400 });
401
402 ChannelTexture::from_view_and_texture(view, sampler, size, size, texture)
403 }
404
405 pub fn set_background(
410 &mut self,
411 mode: par_term_config::BackgroundMode,
412 color: [u8; 3],
413 image_path: Option<&str>,
414 image_mode: par_term_config::BackgroundImageMode,
415 image_opacity: f32,
416 image_enabled: bool,
417 ) {
418 log::info!(
419 "[BACKGROUND] set_background: mode={:?}, color=RGB({}, {}, {}), image_path={:?}",
420 mode,
421 color[0],
422 color[1],
423 color[2],
424 image_path
425 );
426 match mode {
427 par_term_config::BackgroundMode::Default => {
428 self.bg_image_texture = None;
430 self.bg_image_bind_group = None;
431 self.bg_image_width = 0;
432 self.bg_image_height = 0;
433 self.bg_is_solid_color = false;
434 }
435 par_term_config::BackgroundMode::Color => {
436 self.create_solid_color_texture(color);
438 }
439 par_term_config::BackgroundMode::Image => {
440 if image_enabled {
441 self.set_background_image(image_path, image_mode, image_opacity);
443 } else {
444 self.bg_image_texture = None;
446 self.bg_image_bind_group = None;
447 self.bg_image_width = 0;
448 self.bg_image_height = 0;
449 self.bg_is_solid_color = false;
450 }
451 }
452 }
453 }
454
455 pub(crate) fn load_pane_background(&mut self, path: &str) -> Result<bool> {
458 if self.pane_bg_cache.contains_key(path) {
459 return Ok(false);
460 }
461
462 let expanded = if let Some(rest) = path.strip_prefix("~/") {
464 if let Some(home) = dirs::home_dir() {
465 home.join(rest).to_string_lossy().to_string()
466 } else {
467 path.to_string()
468 }
469 } else {
470 path.to_string()
471 };
472
473 log::info!("Loading per-pane background image: {}", expanded);
474 let img = image::open(&expanded)
475 .map_err(|e| {
476 log::error!("Failed to open pane background image '{}': {}", path, e);
477 e
478 })?
479 .to_rgba8();
480
481 let (width, height) = img.dimensions();
482 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
483 label: Some("pane bg image"),
484 size: wgpu::Extent3d {
485 width,
486 height,
487 depth_or_array_layers: 1,
488 },
489 mip_level_count: 1,
490 sample_count: 1,
491 dimension: wgpu::TextureDimension::D2,
492 format: wgpu::TextureFormat::Rgba8UnormSrgb,
493 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
494 view_formats: &[],
495 });
496
497 self.queue.write_texture(
498 wgpu::TexelCopyTextureInfo {
499 texture: &texture,
500 mip_level: 0,
501 origin: wgpu::Origin3d::ZERO,
502 aspect: wgpu::TextureAspect::All,
503 },
504 &img,
505 wgpu::TexelCopyBufferLayout {
506 offset: 0,
507 bytes_per_row: Some(4 * width),
508 rows_per_image: Some(height),
509 },
510 wgpu::Extent3d {
511 width,
512 height,
513 depth_or_array_layers: 1,
514 },
515 );
516
517 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
518 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
519 mag_filter: wgpu::FilterMode::Linear,
520 min_filter: wgpu::FilterMode::Linear,
521 ..Default::default()
522 });
523
524 self.pane_bg_cache.insert(
525 path.to_string(),
526 super::background::PaneBackgroundEntry {
527 texture,
528 view,
529 sampler,
530 width,
531 height,
532 },
533 );
534
535 Ok(true)
536 }
537
538 #[allow(dead_code)]
540 pub(crate) fn evict_pane_background(&mut self, path: &str) {
541 self.pane_bg_cache.remove(path);
542 }
543
544 #[allow(dead_code)]
546 pub(crate) fn clear_pane_bg_cache(&mut self) {
547 self.pane_bg_cache.clear();
548 }
549
550 #[allow(clippy::too_many_arguments)]
554 pub(crate) fn create_pane_bg_bind_group(
555 &self,
556 entry: &super::background::PaneBackgroundEntry,
557 pane_x: f32,
558 pane_y: f32,
559 pane_width: f32,
560 pane_height: f32,
561 mode: par_term_config::BackgroundImageMode,
562 opacity: f32,
563 darken: f32,
564 ) -> (wgpu::BindGroup, wgpu::Buffer) {
565 let mut data = [0u8; 48];
574 data[0..4].copy_from_slice(&(entry.width as f32).to_le_bytes());
576 data[4..8].copy_from_slice(&(entry.height as f32).to_le_bytes());
577 data[8..12].copy_from_slice(&pane_width.to_le_bytes());
579 data[12..16].copy_from_slice(&pane_height.to_le_bytes());
580 data[16..20].copy_from_slice(&(mode as u32).to_le_bytes());
582 let effective_opacity = opacity * self.window_opacity;
584 data[20..24].copy_from_slice(&effective_opacity.to_le_bytes());
585 data[24..28].copy_from_slice(&pane_x.to_le_bytes());
587 data[28..32].copy_from_slice(&pane_y.to_le_bytes());
588 let surface_w = self.config.width as f32;
590 let surface_h = self.config.height as f32;
591 data[32..36].copy_from_slice(&surface_w.to_le_bytes());
592 data[36..40].copy_from_slice(&surface_h.to_le_bytes());
593 data[40..44].copy_from_slice(&darken.to_le_bytes());
595
596 let uniform_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
597 label: Some("pane bg uniform buffer"),
598 size: 48,
599 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
600 mapped_at_creation: false,
601 });
602 self.queue.write_buffer(&uniform_buffer, 0, &data);
603
604 let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
605 label: Some("pane bg bind group"),
606 layout: &self.bg_image_bind_group_layout,
607 entries: &[
608 wgpu::BindGroupEntry {
609 binding: 0,
610 resource: wgpu::BindingResource::TextureView(&entry.view),
611 },
612 wgpu::BindGroupEntry {
613 binding: 1,
614 resource: wgpu::BindingResource::Sampler(&entry.sampler),
615 },
616 wgpu::BindGroupEntry {
617 binding: 2,
618 resource: uniform_buffer.as_entire_binding(),
619 },
620 ],
621 });
622
623 (bind_group, uniform_buffer)
624 }
625}