383 lines
12 KiB
Rust
383 lines
12 KiB
Rust
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<D> {
|
|
pub data: D,
|
|
pub width: usize,
|
|
pub height: usize,
|
|
|
|
graphics: Arc<WlxGraphics>,
|
|
|
|
pipeline_bg_color: Arc<WlxPipeline<WlxPipelineLegacy>>,
|
|
pipeline_fg_glyph: Arc<WlxPipeline<WlxPipelineLegacy>>,
|
|
pipeline_bg_sprite: Arc<WlxPipeline<WlxPipelineLegacy>>,
|
|
pipeline_hl_sprite: Arc<WlxPipeline<WlxPipelineLegacy>>,
|
|
pipeline_final: Arc<WlxPipeline<WlxPipelineLegacy>>,
|
|
}
|
|
|
|
pub struct Canvas<D, S> {
|
|
controls: Vec<control::Control<D, S>>,
|
|
canvas: CanvasData<D>,
|
|
|
|
hover_controls: [Option<usize>; 2],
|
|
pressed_controls: [Option<usize>; 2],
|
|
|
|
interact_map: Vec<Option<u16>>,
|
|
interact_stride: usize,
|
|
interact_rows: usize,
|
|
|
|
view_final: Arc<ImageView>,
|
|
|
|
pass_fg: WlxPass<WlxPipelineLegacy>,
|
|
pass_bg: WlxPass<WlxPipelineLegacy>,
|
|
}
|
|
|
|
impl<D, S> Canvas<D, S> {
|
|
fn new(
|
|
width: usize,
|
|
height: usize,
|
|
graphics: Arc<WlxGraphics>,
|
|
format: Format,
|
|
data: D,
|
|
) -> anyhow::Result<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())?;
|
|
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<usize> {
|
|
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<D, S> InteractionHandler for Canvas<D, S> {
|
|
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<Haptics> {
|
|
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<D, S> OverlayRenderer for Canvas<D, S> {
|
|
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<Arc<ImageView>> {
|
|
Some(self.view_final.clone())
|
|
}
|
|
|
|
fn extent(&mut self) -> Option<[u32; 3]> {
|
|
Some(self.view_final.image().extent())
|
|
}
|
|
}
|
|
|
|
impl<D, S> OverlayBackend for Canvas<D, S> {
|
|
fn set_renderer(&mut self, _renderer: Box<dyn OverlayRenderer>) {}
|
|
fn set_interaction(&mut self, _interaction: Box<dyn InteractionHandler>) {}
|
|
}
|