diff --git a/bin/app/Makefile b/bin/app/Makefile index 8ec89beae..cf32f8495 100644 --- a/bin/app/Makefile +++ b/bin/app/Makefile @@ -22,7 +22,7 @@ DEBUG_FEATURES = --features=enable-filelog,enable-plugins #DEV_FEATURES = --features=enable-filelog,enable-netdebug,emulate-android #DEV_FEATURES = --features=enable-filelog,enable-netdebug,enable-plugins -DEV_FEATURES = --features=enable-plugins +DEV_FEATURES = --features=schema-test default: build-release ./darkfi-app @@ -82,7 +82,7 @@ assets/forest_720x1280.mp4: # Developer targets dev: $(SRC) fonts assets/forest_1920x1080.ivf - $(CARGO) lbuild $(DEV_FEATURES) + $(CARGO) lbuild --no-default-features $(DEV_FEATURES) -mv target/debug/darkfi-app . ./darkfi-app diff --git a/bin/app/src/app/mod.rs b/bin/app/src/app/mod.rs index 7930a26f9..7bcca61a8 100644 --- a/bin/app/src/app/mod.rs +++ b/bin/app/src/app/mod.rs @@ -260,37 +260,3 @@ impl App { }); } } - -// Just for testing -#[allow(dead_code)] -fn populate_tree(tree: &sled::Tree) { - let chat_txt = include_str!("../../data/chat.txt"); - for line in chat_txt.lines() { - let parts: Vec<&str> = line.splitn(3, ' ').collect(); - assert_eq!(parts.len(), 3); - let time_parts: Vec<&str> = parts[0].splitn(2, ':').collect(); - let (hour, min) = (time_parts[0], time_parts[1]); - let hour = hour.parse::().unwrap(); - let min = min.parse::().unwrap(); - let dt: NaiveDateTime = - NaiveDate::from_ymd_opt(2024, 8, 6).unwrap().and_hms_opt(hour, min, 0).unwrap(); - let timest = dt.and_utc().timestamp_millis() as u64; - - let nick = parts[1].to_string(); - let text = parts[2].to_string(); - - // serial order is important here - let timest = timest.to_be_bytes(); - assert_eq!(timest.len(), 8); - let mut key = [0u8; 8 + 32]; - key[..8].clone_from_slice(×t); - - let msg = chatview::ChatMsg { nick, text }; - let mut val = vec![]; - msg.encode(&mut val).unwrap(); - - tree.insert(&key, val).unwrap(); - } - // O(n) - d!("populated db with {} lines", tree.len()); -} diff --git a/bin/app/src/app/schema/chat.rs b/bin/app/src/app/schema/chat.rs index ec29e423e..5ac4c9e7c 100644 --- a/bin/app/src/app/schema/chat.rs +++ b/bin/app/src/app/schema/chat.rs @@ -1604,3 +1604,38 @@ pub async fn make( }); app.tasks.lock().unwrap().push(editz_text_task); } + +// Just for testing +#[allow(dead_code)] +pub(super) fn populate_tree(tree: &sled::Tree) { + use chrono::{NaiveDate, NaiveDateTime}; + let chat_txt = include_str!("../../../data/chat.txt"); + for line in chat_txt.lines() { + let parts: Vec<&str> = line.splitn(3, ' ').collect(); + assert_eq!(parts.len(), 3); + let time_parts: Vec<&str> = parts[0].splitn(2, ':').collect(); + let (hour, min) = (time_parts[0], time_parts[1]); + let hour = hour.parse::().unwrap(); + let min = min.parse::().unwrap(); + let dt: NaiveDateTime = + NaiveDate::from_ymd_opt(2024, 8, 6).unwrap().and_hms_opt(hour, min, 0).unwrap(); + let timest = dt.and_utc().timestamp_millis() as u64; + + let nick = parts[1].to_string(); + let text = parts[2].to_string(); + + // serial order is important here + let timest = timest.to_be_bytes(); + assert_eq!(timest.len(), 8); + let mut key = [0u8; 8 + 32]; + key[..8].clone_from_slice(×t); + + let msg = chatview::ChatMsg { nick, text }; + let mut val = vec![]; + msg.encode(&mut val).unwrap(); + + tree.insert(&key, val).unwrap(); + } + // O(n) + debug!(target: "app::schema", "populated db with {} lines", tree.len()); +} diff --git a/bin/app/src/app/schema/test.rs b/bin/app/src/app/schema/test.rs index 84634f2e3..90fcdebc4 100644 --- a/bin/app/src/app/schema/test.rs +++ b/bin/app/src/app/schema/test.rs @@ -16,33 +16,37 @@ * along with this program. If not, see . */ +use sled_overlay::sled; + use crate::{ app::{ node::{ - create_layer, create_singleline_edit, create_text, create_vector_art, create_video, + create_chatview, create_layer, create_singleline_edit, create_text, create_vector_art, + create_video, }, App, }, - expr, + expr::{self, Compiler}, mesh::COLOR_PURPLE, prop::{PropertyAtomicGuard, PropertyFloat32, Role}, scene::SceneNodePtr, - ui::{BaseEdit, BaseEditType, Layer, Text, VectorArt, VectorShape, Video}, + ui::{BaseEdit, BaseEditType, ChatView, Layer, Text, VectorArt, VectorShape, Video}, util::i18n::I18nBabelFish, }; +use super::chat::populate_tree; const LIGHTMODE: bool = false; #[cfg(target_os = "android")] mod ui_consts { - //pub const CHATDB_PATH: &str = "/data/data/darkfi.app/chatdb/"; + pub const CHATDB_PATH: &str = "/data/data/darkfi.app/chatdb/"; //pub const KING_PATH: &str = "king.png"; pub const VID_PATH: &str = "forest_720x1280.mp4"; } #[cfg(not(target_os = "android"))] mod ui_consts { - //pub const CHATDB_PATH: &str = "chatdb"; + pub const CHATDB_PATH: &str = "chatdb"; //pub const KING_PATH: &str = "assets/king.png"; pub const VID_PATH: &str = "assets/forest_1920x1080.ivf"; } @@ -61,6 +65,8 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) { ) .unwrap(); + let mut cc = Compiler::new(); + // Create a layer called view let layer_node = create_layer("view"); let prop = layer_node.get_property("rect").unwrap(); @@ -226,6 +232,7 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) { */ // Create KING GNU! + /* let node = create_video("king"); let prop = node.get_property("rect").unwrap(); prop.set_f32(atom, Role::App, 0, 80.).unwrap(); @@ -236,6 +243,7 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) { node.set_property_u32(atom, Role::App, "z_index", 1).unwrap(); let node = node.setup(|me| Video::new(me, app.render_api.clone(), app.ex.clone())).await; layer_node.link(node); + */ // Create some text let node = create_text("label"); @@ -261,13 +269,13 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) { prop.set_f32(atom, Role::App, 2, 0.).unwrap(); prop.set_f32(atom, Role::App, 3, 1.).unwrap(); node.set_property_u32(atom, Role::App, "z_index", 1).unwrap(); + node.set_property_bool(atom, Role::App, "debug", true).unwrap(); let node = node .setup(|me| Text::new(me, window_scale.clone(), app.render_api.clone(), i18n_fish.clone())) .await; layer_node.link(node); - /* // ChatView let node = create_chatview("chatty"); let prop = node.get_property("rect").unwrap(); @@ -318,7 +326,7 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) { 1.00, 0.30, 0.00, 1. ]; for c in nick_colors { - prop.push_f32(Role::App, c).unwrap(); + prop.push_f32(atom, Role::App, c).unwrap(); } let prop = node.get_property("hi_bg_color").unwrap(); @@ -348,13 +356,13 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) { window_scale.clone(), app.render_api.clone(), app.text_shaper.clone(), - app.ex.clone(), + app.sg_root.clone(), ) }) .await; layer_node.link(node); - */ + /* // Text edit let node = create_singleline_edit("editz"); //let node = create_multiline_edit("editz"); @@ -449,4 +457,5 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) { node.call_method("focus", vec![]).await.unwrap(); }); app.tasks.lock().unwrap().push(focus_task); + */ } diff --git a/bin/app/src/logger.rs b/bin/app/src/logger.rs index 9b2097c2a..d233a90c2 100644 --- a/bin/app/src/logger.rs +++ b/bin/app/src/logger.rs @@ -115,7 +115,7 @@ pub fn setup_logging() -> Option { #[cfg(not(target_os = "android"))] { let terminal_layer = tracing_subscriber::fmt::Layer::new() - .with_span_events(FmtSpan::ENTER | FmtSpan::CLOSE) + //.with_span_events(FmtSpan::ENTER | FmtSpan::CLOSE) .event_format(EventFormatter::new(true, true)) .fmt_fields(tracing_subscriber::fmt::format::debug_fn( darkfi::util::logger::terminal_field_formatter, diff --git a/bin/app/src/text2/render.rs b/bin/app/src/text2/render.rs index 0db5d9775..f04b0699a 100644 --- a/bin/app/src/text2/render.rs +++ b/bin/app/src/text2/render.rs @@ -78,6 +78,7 @@ pub fn render_layout_with_opts( } } } + // Render the atlas let atlas = atlas.make(); diff --git a/bin/app/src/ui/chatview/mod.rs b/bin/app/src/ui/chatview/mod.rs index 2cc80048b..ea48ff933 100644 --- a/bin/app/src/ui/chatview/mod.rs +++ b/bin/app/src/ui/chatview/mod.rs @@ -460,22 +460,24 @@ impl ChatView { return } + #[cfg(feature = "enable-plugins")] if let Some(url) = get_file_url(&text) { - if let Some(fud) = self.sg_root.lookup_node("/plugin/fud") { - msgbuf.insert_filemsg( - timest, - msg_id, - FileMessageStatus::Initializing, - nick, - url.clone(), - ); + // This is incorrect. Scenegraph paths should not be hardcoded in widgets + // nor should there be dependencies on other widgets. + // Instead use signals and slots through app layer. See how focus is done with + // the edit widgets. + let fud = self.sg_root.lookup_node("/plugin/fud").unwrap(); + msgbuf.insert_filemsg( + timest, + msg_id, + FileMessageStatus::Initializing, + nick, + url.clone(), + ); - let mut data = vec![]; - url.encode(&mut data).unwrap(); - fud.call_method("get", data).await.unwrap(); - } - } else { - error!(target: "ui::chatview", "Fud plugin has not been loaded"); + let mut data = vec![]; + url.encode(&mut data).unwrap(); + fud.call_method("get", data).await.unwrap(); } } @@ -587,11 +589,6 @@ impl ChatView { } }; - let Some(fud) = self.sg_root.lookup_node("/plugin/fud") else { - error!(target: "ui::chatview", "Fud plugin has not been loaded"); - return - }; - let mut do_redraw = false; for entry in iter { let Ok((k, v)) = entry else { break }; @@ -609,7 +606,10 @@ impl ChatView { chatmsg.text.clone(), ); + // See comment in handle_insert_line() + #[cfg(feature = "enable-plugins")] if let Some(url) = get_file_url(&chatmsg.text) { + let fud = self.sg_root.lookup_node("/plugin/fud").unwrap(); msgbuf.insert_filemsg( timest, msg_id, @@ -634,7 +634,6 @@ impl ChatView { } remaining_visible -= msg_height; } - //t!("do_redraw = {do_redraw} [trace_id={trace_id}]"); if do_redraw { let atom = self.render_api.make_guard(gfxtag!("ChatView::handle_bgload")); self.redraw_cached(atom.batch_id, &mut msgbuf).await; @@ -710,7 +709,7 @@ impl ChatView { let meshes = msgbuf.gen_meshes(rect, scroll).await; - for (y_pos, mesh) in meshes { + for (y_pos, mut minstrs) in meshes { // Apply scroll and scissor // We use the scissor for scrolling // Because we use the scissor, our actual rect is now rect instead of parent_rect @@ -720,7 +719,7 @@ impl ChatView { let pos = Point::from([off_x, off_y]); instrs.push(DrawInstruction::SetPos(pos)); - instrs.push(DrawInstruction::Draw(mesh)); + instrs.append(&mut minstrs); } instrs diff --git a/bin/app/src/ui/chatview/page.rs b/bin/app/src/ui/chatview/page.rs index 51c7518ca..c154a815d 100644 --- a/bin/app/src/ui/chatview/page.rs +++ b/bin/app/src/ui/chatview/page.rs @@ -35,12 +35,13 @@ use url::Url; use super::{max, MessageId, Timestamp}; use crate::{ - gfx::{gfxtag, DrawMesh, ManagedTexturePtr, Rectangle, RenderApi}, + gfx::{gfxtag, DrawInstruction, DrawMesh, ManagedTexturePtr, Point, Rectangle, RenderApi}, mesh::{ Color, MeshBuilder, COLOR_BLUE, COLOR_CYAN, COLOR_GREEN, COLOR_PINK, COLOR_RED, COLOR_WHITE, }, prop::{PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr}, text::{self, Glyph, GlyphPositionIter, TextShaper, TextShaperPtr}, + text2, util::enumerate_mut, }; @@ -379,34 +380,19 @@ impl std::fmt::Debug for PrivMessage { pub struct DateMessage { font_size: f32, window_scale: f32, - timestamp: Timestamp, - glyphs: Vec, - - atlas: text::RenderedAtlas, - mesh_cache: Option, + mesh_cache: Option>, } impl DateMessage { pub fn new( font_size: f32, window_scale: f32, - timestamp: Timestamp, - - text_shaper: &TextShaper, - render_api: &RenderApi, + _render_api: &RenderApi, ) -> Message { - let datestr = Self::datestr(timestamp); let timestamp = Self::timest_to_midnight(timestamp); - - let glyphs = text_shaper.shape(datestr, font_size, window_scale); - - let mut atlas = text::Atlas::new(render_api, gfxtag!("chatview_datemsg")); - atlas.push(&glyphs); - let atlas = atlas.make(); - - Message::Date(Self { font_size, window_scale, timestamp, glyphs, atlas, mesh_cache: None }) + Message::Date(Self { font_size, window_scale, timestamp, mesh_cache: None }) } fn datestr(timestamp: Timestamp) -> String { @@ -423,66 +409,60 @@ impl DateMessage { timestamp } - /// clear_mesh() must be called after this. fn adjust_params( &mut self, font_size: f32, window_scale: f32, - text_shaper: &TextShaper, - render_api: &RenderApi, + _text_shaper: &TextShaper, + _render_api: &RenderApi, ) { self.font_size = font_size; self.window_scale = window_scale; - - let datestr = Self::datestr(self.timestamp); - self.glyphs = text_shaper.shape(datestr, font_size, window_scale); - - let mut atlas = text::Atlas::new(render_api, gfxtag!("chatview_datemsg")); - atlas.push(&self.glyphs); - self.atlas = atlas.make(); - } - - //fn adjust_width(&mut self, line_width: f32) { } - - fn clear_mesh(&mut self) { - // Auto-deletes when refs are dropped self.mesh_cache = None; } - fn gen_mesh( + fn clear_mesh(&mut self) { + self.mesh_cache = None; + } + + async fn gen_mesh( &mut self, - clip: &Rectangle, + _clip: &Rectangle, line_height: f32, - baseline: f32, + _baseline: f32, _nick_colors: &[Color], timestamp_color: Color, _text_color: Color, - debug_render: bool, + _debug_render: bool, render_api: &RenderApi, - ) -> DrawMesh { - let mut mesh = MeshBuilder::new(gfxtag!("chatview_datemsg")); - - let glyph_pos_iter = - GlyphPositionIter::new(self.font_size, self.window_scale, &self.glyphs, baseline); - for (mut glyph_rect, glyph) in glyph_pos_iter.zip(self.glyphs.iter()) { - let uv_rect = self.atlas.fetch_uv(glyph.glyph_id).expect("missing glyph UV rect"); - glyph_rect.y -= line_height; - mesh.draw_box(&glyph_rect, timestamp_color, uv_rect); + ) -> Vec { + // Return cached mesh if available + if let Some(cache) = &self.mesh_cache { + return cache.clone() } - if debug_render { - mesh.draw_outline( - &Rectangle { x: 0., y: -line_height, w: clip.w, h: line_height }, - COLOR_PINK, - 1., - ); - } + let datestr = Self::datestr(self.timestamp); - let mesh = mesh.alloc(render_api); - let mesh = mesh.draw_with_textures(vec![self.atlas.texture.clone()]); - self.mesh_cache = Some(mesh.clone()); + let mut txt_ctx = text2::TEXT_CTX.get().await; + let layout = txt_ctx.make_layout( + &datestr, + timestamp_color, + self.font_size, + line_height / self.font_size, + self.window_scale, + None, + &[], + ); + drop(txt_ctx); - mesh + let mut txt_instrs = text2::render_layout(&layout, render_api, gfxtag!("chatview_datemsg")); + let mut instrs = Vec::with_capacity(1 + txt_instrs.len()); + instrs.push(DrawInstruction::Move(Point::new(0., -line_height))); + instrs.append(&mut txt_instrs); + + // Cache the instructions + self.mesh_cache = Some(instrs.clone()); + instrs } } @@ -854,7 +834,7 @@ impl Message { } } - fn gen_mesh( + async fn gen_mesh( &mut self, clip: &Rectangle, line_height: f32, @@ -867,44 +847,51 @@ impl Message { hi_bg_color: Color, debug_render: bool, render_api: &RenderApi, - ) -> Vec { + ) -> Vec { match self { - Self::Priv(m) => vec![m.gen_mesh( - clip, - line_height, - msg_spacing, - baseline, - timestamp_width, - nick_colors, - timestamp_color, - text_color, - hi_bg_color, - debug_render, - render_api, - )], - Self::Date(m) => vec![m.gen_mesh( - clip, - line_height, - baseline, - // No timestamp_width - nick_colors, - timestamp_color, - text_color, - // No hi_bg_color since dates can't be highlighted - debug_render, - render_api, - )], - Self::File(m) => m.gen_mesh( - clip, - line_height, - baseline, - timestamp_width, - nick_colors, - timestamp_color, - text_color, - debug_render, - render_api, - ), + Self::Priv(m) => { + let mesh = m.gen_mesh( + clip, + line_height, + msg_spacing, + baseline, + timestamp_width, + nick_colors, + timestamp_color, + text_color, + hi_bg_color, + debug_render, + render_api, + ); + vec![DrawInstruction::Draw(mesh)] + } + Self::Date(m) => { + m.gen_mesh( + clip, + line_height, + baseline, + nick_colors, + timestamp_color, + text_color, + debug_render, + render_api, + ) + .await + } + Self::File(m) => { + let meshes = m.gen_mesh( + clip, + line_height, + baseline, + timestamp_width, + nick_colors, + timestamp_color, + text_color, + debug_render, + render_api, + ); + meshes.into_iter().map(DrawInstruction::Draw).collect() + } } } @@ -1221,8 +1208,12 @@ impl MessageBuffer { msg_height } - /// Generate caches and return meshes - pub async fn gen_meshes(&mut self, rect: &Rectangle, scroll: f32) -> Vec<(f32, DrawMesh)> { + /// Generate caches and return draw instructions + pub async fn gen_meshes( + &mut self, + rect: &Rectangle, + scroll: f32, + ) -> Vec<(f32, Vec)> { let line_height = self.line_height.get(); let msg_spacing = self.msg_spacing.get(); let baseline = self.baseline.get(); @@ -1255,27 +1246,27 @@ impl MessageBuffer { continue } - for mesh in msg.gen_mesh( - rect, - line_height, - msg_spacing, - baseline, - timestamp_width, - &nick_colors, - timest_color, - text_color, - hi_bg_color, - debug_render, - &render_api, - ) { - meshes.push((current_pos, mesh)); - } + let instrs = msg + .gen_mesh( + rect, + line_height, + msg_spacing, + baseline, + timestamp_width, + &nick_colors, + timest_color, + text_color, + hi_bg_color, + debug_render, + &render_api, + ) + .await; + + meshes.push((current_pos, instrs)); current_pos += msg_spacing; current_pos += mesh_height; } - - //t!("gen_meshes() returning {} meshes", meshes.len()); meshes } @@ -1364,13 +1355,7 @@ impl MessageBuffer { let timest = Local.from_local_datetime(&dt).unwrap().timestamp_millis() as u64; if !self.date_msgs.contains_key(&date) { - let datemsg = DateMessage::new( - font_size, - window_scale, - timest, - &self.text_shaper, - &self.render_api, - ); + let datemsg = DateMessage::new(font_size, window_scale, timest, &self.render_api); self.date_msgs.insert(date, datemsg); } diff --git a/bin/app/src/ui/text.rs b/bin/app/src/ui/text.rs index 8a8dfb458..343b478f9 100644 --- a/bin/app/src/ui/text.rs +++ b/bin/app/src/ui/text.rs @@ -24,6 +24,7 @@ use tracing::instrument; use crate::{ gfx::{gfxtag, DrawCall, DrawInstruction, Rectangle, RenderApi}, + mesh::MeshBuilder, prop::{ BatchGuardPtr, PropertyAtomicGuard, PropertyBool, PropertyColor, PropertyFloat32, PropertyRect, PropertyStr, PropertyUint32, Role, @@ -158,6 +159,14 @@ impl Text { let mut instrs = vec![DrawInstruction::Move(rect.pos())]; instrs.append(&mut self.regen_mesh().await); + if self.debug.get() { + let rect = self.rect.get().with_zero_pos(); + let mut mesh = MeshBuilder::new(gfxtag!("text_debug-rect")); + mesh.draw_outline(&rect, [0., 1., 0., 0.7], 1.); + let mesh = mesh.alloc(&self.render_api).draw_untextured(); + instrs.push(DrawInstruction::Draw(mesh)); + } + Some(DrawUpdate { key: self.dc_key, draw_calls: vec![(