pub(crate) mod builder; pub(crate) mod control; use std::sync::Arc; use glam::{Vec2, Vec4}; use vulkano::{ command_buffer::CommandBufferUsage, format::Format, image::{view::ImageView, ImageLayout}, }; use crate::{ backend::{ input::{Haptics, InteractionHandler, PointerHit}, overlay::{OverlayBackend, OverlayRenderer}, }, graphics::{WlxGraphics, WlxPass, WlxPipeline, WlxPipelineLegacy, BLEND_ALPHA}, state::AppState, }; const RES_DIVIDER: usize = 4; pub struct Rect { x: f32, y: f32, w: f32, h: f32, } pub struct CanvasData { pub data: D, pub width: usize, pub height: usize, graphics: Arc, pipeline_bg_color: Arc>, pipeline_fg_glyph: Arc>, pipeline_bg_sprite: Arc>, pipeline_hl_sprite: Arc>, pipeline_final: Arc>, } pub struct Canvas { controls: Vec>, canvas: CanvasData, hover_controls: [Option; 2], pressed_controls: [Option; 2], interact_map: Vec>, interact_stride: usize, interact_rows: usize, view_final: Arc, pass_fg: WlxPass, pass_bg: WlxPass, } impl Canvas { fn new( width: usize, height: usize, graphics: Arc, format: Format, data: D, ) -> anyhow::Result { let tex_fg = graphics.render_texture(width as _, height as _, format)?; let tex_bg = graphics.render_texture(width as _, height as _, format)?; let tex_final = graphics.render_texture(width as _, height as _, format)?; let view_fg = ImageView::new_default(tex_fg.clone())?; let view_bg = ImageView::new_default(tex_bg.clone())?; let view_final = ImageView::new_default(tex_final.clone())?; let Ok(shaders) = graphics.shared_shaders.read() else { anyhow::bail!("Failed to lock shared shaders for reading"); }; let pipeline_bg_color = graphics.create_pipeline( view_bg.clone(), shaders.get("vert_common").unwrap().clone(), // want panic shaders.get("frag_color").unwrap().clone(), // want panic format, Some(BLEND_ALPHA), )?; let pipeline_fg_glyph = graphics.create_pipeline( view_fg.clone(), shaders.get("vert_common").unwrap().clone(), // want panic shaders.get("frag_glyph").unwrap().clone(), // want panic format, Some(BLEND_ALPHA), )?; let pipeline_bg_sprite = graphics.create_pipeline( view_fg.clone(), shaders.get("vert_common").unwrap().clone(), // want panic shaders.get("frag_sprite2").unwrap().clone(), // want panic format, Some(BLEND_ALPHA), )?; let pipeline_hl_sprite = graphics.create_pipeline( view_fg.clone(), shaders.get("vert_common").unwrap().clone(), // want panic shaders.get("frag_sprite2_hl").unwrap().clone(), // want panic format, Some(BLEND_ALPHA), )?; let vertex_buffer = graphics.upload_verts(width as _, height as _, 0., 0., width as _, height as _)?; let pipeline_final = graphics.create_pipeline_with_layouts( view_final.clone(), shaders.get("vert_common").unwrap().clone(), // want panic shaders.get("frag_sprite").unwrap().clone(), // want panic format, Some(BLEND_ALPHA), ImageLayout::TransferSrcOptimal, ImageLayout::TransferSrcOptimal, )?; let set_fg = pipeline_final.uniform_sampler(0, view_fg.clone(), graphics.texture_filtering)?; let set_bg = pipeline_final.uniform_sampler(0, view_bg.clone(), graphics.texture_filtering)?; let pass_fg = pipeline_final.create_pass( [width as _, height as _], vertex_buffer.clone(), graphics.quad_indices.clone(), vec![set_fg], )?; let pass_bg = pipeline_final.create_pass( [width as _, height as _], vertex_buffer.clone(), graphics.quad_indices.clone(), vec![set_bg], )?; let stride = width / RES_DIVIDER; let rows = height / RES_DIVIDER; Ok(Self { canvas: CanvasData { data, width, height, graphics: graphics.clone(), pipeline_bg_color, pipeline_fg_glyph, pipeline_bg_sprite, pipeline_hl_sprite, pipeline_final, }, controls: Vec::new(), hover_controls: [None, None], pressed_controls: [None, None], interact_map: vec![None; stride * rows], interact_stride: stride, interact_rows: rows, view_final, pass_fg, pass_bg, }) } fn interactive_set_idx(&mut self, x: f32, y: f32, w: f32, h: f32, idx: usize) { let (x, y, w, h) = (x as usize, y as usize, w as usize, h as usize); let x_min = (x / RES_DIVIDER).max(0); let y_min = (y / RES_DIVIDER).max(0); let x_max = (x_min + (w / RES_DIVIDER)).min(self.interact_stride - 1); let y_max = (y_min + (h / RES_DIVIDER)).min(self.interact_rows - 1); for y in y_min..y_max { for x in x_min..x_max { self.interact_map[y * self.interact_stride + x] = Some(idx as u16); } } } fn interactive_get_idx(&self, uv: Vec2) -> Option { let x = (uv.x * self.canvas.width as f32) as usize; let y = (uv.y * self.canvas.height as f32) as usize; let x = (x / RES_DIVIDER).max(0).min(self.interact_stride - 1); let y = (y / RES_DIVIDER).max(0).min(self.interact_rows - 1); self.interact_map[y * self.interact_stride + x].map(|x| x as usize) } fn render_bg(&mut self, app: &mut AppState) -> anyhow::Result<()> { let mut cmd_buffer = self .canvas .graphics .create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; cmd_buffer.begin_render_pass(&self.canvas.pipeline_bg_color)?; for c in self.controls.iter_mut() { if let Some(fun) = c.on_render_bg { fun(c, &self.canvas, app, &mut cmd_buffer)?; } } cmd_buffer.end_render_pass()?; cmd_buffer.build_and_execute_now() } fn render_fg(&mut self, app: &mut AppState) -> anyhow::Result<()> { let mut cmd_buffer = self .canvas .graphics .create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; cmd_buffer.begin_render_pass(&self.canvas.pipeline_fg_glyph)?; for c in self.controls.iter_mut() { if let Some(fun) = c.on_render_fg { fun(c, &self.canvas, app, &mut cmd_buffer)?; } } cmd_buffer.end_render_pass()?; cmd_buffer.build_and_execute_now() } } impl InteractionHandler for Canvas { fn on_left(&mut self, _app: &mut AppState, pointer: usize) { self.hover_controls[pointer] = None; } fn on_hover(&mut self, _app: &mut AppState, hit: &PointerHit) -> Option { let old = self.hover_controls[hit.pointer]; if let Some(i) = self.interactive_get_idx(hit.uv) { self.hover_controls[hit.pointer] = Some(i); } else { self.hover_controls[hit.pointer] = None; } if old != self.hover_controls[hit.pointer] { Some(Haptics { intensity: 0.1, duration: 0.01, frequency: 5.0, }) } else { None } } fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool) { let idx = if pressed { self.interactive_get_idx(hit.uv) } else { self.pressed_controls[hit.pointer] }; if let Some(idx) = idx { let c = &mut self.controls[idx]; if pressed { if let Some(ref mut f) = c.on_press { self.pressed_controls[hit.pointer] = Some(idx); f(c, &mut self.canvas.data, app, hit.mode); } } else if let Some(ref mut f) = c.on_release { self.pressed_controls[hit.pointer] = None; f(c, &mut self.canvas.data, app); } } } fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta: f32) { let idx = self.hover_controls[hit.pointer]; if let Some(idx) = idx { let c = &mut self.controls[idx]; if let Some(ref mut f) = c.on_scroll { f(c, &mut self.canvas.data, app, delta); } } } } impl OverlayRenderer for Canvas { fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> { self.render_bg(app)?; self.render_fg(app) } fn pause(&mut self, _app: &mut AppState) -> anyhow::Result<()> { Ok(()) } fn resume(&mut self, _app: &mut AppState) -> anyhow::Result<()> { Ok(()) } fn render(&mut self, app: &mut AppState) -> anyhow::Result<()> { let mut dirty = false; for c in self.controls.iter_mut() { if let Some(fun) = c.on_update { fun(c, &mut self.canvas.data, app); } if c.dirty { dirty = true; c.dirty = false; } } if dirty { self.render_bg(app)?; self.render_fg(app)?; } /* let image = self.view_final.image().clone(); if self.first_render { self.first_render = false; } else { self.canvas .graphics .transition_layout( image.clone(), ImageLayout::TransferSrcOptimal, ImageLayout::ColorAttachmentOptimal, ) .wait(None) .unwrap(); } */ let mut cmd_buffer = self .canvas .graphics .create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; cmd_buffer.begin_render_pass(&self.canvas.pipeline_final)?; // static background cmd_buffer.run_ref(&self.pass_bg)?; for (i, c) in self.controls.iter_mut().enumerate() { if let Some(render) = c.on_render_hl { if let Some(test) = c.test_highlight { if let Some(hl_color) = test(c, &mut self.canvas.data, app) { render(c, &self.canvas, app, &mut cmd_buffer, hl_color)?; } } if self.hover_controls.contains(&Some(i)) { render( c, &self.canvas, app, &mut cmd_buffer, Vec4::new(1., 1., 1., 0.3), )?; } } } // mostly static text cmd_buffer.run_ref(&self.pass_fg)?; cmd_buffer.end_render_pass()?; cmd_buffer.build_and_execute_now() /* self.canvas .graphics .transition_layout( image, ImageLayout::ColorAttachmentOptimal, ImageLayout::TransferSrcOptimal, ) .wait(None) .unwrap(); */ } fn view(&mut self) -> Option> { Some(self.view_final.clone()) } fn extent(&mut self) -> Option<[u32; 3]> { Some(self.view_final.image().extent()) } } impl OverlayBackend for Canvas { fn set_renderer(&mut self, _renderer: Box) {} fn set_interaction(&mut self, _interaction: Box) {} }