diff --git a/bin/app/Makefile b/bin/app/Makefile index c03739796..5b8db947e 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 = --no-default-features --features=schema-app +DEV_FEATURES = --features=schema-app default: build-release ./darkfi-app @@ -88,7 +88,7 @@ dev: $(SRC) fonts assets/forest_1920x1080.ivf # Users should use the android-release and android-debug targets instead. apk: $(SRC) fonts assets/forest_720x1280.mp4 - cargo quad-apk build $(DEV_FEATURES) + cargo quad-apk build --no-default-features $(DEV_FEATURES) $(MAKE) install-apk install-apk: diff --git a/bin/app/src/app/schema/chat.rs b/bin/app/src/app/schema/chat.rs index 5ac4c9e7c..ff423a928 100644 --- a/bin/app/src/app/schema/chat.rs +++ b/bin/app/src/app/schema/chat.rs @@ -76,7 +76,7 @@ mod android_ui_consts { pub const SENDARROW_NEG_X: f32 = 80.; pub const SENDARROW_NEG_Y: f32 = 80.; pub const SENDBTN_BOX: [f32; 4] = [116., 120., 80., 70.]; - pub const FONTSIZE: f32 = 40.; + pub const FONTSIZE: f32 = 50.; pub const TIMESTAMP_FONTSIZE: f32 = 30.; pub const TIMESTAMP_WIDTH: f32 = 135.; pub const MESSAGE_SPACING: f32 = 15.; @@ -143,7 +143,7 @@ mod ui_consts { pub const SENDARROW_NEG_X: f32 = 50.; pub const SENDARROW_NEG_Y: f32 = 32.; pub const SENDBTN_BOX: [f32; 4] = [72., 50., 45., 34.]; - pub const FONTSIZE: f32 = 20.; + pub const FONTSIZE: f32 = 25.; pub const TIMESTAMP_FONTSIZE: f32 = 12.; pub const TIMESTAMP_WIDTH: f32 = 60.; pub const MESSAGE_SPACING: f32 = 5.; @@ -373,7 +373,7 @@ pub async fn make( prop.set_f32(atom, Role::App, 1, CHANNEL_LABEL_Y).unwrap(); prop.set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap(); prop.set_f32(atom, Role::App, 3, CHATEDIT_HEIGHT).unwrap(); - node.set_property_f32(atom, Role::App, "font_size", FONTSIZE * 1.2).unwrap(); + node.set_property_f32(atom, Role::App, "font_size", FONTSIZE).unwrap(); node.set_property_str(atom, Role::App, "text", &("#".to_string() + channel)).unwrap(); //node.set_property_bool(atom, Role::App, "debug", true).unwrap(); //node.set_property_str(atom, Role::App, "text", "anon1").unwrap(); @@ -719,7 +719,7 @@ pub async fn make( prop.set_f32(atom, Role::App, 3, 0.).unwrap(); node.set_property_f32(atom, Role::App, "baseline", TEXTBAR_BASELINE).unwrap(); - node.set_property_f32(atom, Role::App, "font_size", FONTSIZE * 1.2).unwrap(); + node.set_property_f32(atom, Role::App, "font_size", FONTSIZE).unwrap(); //node.set_property_str(atom, Role::App, "text", "hello king!๐Ÿ˜๐Ÿ†jelly ๐Ÿ†1234").unwrap(); let prop = node.get_property("text_color").unwrap(); if COLOR_SCHEME == ColorScheme::PaperLight { @@ -1325,7 +1325,7 @@ pub async fn make( prop.set_f32(atom, Role::App, 1, ACTION_LABEL_POS.y).unwrap(); prop.set_f32(atom, Role::App, 2, ACTION_SELECT_ALL_RECT.rhs()).unwrap(); prop.set_f32(atom, Role::App, 3, ACTION_SELECT_ALL_RECT.h).unwrap(); - node.set_property_f32(atom, Role::App, "font_size", FONTSIZE * 1.24).unwrap(); + node.set_property_f32(atom, Role::App, "font_size", FONTSIZE).unwrap(); node.set_property_str(atom, Role::App, "text", "copy paste select all").unwrap(); //node.set_property_bool(atom, Role::App, "debug", true).unwrap(); //node.set_property_str(atom, Role::App, "text", "anon1").unwrap(); diff --git a/bin/app/src/app/schema/test.rs b/bin/app/src/app/schema/test.rs index e04c5e939..ee95c248f 100644 --- a/bin/app/src/app/schema/test.rs +++ b/bin/app/src/app/schema/test.rs @@ -54,7 +54,7 @@ mod ui_consts { use std::path::PathBuf; pub fn get_chatdb_path() -> PathBuf { - "chatdb" + "chatdb".into() } //pub const KING_PATH: &str = "assets/king.png"; pub const VID_PATH: &str = "assets/forest_1920x1080.ivf"; diff --git a/bin/app/src/ui/chatview/mod.rs b/bin/app/src/ui/chatview/mod.rs index ea48ff933..7ae822ad2 100644 --- a/bin/app/src/ui/chatview/mod.rs +++ b/bin/app/src/ui/chatview/mod.rs @@ -460,13 +460,7 @@ impl ChatView { return } - #[cfg(feature = "enable-plugins")] if let Some(url) = get_file_url(&text) { - // 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, @@ -475,9 +469,17 @@ impl ChatView { url.clone(), ); - let mut data = vec![]; - url.encode(&mut data).unwrap(); - fud.call_method("get", data).await.unwrap(); + // 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. + #[cfg(feature = "enable-plugins")] + { + let mut data = vec![]; + url.encode(&mut data).unwrap(); + let fud = self.sg_root.lookup_node("/plugin/fud").unwrap(); + fud.call_method("get", data).await.unwrap(); + } } } @@ -606,10 +608,7 @@ 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, @@ -618,9 +617,14 @@ impl ChatView { url.clone(), ); - let mut data = vec![]; - url.encode(&mut data).unwrap(); - fud.call_method("get", data).await.unwrap(); + // See comment in handle_insert_line() + #[cfg(feature = "enable-plugins")] + { + let mut data = vec![]; + url.encode(&mut data).unwrap(); + let fud = self.sg_root.lookup_node("/plugin/fud").unwrap(); + fud.call_method("get", data).await.unwrap(); + } } remaining_load_height -= msg_height; diff --git a/bin/app/src/ui/chatview/page.rs b/bin/app/src/ui/chatview/page.rs index 7aaf56266..3f410bdc9 100644 --- a/bin/app/src/ui/chatview/page.rs +++ b/bin/app/src/ui/chatview/page.rs @@ -35,12 +35,10 @@ use url::Url; use super::{max, MessageId, Timestamp}; use crate::{ - gfx::{gfxtag, DrawInstruction, DrawMesh, ManagedTexturePtr, Point, Rectangle, RenderApi}, - mesh::{ - Color, MeshBuilder, COLOR_BLUE, COLOR_CYAN, COLOR_GREEN, COLOR_PINK, COLOR_RED, COLOR_WHITE, - }, + gfx::{gfxtag, DrawInstruction, ManagedTexturePtr, Point, Rectangle, RenderApi}, + mesh::{Color, MeshBuilder, COLOR_CYAN, COLOR_GREEN, COLOR_RED, COLOR_WHITE}, prop::{PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr}, - text::{self, Glyph, GlyphPositionIter, TextShaper, TextShaperPtr}, + text::{TextShaper, TextShaperPtr}, text2, util::enumerate_mut, }; @@ -372,12 +370,12 @@ pub struct FileMessage { status: FileMessageStatus, imgbuf: Arc>>, timestamp: Timestamp, - glyphs: Vec>, - atlas: text::RenderedAtlas, + mesh_cache: Option>, } impl FileMessage { + // This is not portable across devices and will break const GLOW_SIZE: f32 = 20.; const MARGIN_TOP: f32 = 4.; const MARGIN_BOTTOM: f32 = 10.; @@ -395,20 +393,9 @@ impl FileMessage { timestamp: Timestamp, _nick: String, - text_shaper: &TextShaper, - render_api: &RenderApi, + _text_shaper: &TextShaper, + _render_api: &RenderApi, ) -> Message { - let mut glyphs = Vec::new(); - let mut atlas = text::Atlas::new(render_api, gfxtag!("chatview_filemsg")); - - for str in Self::filestr(&file_url, &status) { - let glyphs_ = text_shaper.shape(str, font_size, window_scale); - atlas.push(&glyphs_); - glyphs.push(glyphs_); - } - - let atlas = atlas.make(); - Message::File(Self { font_size, window_scale, @@ -417,8 +404,7 @@ impl FileMessage { status, imgbuf: Arc::new(SyncMutex::new(None)), timestamp, - glyphs, - atlas, + mesh_cache: None, }) } @@ -460,31 +446,19 @@ impl FileMessage { &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; - - self.glyphs = Vec::new(); - let mut atlas = text::Atlas::new(render_api, gfxtag!("chatview_filemsg")); - - for str in Self::filestr(&self.file_url, &self.status) { - let glyphs = text_shaper.shape(str, font_size, window_scale); - atlas.push(&glyphs); - self.glyphs.push(glyphs); - } - - self.atlas = atlas.make(); + self.mesh_cache = None; } - fn adjust_width(&mut self, line_width: f32, timestamp_width: f32) { - let width = line_width - timestamp_width; - // clamp to > 0 - self.max_width = max(width, 0.); - } + fn adjust_width(&mut self, line_width: f32, timestamp_width: f32) {} - fn clear_mesh(&mut self) {} + fn clear_mesh(&mut self) { + self.mesh_cache = None; + } fn get_img_size(&self, imgbuf: &ImageBuffer, Vec>) -> (f32, f32) { let img_w = imgbuf.width() as f32; @@ -497,25 +471,34 @@ impl FileMessage { (img_w * scale, img_h * scale) } - fn gen_mesh( + async fn gen_mesh( &mut self, - _clip: &Rectangle, + clip: &Rectangle, line_height: f32, + msg_spacing: f32, baseline: f32, timestamp_width: f32, _nick_colors: &[Color], timestamp_color: Color, _text_color: Color, + _hi_bg_color: Color, _debug_render: bool, render_api: &RenderApi, - ) -> Vec { - let uv_rect = Rectangle::from([0., 0., 1., 1.]); + ) -> Vec { + if let Some(instrs) = &self.mesh_cache { + return instrs.clone() + } - let imgbuf_ = self.imgbuf.lock(); - if let Some(ref imgbuf) = *imgbuf_ { - let (img_w, img_h) = self.get_img_size(imgbuf); - drop(imgbuf_); + self.max_width = clip.w - timestamp_width; + // Extract image size while holding lock, then drop it + let mut img_size = None; + if let Some(img) = &*self.imgbuf.lock() { + img_size = Some(self.get_img_size(img)); + } + + // Lock is dropped here, safe to await now + if let Some((img_w, img_h)) = img_size { let mesh_rect = Rectangle::from([timestamp_width, -img_h - Self::MARGIN_BOTTOM, img_w, img_h]); let texture = self.load_texture(render_api); @@ -524,17 +507,24 @@ impl FileMessage { mesh_gradient.draw_box_shadow(&mesh_rect, glow_color, Self::GLOW_SIZE); let mesh_gradient = mesh_gradient.alloc(render_api); - let mesh_gradient = mesh_gradient.draw_untextured(); + let mut instrs = vec![DrawInstruction::Draw(mesh_gradient.draw_untextured())]; let mut mesh_img = MeshBuilder::new(gfxtag!("file_img")); + let uv_rect = Rectangle::from([0., 0., 1., 1.]); mesh_img.draw_box(&mesh_rect, COLOR_WHITE, &uv_rect); let mesh_img = mesh_img.alloc(render_api); - let mesh_img = mesh_img.draw_with_textures(vec![texture]); - return vec![mesh_img, mesh_gradient]; - } - drop(imgbuf_); + instrs.push(DrawInstruction::Draw(mesh_img.draw_with_textures(vec![texture]))); - let mut mesh = MeshBuilder::new(gfxtag!("chatview_filemsg")); + self.mesh_cache = Some(instrs.clone()); + // Image is downloaded so return + return instrs; + } + + // Image is not downloaded yet + + let mut all_instrs = vec![]; + + // Draw background box let color = match self.status { FileMessageStatus::Initializing => timestamp_color, @@ -542,43 +532,55 @@ impl FileMessage { FileMessageStatus::Downloaded { .. } => COLOR_GREEN, FileMessageStatus::Error { .. } => COLOR_RED, }; + let box_height = 2. * line_height + Self::BOX_PADDING_TOP + Self::BOX_PADDING_BOTTOM; - let mut text_width = 0.; - for (i, glyphs) in self.glyphs.iter().enumerate() { - let glyph_pos_iter = - GlyphPositionIter::new(self.font_size, self.window_scale, &glyphs, baseline); - for (mut glyph_rect, glyph) in glyph_pos_iter.zip(glyphs.iter()) { - let uv_rect = self.atlas.fetch_uv(glyph.glyph_id).expect("missing glyph UV rect"); - if glyph_rect.x + glyph_rect.w > text_width { - text_width = glyph_rect.x + glyph_rect.w; - } - glyph_rect.x += timestamp_width + Self::BOX_PADDING_X; - glyph_rect.y -= line_height * (self.glyphs.len() - i) as f32 + - Self::BOX_PADDING_BOTTOM + - Self::MARGIN_BOTTOM; - mesh.draw_box(&glyph_rect, color, uv_rect); - } - } - - let box_width = text_width + Self::BOX_PADDING_X * 2.; - let box_height = self.glyphs.len() as f32 * line_height + - Self::BOX_PADDING_TOP + - Self::BOX_PADDING_BOTTOM; - let mesh_rect = Rectangle::from([ + let mut mesh = MeshBuilder::new(gfxtag!("chatview_filemsg_box")); + let box_width = self.max_width + Self::BOX_PADDING_X * 2.; + let mesh_rect = Rectangle::new( timestamp_width, - -box_height - Self::MARGIN_BOTTOM, + -box_height + Self::MARGIN_BOTTOM, box_width, box_height, - ]); + ); mesh.draw_outline(&mesh_rect, color, 1.); let glow_color = [color[0], color[1], color[2], 0.3]; mesh.draw_box_shadow(&mesh_rect, glow_color, Self::GLOW_SIZE); - let mesh = mesh.alloc(render_api); - let mesh = mesh.draw_with_textures(vec![self.atlas.texture.clone()]); - vec![mesh] + all_instrs.push(DrawInstruction::Draw(mesh.draw_untextured())); + + let file_strs = Self::filestr(&self.file_url, &self.status); + + let mut layouts = Vec::with_capacity(file_strs.len()); + let mut txt_ctx = text2::TEXT_CTX.get().await; + for (i, file_str) in file_strs.iter().enumerate() { + let layout = txt_ctx.make_layout( + file_str, + color, + self.font_size, + line_height / self.font_size, + self.window_scale, + Some(self.max_width), + &[], + ); + layouts.push(layout); + } + drop(txt_ctx); + + all_instrs + .push(DrawInstruction::Move(Point::new(timestamp_width + Self::BOX_PADDING_X, 0.))); + let mut text_y_offset = 0.; + for (i, (file_str, layout)) in file_strs.iter().zip(layouts.into_iter()).enumerate() { + all_instrs.push(DrawInstruction::Move(Point::new(0., -line_height))); + + let instrs = + text2::render_layout(&layout, render_api, gfxtag!("chatview_filemsg_text")); + all_instrs.extend(instrs); + } + + self.mesh_cache = Some(all_instrs.clone()); + all_instrs } fn load_img(&self) -> Option, Vec>> { @@ -625,16 +627,20 @@ impl FileMessage { pub fn height(&self, line_height: f32) -> f32 { let imgbuf = self.imgbuf.lock(); - imgbuf - .as_ref() - .map(|buf| self.get_img_size(buf).1 as f32 + Self::MARGIN_TOP + Self::MARGIN_BOTTOM) - .unwrap_or( - line_height * self.glyphs.len() as f32 + - Self::BOX_PADDING_TOP + - Self::BOX_PADDING_BOTTOM + - Self::MARGIN_TOP + - Self::MARGIN_BOTTOM, - ) + // If image is downloaded, return image height plus margins + if let Some(buf) = &*imgbuf { + let img_height = self.get_img_size(buf).1; + return img_height + Self::MARGIN_TOP + Self::MARGIN_BOTTOM; + } + drop(imgbuf); + + // No image yet, so calculate height for text box + // filestr() always returns 2 lines: [file_hash, status_string] + let text_height = 2. * line_height; + let box_padding = Self::BOX_PADDING_TOP + Self::BOX_PADDING_BOTTOM; + let margins = Self::MARGIN_TOP + Self::MARGIN_BOTTOM; + + text_height //+ box_padding + margins } fn select(&mut self) {} @@ -757,18 +763,20 @@ impl Message { .await } Self::File(m) => { - let meshes = m.gen_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, - ); - meshes.into_iter().map(DrawInstruction::Draw).collect() + ) + .await } } } @@ -1279,7 +1287,6 @@ impl MessageBuffer { } msg.select(); - msg.clear_mesh(); break }