nothing works
This commit is contained in:
211
src/gui/font.rs
Normal file
211
src/gui/font.rs
Normal file
@@ -0,0 +1,211 @@
|
||||
use std::{rc::Rc, str::FromStr, sync::Arc};
|
||||
|
||||
use fontconfig::{FontConfig, OwnedPattern};
|
||||
use freetype::{bitmap::PixelMode, face::LoadFlag, Face, Library};
|
||||
use idmap::IdMap;
|
||||
use log::debug;
|
||||
use vulkano::{format::Format, command_buffer::CommandBufferUsage, image::ImmutableImage};
|
||||
|
||||
use crate::graphics::WlxGraphics;
|
||||
|
||||
const PRIMARY_FONT: &str = "LiberationSans";
|
||||
|
||||
pub struct FontCache {
|
||||
fc: FontConfig,
|
||||
ft: Library,
|
||||
collections: IdMap<isize, FontCollection>,
|
||||
}
|
||||
|
||||
struct FontCollection {
|
||||
fonts: Vec<Font>,
|
||||
cp_map: IdMap<usize, usize>,
|
||||
}
|
||||
|
||||
struct Font {
|
||||
face: Face,
|
||||
path: String,
|
||||
index: isize,
|
||||
size: isize,
|
||||
glyphs: IdMap<usize, Rc<Glyph>>,
|
||||
}
|
||||
|
||||
pub struct Glyph {
|
||||
pub tex: Option<Arc<ImmutableImage>>,
|
||||
pub top: f32,
|
||||
pub left: f32,
|
||||
pub width: f32,
|
||||
pub height: f32,
|
||||
pub advance: f32,
|
||||
}
|
||||
|
||||
impl FontCache {
|
||||
pub fn new() -> Self {
|
||||
let ft = Library::init().expect("Failed to initialize freetype");
|
||||
let fc = FontConfig::default();
|
||||
|
||||
FontCache {
|
||||
fc,
|
||||
ft,
|
||||
collections: IdMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_text_size(&mut self, text: &str, size: isize, graphics: Arc<WlxGraphics>) -> (f32, f32) {
|
||||
let sizef = size as f32;
|
||||
|
||||
let height = sizef + ((text.lines().count() as f32) - 1f32) * (sizef * 1.5);
|
||||
|
||||
let mut max_w = sizef * 0.33;
|
||||
for line in text.lines() {
|
||||
let w: f32 = line
|
||||
.chars()
|
||||
.map(|c| self.get_glyph_for_cp(c as usize, size, graphics.clone()).advance)
|
||||
.sum();
|
||||
|
||||
if w > max_w {
|
||||
max_w = w;
|
||||
}
|
||||
}
|
||||
(max_w, height)
|
||||
}
|
||||
|
||||
pub fn get_glyphs(&mut self, text: &str, size: isize, graphics: Arc<WlxGraphics>) -> Vec<Rc<Glyph>> {
|
||||
let mut glyphs = Vec::new();
|
||||
for line in text.lines() {
|
||||
for c in line.chars() {
|
||||
glyphs.push(self.get_glyph_for_cp(c as usize, size, graphics.clone()));
|
||||
}
|
||||
}
|
||||
glyphs
|
||||
}
|
||||
|
||||
fn get_font_for_cp(&mut self, cp: usize, size: isize) -> usize {
|
||||
if !self.collections.contains_key(size) {
|
||||
self.collections.insert(
|
||||
size,
|
||||
FontCollection {
|
||||
fonts: Vec::new(),
|
||||
cp_map: IdMap::new(),
|
||||
},
|
||||
);
|
||||
}
|
||||
let coll = self.collections.get_mut(size).unwrap();
|
||||
|
||||
if let Some(font) = coll.cp_map.get(cp) {
|
||||
return *font;
|
||||
}
|
||||
|
||||
let pattern_str = format!("{PRIMARY_FONT}-{size}:style=bold:charset={cp:04x}");
|
||||
|
||||
let mut pattern =
|
||||
OwnedPattern::from_str(&pattern_str).expect("Failed to create fontconfig pattern");
|
||||
self.fc
|
||||
.substitute(&mut pattern, fontconfig::MatchKind::Pattern);
|
||||
pattern.default_substitute();
|
||||
|
||||
let pattern = pattern.font_match(&mut self.fc);
|
||||
|
||||
if let Some(path) = pattern.filename() {
|
||||
debug!(
|
||||
"Loading font: {} {}pt",
|
||||
pattern.name().unwrap_or(path),
|
||||
size
|
||||
);
|
||||
|
||||
let font_idx = pattern.face_index().unwrap_or(0);
|
||||
|
||||
let face = self
|
||||
.ft
|
||||
.new_face(path, font_idx as _)
|
||||
.expect("Failed to load font face");
|
||||
face.set_char_size(size << 6, size << 6, 96, 96)
|
||||
.expect("Failed to set font size");
|
||||
|
||||
let idx = coll.fonts.len();
|
||||
for cp in 0..0xFFFF {
|
||||
if coll.cp_map.contains_key(cp) {
|
||||
continue;
|
||||
}
|
||||
let g = face.get_char_index(cp);
|
||||
if g > 0 {
|
||||
coll.cp_map.insert(cp, idx);
|
||||
}
|
||||
}
|
||||
|
||||
let zero_glyph = Rc::new(Glyph {
|
||||
tex: None,
|
||||
top: 0.,
|
||||
left: 0.,
|
||||
width: 0.,
|
||||
height: 0.,
|
||||
advance: size as f32 / 3.,
|
||||
});
|
||||
let mut glyphs = IdMap::new();
|
||||
glyphs.insert(0, zero_glyph);
|
||||
|
||||
let font = Font {
|
||||
face,
|
||||
path: path.to_string(),
|
||||
size,
|
||||
index: font_idx as _,
|
||||
glyphs,
|
||||
};
|
||||
coll.fonts.push(font);
|
||||
|
||||
idx
|
||||
} else {
|
||||
coll.cp_map.insert(cp, 0);
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn get_glyph_for_cp(&mut self, cp: usize, size: isize, graphics: Arc<WlxGraphics>) -> Rc<Glyph> {
|
||||
let key = self.get_font_for_cp(cp, size);
|
||||
|
||||
let font = &mut self.collections[size].fonts[key];
|
||||
|
||||
if let Some(glyph) = font.glyphs.get(cp) {
|
||||
return glyph.clone();
|
||||
}
|
||||
|
||||
if font.face.load_char(cp, LoadFlag::DEFAULT).is_err() {
|
||||
return font.glyphs[0].clone();
|
||||
}
|
||||
|
||||
let glyph = font.face.glyph();
|
||||
if glyph.render_glyph(freetype::RenderMode::Normal).is_err() {
|
||||
return font.glyphs[0].clone();
|
||||
}
|
||||
|
||||
let bmp = glyph.bitmap();
|
||||
let buf = bmp.buffer().to_vec();
|
||||
if buf.len() == 0 {
|
||||
return font.glyphs[0].clone();
|
||||
}
|
||||
|
||||
let metrics = glyph.metrics();
|
||||
|
||||
let format = match bmp.pixel_mode() {
|
||||
Ok(PixelMode::Gray) => Format::R8_UNORM,
|
||||
Ok(PixelMode::Gray2) => Format::R16_SFLOAT,
|
||||
Ok(PixelMode::Gray4) => Format::R32_SFLOAT,
|
||||
_ => return font.glyphs[0].clone(),
|
||||
};
|
||||
|
||||
let mut cmd_buffer = graphics.create_command_buffer(CommandBufferUsage::OneTimeSubmit);
|
||||
let texture = cmd_buffer.texture2d(bmp.width() as _, bmp.rows() as _, format, buf);
|
||||
let _ = cmd_buffer.end_and_execute();
|
||||
|
||||
let g = Glyph {
|
||||
tex: Some(texture),
|
||||
top: (metrics.horiBearingY >> 6i64) as _,
|
||||
left: (metrics.horiBearingX >> 6i64) as _,
|
||||
advance: (metrics.horiAdvance >> 6i64) as _,
|
||||
width: bmp.width() as _,
|
||||
height: bmp.rows() as _,
|
||||
};
|
||||
|
||||
font.glyphs.insert(cp, Rc::new(g));
|
||||
font.glyphs[cp].clone()
|
||||
}
|
||||
}
|
||||
669
src/gui/mod.rs
Normal file
669
src/gui/mod.rs
Normal file
@@ -0,0 +1,669 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use glam::{Vec2, Vec3};
|
||||
use vulkano::{
|
||||
command_buffer::{CommandBufferUsage, PrimaryAutoCommandBuffer},
|
||||
format::Format,
|
||||
image::{view::ImageView, AttachmentImage},
|
||||
sampler::Filter,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
graphics::{WlxCommandBuffer, WlxGraphics, WlxPass, WlxPipeline},
|
||||
overlays::{
|
||||
interactions::{InteractionHandler, PointerHit},
|
||||
OverlayBackend, OverlayRenderer,
|
||||
},
|
||||
shaders::{frag_color, frag_glyph, frag_sprite, vert_common},
|
||||
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<D, S> {
|
||||
canvas: Canvas<D, S>,
|
||||
|
||||
pub fg_color: Vec3,
|
||||
pub bg_color: Vec3,
|
||||
pub font_size: isize,
|
||||
}
|
||||
|
||||
impl<D, S> CanvasBuilder<D, S> {
|
||||
pub fn new(
|
||||
width: usize,
|
||||
height: usize,
|
||||
graphics: Arc<WlxGraphics>,
|
||||
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<D, S> {
|
||||
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<D, S> {
|
||||
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<str>) -> &mut Control<D, S> {
|
||||
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
|
||||
pub fn label_centered(
|
||||
&mut self,
|
||||
x: f32,
|
||||
y: f32,
|
||||
w: f32,
|
||||
h: f32,
|
||||
text: Arc<str>,
|
||||
) -> &mut Control<D, S> {
|
||||
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<str>) -> &mut Control<D, S> {
|
||||
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<String>,
|
||||
) -> &mut Control<D, S> {
|
||||
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<D> {
|
||||
pub data: D,
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
|
||||
graphics: Arc<WlxGraphics>,
|
||||
|
||||
pipeline_color: Arc<WlxPipeline>,
|
||||
pipeline_glyph: Arc<WlxPipeline>,
|
||||
}
|
||||
|
||||
pub struct Canvas<D, S> {
|
||||
controls: Vec<Control<D, S>>,
|
||||
canvas: CanvasData<D>,
|
||||
|
||||
hover_controls: [Option<usize>; 2],
|
||||
pressed_controls: [Option<usize>; 2],
|
||||
|
||||
interact_map: Vec<Option<u8>>,
|
||||
interact_stride: usize,
|
||||
interact_rows: usize,
|
||||
|
||||
tex_fg: Arc<AttachmentImage>,
|
||||
view_fg: Arc<ImageView<AttachmentImage>>,
|
||||
tex_bg: Arc<AttachmentImage>,
|
||||
view_bg: Arc<ImageView<AttachmentImage>>,
|
||||
tex_final: Arc<AttachmentImage>,
|
||||
view_final: Arc<ImageView<AttachmentImage>>,
|
||||
|
||||
pass_fg: WlxPass,
|
||||
pass_bg: WlxPass,
|
||||
}
|
||||
|
||||
impl<D, S> Canvas<D, S> {
|
||||
fn new(
|
||||
width: usize,
|
||||
height: usize,
|
||||
graphics: Arc<WlxGraphics>,
|
||||
format: Format,
|
||||
data: D,
|
||||
) -> Self {
|
||||
let pipeline_color = graphics.create_pipeline(
|
||||
vert_common::load(graphics.device.clone()).unwrap(),
|
||||
frag_color::load(graphics.device.clone()).unwrap(),
|
||||
format,
|
||||
);
|
||||
|
||||
let pipeline_glyph = graphics.create_pipeline(
|
||||
vert_common::load(graphics.device.clone()).unwrap(),
|
||||
frag_glyph::load(graphics.device.clone()).unwrap(),
|
||||
format,
|
||||
);
|
||||
|
||||
let vertex_buffer =
|
||||
graphics.upload_verts(width as _, height as _, 0., 0., width as _, height as _);
|
||||
|
||||
let pipeline = graphics.create_pipeline(
|
||||
vert_common::load(graphics.device.clone()).unwrap(),
|
||||
frag_sprite::load(graphics.device.clone()).unwrap(),
|
||||
format,
|
||||
);
|
||||
|
||||
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 set_fg = pipeline.uniform_sampler(0, view_fg.clone(), Filter::Nearest);
|
||||
let set_bg = pipeline.uniform_sampler(0, view_bg.clone(), Filter::Nearest);
|
||||
let pass_fg = pipeline.create_pass(
|
||||
[width as _, height as _],
|
||||
vertex_buffer.clone(),
|
||||
graphics.quad_indices.clone(),
|
||||
vec![set_fg],
|
||||
);
|
||||
let pass_bg = pipeline.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,
|
||||
pipeline_color,
|
||||
pipeline_glyph,
|
||||
},
|
||||
controls: Vec::new(),
|
||||
hover_controls: [None, None],
|
||||
pressed_controls: [None, None],
|
||||
interact_map: vec![None; stride * rows],
|
||||
interact_stride: stride,
|
||||
interact_rows: rows,
|
||||
tex_fg,
|
||||
view_fg,
|
||||
tex_bg,
|
||||
view_bg,
|
||||
tex_final,
|
||||
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<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) {
|
||||
let mut cmd_buffer = self
|
||||
.canvas
|
||||
.graphics
|
||||
.create_command_buffer(CommandBufferUsage::OneTimeSubmit)
|
||||
.begin(self.view_bg.clone());
|
||||
for c in self.controls.iter_mut() {
|
||||
if let Some(fun) = c.on_render_bg {
|
||||
fun(c, &self.canvas, app, &mut cmd_buffer);
|
||||
}
|
||||
}
|
||||
let _ = cmd_buffer.end_render_and_execute();
|
||||
}
|
||||
|
||||
fn render_fg(&mut self, app: &mut AppState) {
|
||||
let mut cmd_buffer = self
|
||||
.canvas
|
||||
.graphics
|
||||
.create_command_buffer(CommandBufferUsage::OneTimeSubmit)
|
||||
.begin(self.view_fg.clone());
|
||||
for c in self.controls.iter_mut() {
|
||||
if let Some(fun) = c.on_render_fg {
|
||||
fun(c, &self.canvas, app, &mut cmd_buffer);
|
||||
}
|
||||
}
|
||||
let _ = cmd_buffer.end_render_and_execute();
|
||||
}
|
||||
|
||||
pub fn render_view(&self) -> Arc<ImageView<AttachmentImage>> {
|
||||
self.view_final.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<D, S> InteractionHandler for Canvas<D, S> {
|
||||
fn on_left(&mut self, _app: &mut AppState, hand: usize) {
|
||||
self.hover_controls[hand] = 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.hand] = Some(i);
|
||||
} else {
|
||||
self.hover_controls[hit.hand] = 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.hand]
|
||||
};
|
||||
|
||||
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.hand] = Some(idx);
|
||||
f(c, &mut self.canvas.data, app);
|
||||
}
|
||||
} else if let Some(ref mut f) = c.on_release {
|
||||
self.pressed_controls[hit.hand] = None;
|
||||
f(c, &mut self.canvas.data, app);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn on_scroll(&mut self, _app: &mut AppState, _hit: &PointerHit, _delta: f32) {}
|
||||
}
|
||||
|
||||
impl<D, S> OverlayRenderer for Canvas<D, S> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
let mut cmd_buffer = self
|
||||
.canvas
|
||||
.graphics
|
||||
.create_command_buffer(CommandBufferUsage::OneTimeSubmit)
|
||||
.begin(self.view_final.clone());
|
||||
|
||||
if dirty {
|
||||
self.render_fg(app);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
let _ = cmd_buffer.end_render_and_execute();
|
||||
}
|
||||
fn view(&mut self) -> Arc<dyn vulkano::image::ImageViewAbstract> {
|
||||
self.view_final.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<D, S> OverlayBackend for Canvas<D, S> {}
|
||||
|
||||
pub struct Control<D, S> {
|
||||
pub state: Option<S>,
|
||||
rect: Rect,
|
||||
fg_color: Vec3,
|
||||
bg_color: Vec3,
|
||||
text: Arc<str>,
|
||||
size: isize,
|
||||
dirty: bool,
|
||||
pass_hl: Option<(WlxPass, WlxPass)>,
|
||||
|
||||
pub on_update: Option<fn(&mut Self, &mut D, &mut AppState)>,
|
||||
pub on_press: Option<fn(&mut Self, &mut D, &mut AppState)>,
|
||||
pub on_release: Option<fn(&mut Self, &mut D, &mut AppState)>,
|
||||
pub test_highlight: Option<fn(&Self, &mut D, &mut AppState) -> bool>,
|
||||
|
||||
on_render_bg: Option<
|
||||
fn(&Self, &CanvasData<D>, &mut AppState, &mut WlxCommandBuffer<PrimaryAutoCommandBuffer>),
|
||||
>,
|
||||
on_render_hl: Option<
|
||||
fn(
|
||||
&Self,
|
||||
&CanvasData<D>,
|
||||
&mut AppState,
|
||||
&mut WlxCommandBuffer<PrimaryAutoCommandBuffer>,
|
||||
bool,
|
||||
),
|
||||
>,
|
||||
on_render_fg: Option<
|
||||
fn(&Self, &CanvasData<D>, &mut AppState, &mut WlxCommandBuffer<PrimaryAutoCommandBuffer>),
|
||||
>,
|
||||
}
|
||||
|
||||
impl<D, S> Control<D, S> {
|
||||
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: false,
|
||||
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,
|
||||
pass_hl: 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 get_text(&self) -> &str {
|
||||
&self.text
|
||||
}
|
||||
|
||||
fn render_rect(
|
||||
&self,
|
||||
canvas: &CanvasData<D>,
|
||||
_: &mut AppState,
|
||||
cmd_buffer: &mut WlxCommandBuffer<PrimaryAutoCommandBuffer>,
|
||||
) {
|
||||
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_color.uniform_buffer(
|
||||
0,
|
||||
vec![self.bg_color.x, self.bg_color.y, self.bg_color.z, 1.],
|
||||
);
|
||||
canvas.pipeline_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<D>,
|
||||
_: &mut AppState,
|
||||
cmd_buffer: &mut WlxCommandBuffer<PrimaryAutoCommandBuffer>,
|
||||
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_color.uniform_buffer(
|
||||
0,
|
||||
vec![
|
||||
self.bg_color.x,
|
||||
self.bg_color.y,
|
||||
self.bg_color.z,
|
||||
if strong { 0.5 } else { 0.3 },
|
||||
],
|
||||
);
|
||||
let pass = canvas.pipeline_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<D>,
|
||||
app: &mut AppState,
|
||||
cmd_buffer: &mut WlxCommandBuffer<PrimaryAutoCommandBuffer>,
|
||||
) {
|
||||
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_glyph.uniform_sampler(
|
||||
0,
|
||||
ImageView::new_default(tex).unwrap(),
|
||||
Filter::Nearest,
|
||||
);
|
||||
let set1 = canvas.pipeline_glyph.uniform_buffer(
|
||||
1,
|
||||
vec![self.fg_color.x, self.fg_color.y, self.fg_color.z, 1.],
|
||||
);
|
||||
let pass = canvas.pipeline_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<D>,
|
||||
app: &mut AppState,
|
||||
cmd_buffer: &mut WlxCommandBuffer<PrimaryAutoCommandBuffer>,
|
||||
) {
|
||||
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_glyph.uniform_sampler(
|
||||
0,
|
||||
ImageView::new_default(tex).unwrap(),
|
||||
Filter::Nearest,
|
||||
);
|
||||
let set1 = canvas.pipeline_glyph.uniform_buffer(
|
||||
1,
|
||||
vec![self.fg_color.x, self.fg_color.y, self.fg_color.z, 1.],
|
||||
);
|
||||
let pass = canvas.pipeline_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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user