From 1ff57a6ecf083747e126e0095953cab922354bf7 Mon Sep 17 00:00:00 2001 From: darkfi Date: Mon, 1 Jul 2024 09:43:21 +0200 Subject: [PATCH] wallet: UI Text properties can be edited realtime --- bin/darkwallet/src/gfx2.rs | 8 +- bin/darkwallet/src/main.rs | 7 +- bin/darkwallet/src/mesh.rs | 17 ++-- bin/darkwallet/src/prop/wrap.rs | 30 +++++- bin/darkwallet/src/text2.rs | 10 +- bin/darkwallet/src/ui/text.rs | 167 +++++++++++++++++++------------- 6 files changed, 148 insertions(+), 91 deletions(-) diff --git a/bin/darkwallet/src/gfx2.rs b/bin/darkwallet/src/gfx2.rs index 4f3eb9e74..2150028af 100644 --- a/bin/darkwallet/src/gfx2.rs +++ b/bin/darkwallet/src/gfx2.rs @@ -413,9 +413,11 @@ impl Stage { sendr: async_channel::Sender, ) { let texture = self.ctx.new_texture_from_rgba8(width, height, &data); - debug!(target: "gfx2", "Invoked method: new_texture({}, {}, ...) -> {:?}\n{}", - width, height, texture, - ansi_texture(width as usize, height as usize, &data)); + debug!(target: "gfx2", "Invoked method: new_texture({}, {}, ...) -> {:?}", + width, height, texture); + //debug!(target: "gfx2", "Invoked method: new_texture({}, {}, ...) -> {:?}\n{}", + // width, height, texture, + // ansi_texture(width as usize, height as usize, &data)); sendr.try_send(texture).unwrap(); } fn method_delete_texture(&mut self, texture: TextureId) { diff --git a/bin/darkwallet/src/main.rs b/bin/darkwallet/src/main.rs index 1faead5f0..ab9986081 100644 --- a/bin/darkwallet/src/main.rs +++ b/bin/darkwallet/src/main.rs @@ -37,20 +37,21 @@ mod util; use crate::{net::ZeroMQAdapter, scene::SceneGraph, text2::TextShaper}; -#[cfg(target_os = "android")] fn panic_hook(panic_info: &std::panic::PanicInfo) { error!("panic occurred: {panic_info}"); //error!("panic: {}", std::backtrace::Backtrace::force_capture().to_string()); + std::process::exit(1); } fn main() { + // Exit the application on panic right away + std::panic::set_hook(Box::new(panic_hook)); + #[cfg(target_os = "android")] { android_logger::init_once( android_logger::Config::default().with_max_level(LevelFilter::Debug).with_tag("darkfi"), ); - - std::panic::set_hook(Box::new(panic_hook)); } #[cfg(target_os = "linux")] diff --git a/bin/darkwallet/src/mesh.rs b/bin/darkwallet/src/mesh.rs index 058b07951..4293dea57 100644 --- a/bin/darkwallet/src/mesh.rs +++ b/bin/darkwallet/src/mesh.rs @@ -12,6 +12,13 @@ pub const COLOR_GREEN: Color = [0., 1., 0., 1.]; pub const COLOR_BLUE: Color = [0., 0., 1., 1.]; pub const COLOR_WHITE: Color = [1., 1., 1., 1.]; +#[derive(Clone)] +pub struct MeshInfo { + pub vertex_buffer: BufferId, + pub index_buffer: BufferId, + pub num_elements: i32, +} + pub struct MeshBuilder { verts: Vec, indices: Vec, @@ -67,18 +74,14 @@ impl MeshBuilder { self.draw_box(&Rectangle::new(x1, y2 - thickness, dist_x, thickness), color, &uv); } - // Needed by OpenGL - pub fn num_elements(&self) -> i32 { - self.indices.len() as i32 - } - - pub async fn alloc(self, render_api: &RenderApi) -> Result<(BufferId, BufferId)> { + pub async fn alloc(self, render_api: &RenderApi) -> Result { //debug!(target: "mesh", "allocating {} verts:", self.verts.len()); //for vert in &self.verts { // debug!(target: "mesh", " {:?}", vert); //} + let num_elements = self.indices.len() as i32; let vertex_buffer = render_api.new_vertex_buffer(self.verts).await?; let index_buffer = render_api.new_index_buffer(self.indices).await?; - Ok((vertex_buffer, index_buffer)) + Ok(MeshInfo { vertex_buffer, index_buffer, num_elements }) } } diff --git a/bin/darkwallet/src/prop/wrap.rs b/bin/darkwallet/src/prop/wrap.rs index 48a595a32..41fd8ebab 100644 --- a/bin/darkwallet/src/prop/wrap.rs +++ b/bin/darkwallet/src/prop/wrap.rs @@ -7,7 +7,7 @@ use crate::{ }; pub struct PropertyBool { - prop: Arc, + prop: PropertyPtr, idx: usize, } @@ -28,10 +28,14 @@ impl PropertyBool { pub fn set(&self, val: bool) { self.prop.set_bool(self.idx, val).unwrap() } + + pub fn prop(&self) -> PropertyPtr { + self.prop.clone() + } } pub struct PropertyUint32 { - prop: Arc, + prop: PropertyPtr, idx: usize, } @@ -59,10 +63,14 @@ impl PropertyUint32 { pub fn set(&self, val: u32) { self.prop.set_u32(self.idx, val).unwrap() } + + pub fn prop(&self) -> PropertyPtr { + self.prop.clone() + } } pub struct PropertyFloat32 { - prop: Arc, + prop: PropertyPtr, idx: usize, } @@ -83,10 +91,14 @@ impl PropertyFloat32 { pub fn set(&self, val: f32) { self.prop.set_f32(self.idx, val).unwrap() } + + pub fn prop(&self) -> PropertyPtr { + self.prop.clone() + } } pub struct PropertyStr { - prop: Arc, + prop: PropertyPtr, idx: usize, } @@ -107,10 +119,14 @@ impl PropertyStr { pub fn set>(&self, val: S) { self.prop.set_str(self.idx, val.into()).unwrap() } + + pub fn prop(&self) -> PropertyPtr { + self.prop.clone() + } } pub struct PropertyColor { - prop: Arc, + prop: PropertyPtr, } impl PropertyColor { @@ -142,4 +158,8 @@ impl PropertyColor { self.prop.set_f32(2, val[2]).unwrap(); self.prop.set_f32(3, val[3]).unwrap(); } + + pub fn prop(&self) -> PropertyPtr { + self.prop.clone() + } } diff --git a/bin/darkwallet/src/text2.rs b/bin/darkwallet/src/text2.rs index 8dd4325e2..70d773c75 100644 --- a/bin/darkwallet/src/text2.rs +++ b/bin/darkwallet/src/text2.rs @@ -16,7 +16,7 @@ use std::{ use crate::{ error::Result, - gfx2::{Rectangle, RenderApiPtr}, + gfx2::{Rectangle, RenderApi, RenderApiPtr}, util::ansi_texture, }; @@ -53,7 +53,7 @@ pub struct RenderedAtlas { const ATLAS_GAP: usize = 2; pub async fn make_texture_atlas( - render_api: RenderApiPtr, + render_api: &RenderApi, font_size: f32, glyphs: &Vec, ) -> Result { @@ -337,7 +337,9 @@ impl TextShaper { let mut prev_cluster = 0; //for (i, (position, info)) in positions.iter().zip(infos).enumerate() { - for (i, (position, info)) in glyph_pos_iter.iter().zip(glyph_infos_iter.iter()).enumerate() { + for (i, (position, info)) in + glyph_pos_iter.iter().zip(glyph_infos_iter.iter()).enumerate() + { let glyph_id = info.codepoint as u32; // Index within this substr let curr_cluster = info.cluster as usize; @@ -450,6 +452,7 @@ impl TextShaper { bearing_x, bearing_y, has_fixed_sizes, + has_color: face.has_color(), }); cache.insert(cache_key, Arc::downgrade(&sprite)); @@ -510,6 +513,7 @@ pub struct Sprite { pub bearing_x: f32, pub bearing_y: f32, pub has_fixed_sizes: bool, + pub has_color: bool, } pub struct Glyph { diff --git a/bin/darkwallet/src/ui/text.rs b/bin/darkwallet/src/ui/text.rs index b930afab2..3b89d358e 100644 --- a/bin/darkwallet/src/ui/text.rs +++ b/bin/darkwallet/src/ui/text.rs @@ -1,37 +1,46 @@ -use async_lock::Mutex; +//use async_lock::Mutex; use miniquad::{BufferId, TextureId}; use rand::{rngs::OsRng, Rng}; -use std::sync::{Arc, Weak}; +use std::sync::{Arc, Mutex, Weak}; use crate::{ - gfx2::{DrawCall, DrawInstruction, DrawMesh, Rectangle, RenderApiPtr, Vertex}, - mesh::{MeshBuilder, COLOR_BLUE, COLOR_WHITE}, - prop::{PropertyPtr, PropertyUint32}, + gfx2::{DrawCall, DrawInstruction, DrawMesh, Rectangle, RenderApi, RenderApiPtr, Vertex}, + mesh::{Color, MeshBuilder, MeshInfo, COLOR_BLUE, COLOR_WHITE}, + prop::{ + PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr, PropertyStr, PropertyUint32, + }, scene::{Pimpl, SceneGraph, SceneGraphPtr2, SceneNodeId}, - text2::{self, Glyph, GlyphPositionIter, RenderedAtlas, SpritePtr, TextShaperPtr}, + text2::{self, Glyph, GlyphPositionIter, RenderedAtlas, SpritePtr, TextShaper, TextShaperPtr}, }; use super::{eval_rect, get_parent_rect, read_rect, DrawUpdate, OnModify, Stoppable}; pub type TextPtr = Arc; +#[derive(Clone)] +struct TextRenderInfo { + glyph_sprites: Vec, + mesh: MeshInfo, + texture_id: TextureId, +} + pub struct Text { sg: SceneGraphPtr2, render_api: RenderApiPtr, text_shaper: TextShaperPtr, tasks: Vec>, - glyph_sprites: Mutex>, - - texture_id: TextureId, - vertex_buffer: BufferId, - index_buffer: BufferId, - num_elements: i32, + render_info: Mutex, dc_key: u64, node_id: SceneNodeId, rect: PropertyPtr, z_index: PropertyUint32, + text: PropertyStr, + font_size: PropertyFloat32, + color: PropertyColor, + baseline: PropertyFloat32, + debug: PropertyBool, } impl Text { @@ -46,83 +55,99 @@ impl Text { let node = scene_graph.get_node(node_id).unwrap(); let node_name = node.name.clone(); let rect = node.get_property("rect").expect("Text::rect"); - let z_index_prop = node.get_property("z_index").expect("Text::z_index"); - let z_index = PropertyUint32::from(z_index_prop.clone(), 0).unwrap(); - let text = node.get_property("text").expect("Text::text"); - let font_size = node.get_property("font_size").expect("Text::font_size"); - let color = node.get_property("color").expect("Text::color"); - let debug = node.get_property("debug").expect("Text::debug"); - let baseline = node.get_property("baseline").expect("Text::baseline"); + let z_index = PropertyUint32::wrap(node, "z_index", 0).unwrap(); + let text = PropertyStr::wrap(node, "text", 0).unwrap(); + let font_size = PropertyFloat32::wrap(node, "font_size", 0).unwrap(); + let color = PropertyColor::wrap(node, "color").unwrap(); + let debug = PropertyBool::wrap(node, "debug", 0).unwrap(); + let baseline = PropertyFloat32::wrap(node, "baseline", 0).unwrap(); drop(scene_graph); - let text_str = text.get_str(0).unwrap(); - let font_size_val = font_size.get_f32(0).unwrap(); - debug!(target: "ui::text", "Rendering label '{}'", text_str); - let glyphs = text_shaper.shape(text_str, font_size_val).await; - let atlas = - text2::make_texture_atlas(render_api.clone(), font_size_val, &glyphs).await.unwrap(); - - let (x1, y1) = (0., 0.); - let (x2, y2) = (1., 1.); - let verts = vec![ - // top left - Vertex { pos: [x1, y1], color: [1., 0., 0., 1.], uv: [0., 0.] }, - // top right - Vertex { pos: [x2, y1], color: [1., 0., 1., 1.], uv: [1., 0.] }, - // bottom left - Vertex { pos: [x1, y2], color: [0., 0., 1., 1.], uv: [0., 1.] }, - // bottom right - Vertex { pos: [x2, y2], color: [1., 1., 0., 1.], uv: [1., 1.] }, - ]; - let indices = vec![0, 2, 1, 1, 2, 3]; - assert_eq!(atlas.uv_rects.len(), glyphs.len()); - - let baseline_y = baseline.get_f32(0).unwrap(); - - let mut mesh = MeshBuilder::new(); - let mut glyph_pos_iter = GlyphPositionIter::new(font_size_val, &glyphs, baseline_y); - for (uv_rect, glyph_rect) in atlas.uv_rects.into_iter().zip(glyph_pos_iter) { - //mesh.draw_outline(&glyph_rect, COLOR_BLUE, 2.); - mesh.draw_box(&glyph_rect, COLOR_WHITE, &uv_rect); - } - - let num_elements = mesh.num_elements(); - let (vertex_buffer, index_buffer) = mesh.alloc(&render_api).await.unwrap(); - - let sprites = glyphs.into_iter().map(|glyph| glyph.sprite).collect(); + let render_info = Self::regen_mesh( + &render_api, + &text_shaper, + text.get(), + font_size.get(), + color.get(), + baseline.get(), + ) + .await; let self_ = Arc::new_cyclic(|me: &Weak| { let mut on_modify = OnModify::new(ex, node_name, node_id, me.clone()); on_modify.when_change(rect.clone(), Self::redraw); - on_modify.when_change(z_index_prop, Self::redraw); - on_modify.when_change(text, Self::redraw); - on_modify.when_change(font_size, Self::redraw); - on_modify.when_change(color, Self::redraw); - on_modify.when_change(debug, Self::redraw); - on_modify.when_change(baseline, Self::redraw); + on_modify.when_change(z_index.prop(), Self::redraw); + on_modify.when_change(text.prop(), Self::redraw); + on_modify.when_change(font_size.prop(), Self::redraw); + on_modify.when_change(color.prop(), Self::redraw); + on_modify.when_change(debug.prop(), Self::redraw); + on_modify.when_change(baseline.prop(), Self::redraw); Self { sg, render_api, text_shaper, tasks: on_modify.tasks, - //glyphs: Mutex::new((glyphs, atlas)), - glyph_sprites: Mutex::new(sprites), - texture_id: atlas.texture_id, - vertex_buffer, - index_buffer, - num_elements, + render_info: Mutex::new(render_info), dc_key: OsRng.gen(), node_id, rect, z_index, + text, + font_size, + color, + baseline, + debug, } }); Pimpl::Text(self_) } + async fn regen_mesh( + render_api: &RenderApi, + text_shaper: &TextShaper, + text: String, + font_size: f32, + text_color: Color, + baseline: f32, + ) -> TextRenderInfo { + debug!(target: "ui::text", "Rendering label '{}'", text); + let glyphs = text_shaper.shape(text, font_size).await; + let atlas = text2::make_texture_atlas(render_api, font_size, &glyphs).await.unwrap(); + + let mut mesh = MeshBuilder::new(); + let mut glyph_pos_iter = GlyphPositionIter::new(font_size, &glyphs, baseline); + for ((uv_rect, glyph_rect), glyph) in + atlas.uv_rects.into_iter().zip(glyph_pos_iter).zip(glyphs.iter()) + { + //mesh.draw_outline(&glyph_rect, COLOR_BLUE, 2.); + let mut color = text_color.clone(); + if glyph.sprite.has_color { + color = COLOR_WHITE; + } + mesh.draw_box(&glyph_rect, color, &uv_rect); + } + + let mesh = mesh.alloc(&render_api).await.unwrap(); + + let glyph_sprites = glyphs.into_iter().map(|glyph| glyph.sprite).collect(); + + TextRenderInfo { glyph_sprites, mesh, texture_id: atlas.texture_id } + } + async fn redraw(self: Arc) { + let render_info = Self::regen_mesh( + &self.render_api, + &self.text_shaper, + self.text.get(), + self.font_size.get(), + self.color.get(), + self.baseline.get(), + ) + .await; + *self.render_info.lock().unwrap() = render_info; + let sg = self.sg.lock().await; let node = sg.get_node(self.node_id).unwrap(); @@ -143,11 +168,13 @@ impl Text { // Only used for debug messages let node = sg.get_node(self.node_id).unwrap(); + let render_info = self.render_info.lock().unwrap().clone(); + let mesh = DrawMesh { - vertex_buffer: self.vertex_buffer, - index_buffer: self.index_buffer, - texture: Some(self.texture_id), - num_elements: self.num_elements, + vertex_buffer: render_info.mesh.vertex_buffer, + index_buffer: render_info.mesh.index_buffer, + texture: Some(render_info.texture_id), + num_elements: render_info.mesh.num_elements, }; if let Err(err) = eval_rect(self.rect.clone(), parent_rect) {