mirror of https://github.com/wayvr-org/wayvr.git
389 lines
12 KiB
Rust
389 lines
12 KiB
Rust
use glam::Vec4;
|
|
use std::sync::Arc;
|
|
use vulkano::image::view::ImageView;
|
|
|
|
use crate::{
|
|
backend::input::PointerMode, graphics::WlxCommandBuffer, gui::GuiColor, state::AppState,
|
|
};
|
|
|
|
use super::{CanvasData, Rect};
|
|
|
|
pub type ControlRenderer<D, S> =
|
|
fn(&Control<D, S>, &CanvasData<D>, &mut AppState, &mut WlxCommandBuffer) -> anyhow::Result<()>;
|
|
|
|
pub type ControlRendererHl<D, S> = fn(
|
|
&Control<D, S>,
|
|
&CanvasData<D>,
|
|
&mut AppState,
|
|
&mut WlxCommandBuffer,
|
|
Vec4,
|
|
) -> anyhow::Result<()>;
|
|
|
|
#[allow(clippy::type_complexity)]
|
|
pub(crate) struct Control<D, S> {
|
|
pub state: Option<S>,
|
|
pub rect: Rect,
|
|
pub corner_radius: f32,
|
|
pub fg_color: GuiColor,
|
|
pub bg_color: GuiColor,
|
|
pub text: Arc<str>,
|
|
pub size: isize,
|
|
pub sprite: Option<Arc<ImageView>>,
|
|
pub sprite_st: Vec4,
|
|
pub(super) dirty: bool,
|
|
|
|
pub on_update: Option<fn(&mut Self, &mut D, &mut AppState)>,
|
|
pub on_press: Option<fn(&mut Self, &mut D, &mut AppState, PointerMode)>,
|
|
pub on_release: Option<fn(&mut Self, &mut D, &mut AppState)>,
|
|
pub on_scroll: Option<fn(&mut Self, &mut D, &mut AppState, f32)>,
|
|
pub test_highlight: Option<fn(&Self, &mut D, &mut AppState) -> Option<Vec4>>,
|
|
|
|
pub(super) on_render_bg: Option<ControlRenderer<D, S>>,
|
|
pub(super) on_render_hl: Option<ControlRendererHl<D, S>>,
|
|
pub(super) on_render_fg: Option<ControlRenderer<D, S>>,
|
|
}
|
|
|
|
impl<D, S> Control<D, S> {
|
|
pub(super) fn new() -> Self {
|
|
Self {
|
|
rect: Rect {
|
|
x: 0.,
|
|
y: 0.,
|
|
w: 0.,
|
|
h: 0.,
|
|
},
|
|
corner_radius: 0.,
|
|
fg_color: Vec4::ONE,
|
|
bg_color: Vec4::ZERO,
|
|
text: Arc::from(""),
|
|
sprite: None,
|
|
sprite_st: Vec4::new(1., 1., 0., 0.),
|
|
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,
|
|
on_scroll: None,
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn set_text(&mut self, text: &str) {
|
|
if *self.text == *text {
|
|
return;
|
|
}
|
|
self.text = text.into();
|
|
self.dirty = true;
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn set_sprite(&mut self, sprite: Arc<ImageView>) {
|
|
self.sprite.replace(sprite);
|
|
self.dirty = true;
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn set_sprite_st(&mut self, sprite_st: Vec4) {
|
|
if self.sprite_st == sprite_st {
|
|
return;
|
|
}
|
|
self.sprite_st = sprite_st;
|
|
self.dirty = true;
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn set_fg_color(&mut self, color: GuiColor) {
|
|
if self.fg_color == color {
|
|
return;
|
|
}
|
|
self.fg_color = color;
|
|
self.dirty = true;
|
|
}
|
|
|
|
pub fn render_rounded_rect(
|
|
&self,
|
|
canvas: &CanvasData<D>,
|
|
_: &mut AppState,
|
|
cmd_buffer: &mut WlxCommandBuffer,
|
|
) -> anyhow::Result<()> {
|
|
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 clamped_radius = self
|
|
.corner_radius
|
|
.min(self.rect.w / 2.0)
|
|
.min(self.rect.h / 2.0);
|
|
|
|
let skew_radius = [clamped_radius / self.rect.w, clamped_radius / 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,
|
|
self.bg_color.w,
|
|
skew_radius[0],
|
|
skew_radius[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)
|
|
}
|
|
|
|
pub(super) fn render_highlight(
|
|
&self,
|
|
canvas: &CanvasData<D>,
|
|
_: &mut AppState,
|
|
cmd_buffer: &mut WlxCommandBuffer,
|
|
color: GuiColor,
|
|
) -> anyhow::Result<()> {
|
|
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 clamped_radius = self
|
|
.corner_radius
|
|
.min(self.rect.w / 2.0)
|
|
.min(self.rect.h / 2.0);
|
|
|
|
let skew_radius = [clamped_radius / self.rect.w, clamped_radius / self.rect.h];
|
|
|
|
let set0 = canvas.pipeline_bg_color.uniform_buffer(
|
|
0,
|
|
vec![
|
|
color.x,
|
|
color.y,
|
|
color.z,
|
|
color.w,
|
|
skew_radius[0],
|
|
skew_radius[1],
|
|
],
|
|
)?;
|
|
|
|
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)
|
|
}
|
|
|
|
pub(super) fn render_text(
|
|
&self,
|
|
canvas: &CanvasData<D>,
|
|
app: &mut AppState,
|
|
cmd_buffer: &mut WlxCommandBuffer,
|
|
) -> anyhow::Result<()> {
|
|
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)?,
|
|
app.graphics.texture_filtering,
|
|
)?;
|
|
let set1 = canvas
|
|
.pipeline_fg_glyph
|
|
.uniform_buffer(1, self.fg_color.to_array().to_vec())?;
|
|
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;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub(super) fn render_text_centered(
|
|
&self,
|
|
canvas: &CanvasData<D>,
|
|
app: &mut AppState,
|
|
cmd_buffer: &mut WlxCommandBuffer,
|
|
) -> anyhow::Result<()> {
|
|
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) - (self.size as f32 * 0.25);
|
|
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)?,
|
|
app.graphics.texture_filtering,
|
|
)?;
|
|
let set1 = canvas
|
|
.pipeline_fg_glyph
|
|
.uniform_buffer(1, self.fg_color.to_array().to_vec())?;
|
|
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;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub(super) fn render_sprite_bg(
|
|
&self,
|
|
canvas: &CanvasData<D>,
|
|
app: &mut AppState,
|
|
cmd_buffer: &mut WlxCommandBuffer,
|
|
) -> anyhow::Result<()> {
|
|
let Some(view) = self.sprite.as_ref() else {
|
|
return Ok(());
|
|
};
|
|
|
|
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_sprite.uniform_sampler(
|
|
0,
|
|
view.clone(),
|
|
app.graphics.texture_filtering,
|
|
)?;
|
|
|
|
let uniform = vec![
|
|
self.sprite_st.x,
|
|
self.sprite_st.y,
|
|
self.sprite_st.z,
|
|
self.sprite_st.w,
|
|
self.fg_color.x,
|
|
self.fg_color.y,
|
|
self.fg_color.z,
|
|
self.fg_color.w,
|
|
];
|
|
|
|
let set1 = canvas.pipeline_bg_sprite.uniform_buffer(1, uniform)?;
|
|
|
|
let pass = canvas.pipeline_bg_sprite.create_pass(
|
|
[canvas.width as _, canvas.height as _],
|
|
vertex_buffer,
|
|
canvas.graphics.quad_indices.clone(),
|
|
vec![set0, set1],
|
|
)?;
|
|
cmd_buffer.run_ref(&pass)?;
|
|
Ok(())
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub(super) fn render_sprite_hl(
|
|
&self,
|
|
canvas: &CanvasData<D>,
|
|
app: &mut AppState,
|
|
cmd_buffer: &mut WlxCommandBuffer,
|
|
color: GuiColor,
|
|
) -> anyhow::Result<()> {
|
|
let Some(view) = self.sprite.as_ref() else {
|
|
return Ok(());
|
|
};
|
|
|
|
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_hl_sprite.uniform_sampler(
|
|
0,
|
|
view.clone(),
|
|
app.graphics.texture_filtering,
|
|
)?;
|
|
|
|
let uniform = vec![
|
|
self.sprite_st.x,
|
|
self.sprite_st.y,
|
|
self.sprite_st.z,
|
|
self.sprite_st.w,
|
|
color.x,
|
|
color.y,
|
|
color.z,
|
|
color.w,
|
|
];
|
|
|
|
let set1 = canvas.pipeline_hl_sprite.uniform_buffer(1, uniform)?;
|
|
|
|
let pass = canvas.pipeline_hl_sprite.create_pass(
|
|
[canvas.width as _, canvas.height as _],
|
|
vertex_buffer,
|
|
canvas.graphics.quad_indices.clone(),
|
|
vec![set0, set1],
|
|
)?;
|
|
cmd_buffer.run_ref(&pass)?;
|
|
Ok(())
|
|
}
|
|
}
|