use std::sync::Arc; use glam::{Vec2, Vec3}; use vulkano::{ command_buffer::CommandBufferUsage, format::Format, image::{sampler::Filter, view::ImageView, ImageLayout}, }; use crate::{ backend::{ input::{InteractionHandler, PointerHit}, overlay::{OverlayBackend, OverlayRenderer}, }, graphics::{WlxCommandBuffer, WlxGraphics, WlxPass, WlxPipeline, WlxPipelineLegacy}, state::AppState, }; pub mod font; const RES_DIVIDER: usize = 4; struct Rect { x: f32, y: f32, w: f32, h: f32, } // Parses a color from a HTML hex string pub fn color_parse(html_hex: &str) -> Vec3 { let mut color = Vec3::ZERO; color.x = u8::from_str_radix(&html_hex[1..3], 16).unwrap() as f32 / 255.; color.y = u8::from_str_radix(&html_hex[3..5], 16).unwrap() as f32 / 255.; color.z = u8::from_str_radix(&html_hex[5..7], 16).unwrap() as f32 / 255.; color } pub struct CanvasBuilder { canvas: Canvas, pub fg_color: Vec3, pub bg_color: Vec3, pub font_size: isize, } impl CanvasBuilder { pub fn new( width: usize, height: usize, graphics: Arc, format: Format, data: D, ) -> Self { Self { canvas: Canvas::new(width, height, graphics, format, data), bg_color: Vec3::ZERO, fg_color: Vec3::ONE, font_size: 16, } } pub fn build(self) -> Canvas { self.canvas } // Creates a panel with bg_color inherited from the canvas pub fn panel(&mut self, x: f32, y: f32, w: f32, h: f32) -> &mut Control { let idx = self.canvas.controls.len(); self.canvas.controls.push(Control { rect: Rect { x, y, w, h }, bg_color: self.bg_color, on_render_bg: Some(Control::render_rect), ..Control::new() }); &mut self.canvas.controls[idx] } // Creates a label with fg_color, font_size inherited from the canvas pub fn label(&mut self, x: f32, y: f32, w: f32, h: f32, text: Arc) -> &mut Control { let idx = self.canvas.controls.len(); self.canvas.controls.push(Control { rect: Rect { x, y, w, h }, text, fg_color: self.fg_color, size: self.font_size, on_render_fg: Some(Control::render_text), ..Control::new() }); &mut self.canvas.controls[idx] } // Creates a label with fg_color, font_size inherited from the canvas #[allow(dead_code)] pub fn label_centered( &mut self, x: f32, y: f32, w: f32, h: f32, text: Arc, ) -> &mut Control { let idx = self.canvas.controls.len(); self.canvas.controls.push(Control { rect: Rect { x, y, w, h }, text, fg_color: self.fg_color, size: self.font_size, on_render_fg: Some(Control::render_text_centered), ..Control::new() }); &mut self.canvas.controls[idx] } // Creates a button with fg_color, bg_color, font_size inherited from the canvas pub fn button(&mut self, x: f32, y: f32, w: f32, h: f32, text: Arc) -> &mut Control { let idx = self.canvas.controls.len(); self.canvas.interactive_set_idx(x, y, w, h, idx); self.canvas.controls.push(Control { rect: Rect { x, y, w, h }, text, fg_color: self.fg_color, bg_color: self.bg_color, size: self.font_size, on_render_bg: Some(Control::render_rect), on_render_fg: Some(Control::render_text_centered), on_render_hl: Some(Control::render_highlight), ..Control::new() }); &mut self.canvas.controls[idx] } pub fn key_button( &mut self, x: f32, y: f32, w: f32, h: f32, label: &Vec, ) -> &mut Control { let idx = self.canvas.controls.len(); self.canvas.interactive_set_idx(x, y, w, h, idx); self.canvas.controls.push(Control { rect: Rect { x, y, w, h }, bg_color: self.bg_color, on_render_bg: Some(Control::render_rect), on_render_hl: Some(Control::render_highlight), ..Control::new() }); for (i, item) in label.iter().enumerate().take(label.len().min(2)) { self.canvas.controls.push(Control { rect: if i == 0 { Rect { x: x + 4., y: y + (self.font_size as f32) + 4., w, h, } } else { Rect { x: x + w * 0.5, y: y + h - (self.font_size as f32) + 4., w, h, } }, text: Arc::from(item.as_str()), fg_color: self.fg_color, size: self.font_size, on_render_fg: Some(Control::render_text), ..Control::new() }); } &mut self.canvas.controls[idx] } } pub struct CanvasData { pub data: D, pub width: usize, pub height: usize, graphics: Arc, pipeline_bg_color: Arc>, pipeline_fg_glyph: 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, ) -> Self { 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()).unwrap(); let view_bg = ImageView::new_default(tex_bg.clone()).unwrap(); let view_final = ImageView::new_default(tex_final.clone()).unwrap(); let shaders = graphics.shared_shaders.read().unwrap(); let pipeline_bg_color = graphics.create_pipeline( view_bg.clone(), shaders.get("vert_common").unwrap().clone(), shaders.get("frag_color").unwrap().clone(), format, ); let pipeline_fg_glyph = graphics.create_pipeline( view_fg.clone(), shaders.get("vert_common").unwrap().clone(), shaders.get("frag_glyph").unwrap().clone(), format, ); 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(), shaders.get("frag_sprite").unwrap().clone(), format, ImageLayout::TransferSrcOptimal, ImageLayout::TransferSrcOptimal, ); let set_fg = pipeline_final.uniform_sampler(0, view_fg.clone(), Filter::Linear); let set_bg = pipeline_final.uniform_sampler(0, view_bg.clone(), Filter::Linear); 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; Self { canvas: CanvasData { data, width, height, graphics: graphics.clone(), pipeline_bg_color, pipeline_fg_glyph, 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 u8); } } } 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) { 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) { 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) { if let Some(i) = self.interactive_get_idx(hit.uv) { self.hover_controls[hit.pointer] = Some(i); } else { self.hover_controls[hit.pointer] = 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); } } 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) {} } impl OverlayRenderer for Canvas { fn init(&mut self, app: &mut AppState) { self.render_bg(app); self.render_fg(app); } fn pause(&mut self, _app: &mut AppState) {} fn resume(&mut self, _app: &mut AppState) {} fn render(&mut self, app: &mut AppState) { 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 test(c, &mut self.canvas.data, app) { render(c, &self.canvas, app, &mut cmd_buffer, true); } } if self.hover_controls.contains(&Some(i)) { render(c, &self.canvas, app, &mut cmd_buffer, false); } } } // 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(&self) -> [u32; 3] { self.view_final.image().extent().clone() } } impl OverlayBackend for Canvas {} pub struct Control { pub state: Option, rect: Rect, fg_color: Vec3, bg_color: Vec3, text: Arc, size: isize, dirty: bool, pub on_update: Option, pub on_press: Option, pub on_release: Option, pub test_highlight: Option bool>, on_render_bg: Option, &mut AppState, &mut WlxCommandBuffer)>, on_render_hl: Option, &mut AppState, &mut WlxCommandBuffer, bool)>, on_render_fg: Option, &mut AppState, &mut WlxCommandBuffer)>, } impl Control { fn new() -> Self { Self { rect: Rect { x: 0., y: 0., w: 0., h: 0., }, fg_color: Vec3::ONE, bg_color: Vec3::ZERO, text: Arc::from(""), dirty: true, size: 24, state: None, on_update: None, on_render_bg: None, on_render_hl: None, on_render_fg: None, test_highlight: None, on_press: None, on_release: None, } } #[inline(always)] pub fn set_text(&mut self, text: &str) { if *self.text == *text { return; } self.text = text.into(); self.dirty = true; } fn render_rect( &self, canvas: &CanvasData, _: &mut AppState, cmd_buffer: &mut WlxCommandBuffer, ) { let pass = { let vertex_buffer = canvas.graphics.upload_verts( canvas.width as _, canvas.height as _, self.rect.x, self.rect.y, self.rect.w, self.rect.h, ); let set0 = canvas.pipeline_bg_color.uniform_buffer( 0, vec![self.bg_color.x, self.bg_color.y, self.bg_color.z, 1.], ); canvas.pipeline_bg_color.create_pass( [canvas.width as _, canvas.height as _], vertex_buffer, canvas.graphics.quad_indices.clone(), vec![set0], ) }; cmd_buffer.run_ref(&pass); } fn render_highlight( &self, canvas: &CanvasData, _: &mut AppState, cmd_buffer: &mut WlxCommandBuffer, strong: bool, ) { let vertex_buffer = canvas.graphics.upload_verts( canvas.width as _, canvas.height as _, self.rect.x, self.rect.y, self.rect.w, self.rect.h, ); let set0 = canvas .pipeline_bg_color .uniform_buffer(0, vec![1.0, 1.0, 1.0, if strong { 0.5 } else { 0.3 }]); //FIXME why is //this green let pass = canvas.pipeline_bg_color.create_pass( [canvas.width as _, canvas.height as _], vertex_buffer.clone(), canvas.graphics.quad_indices.clone(), vec![set0], ); cmd_buffer.run_ref(&pass); } fn render_text( &self, canvas: &CanvasData, app: &mut AppState, cmd_buffer: &mut WlxCommandBuffer, ) { let mut cur_y = self.rect.y; for line in self.text.lines() { let mut cur_x = self.rect.x; for glyph in app.fc.get_glyphs(line, self.size, canvas.graphics.clone()) { if let Some(tex) = glyph.tex.clone() { let vertex_buffer = canvas.graphics.upload_verts( canvas.width as _, canvas.height as _, cur_x + glyph.left, cur_y - glyph.top, glyph.width, glyph.height, ); let set0 = canvas.pipeline_fg_glyph.uniform_sampler( 0, ImageView::new_default(tex).unwrap(), Filter::Nearest, ); let set1 = canvas.pipeline_fg_glyph.uniform_buffer( 1, vec![self.fg_color.x, self.fg_color.y, self.fg_color.z, 1.], ); let pass = canvas.pipeline_fg_glyph.create_pass( [canvas.width as _, canvas.height as _], vertex_buffer, canvas.graphics.quad_indices.clone(), vec![set0, set1], ); cmd_buffer.run_ref(&pass); } cur_x += glyph.advance; } cur_y += (self.size as f32) * 1.5; } } fn render_text_centered( &self, canvas: &CanvasData, app: &mut AppState, cmd_buffer: &mut WlxCommandBuffer, ) { let (w, h) = app .fc .get_text_size(&self.text, self.size, canvas.graphics.clone()); let mut cur_y = self.rect.y + (self.rect.h) - (h * 0.5); for line in self.text.lines() { let mut cur_x = self.rect.x + (self.rect.w * 0.5) - (w * 0.5); for glyph in app.fc.get_glyphs(line, self.size, canvas.graphics.clone()) { if let Some(tex) = glyph.tex.clone() { let vertex_buffer = canvas.graphics.upload_verts( canvas.width as _, canvas.height as _, cur_x + glyph.left, cur_y - glyph.top, glyph.width, glyph.height, ); let set0 = canvas.pipeline_fg_glyph.uniform_sampler( 0, ImageView::new_default(tex).unwrap(), Filter::Nearest, ); let set1 = canvas.pipeline_fg_glyph.uniform_buffer( 1, vec![self.fg_color.x, self.fg_color.y, self.fg_color.z, 1.], ); let pass = canvas.pipeline_fg_glyph.create_pass( [canvas.width as _, canvas.height as _], vertex_buffer, canvas.graphics.quad_indices.clone(), vec![set0, set1], ); cmd_buffer.run_ref(&pass); } cur_x += glyph.advance; } cur_y += (self.size as f32) * 1.5; } } }