Browse Source

app/chatview: migrate FileMessage to text2 API

jkds 7 months ago
parent
commit
9d15053bee

+ 2 - 2
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:

+ 5 - 5
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();

+ 1 - 1
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";

+ 19 - 15
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;

+ 102 - 95
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<SyncMutex<Option<GenericImageBuffer>>>,
     timestamp: Timestamp,
-    glyphs: Vec<Vec<Glyph>>,
 
-    atlas: text::RenderedAtlas,
+    mesh_cache: Option<Vec<DrawInstruction>>,
 }
 
 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<Rgba<u8>, Vec<u8>>) -> (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<DrawMesh> {
-        let uv_rect = Rectangle::from([0., 0., 1., 1.]);
+    ) -> Vec<DrawInstruction> {
+        if let Some(instrs) = &self.mesh_cache {
+            return instrs.clone()
+        }
+
+        self.max_width = clip.w - timestamp_width;
 
-        let imgbuf_ = self.imgbuf.lock();
-        if let Some(ref imgbuf) = *imgbuf_ {
-            let (img_w, img_h) = self.get_img_size(imgbuf);
-            drop(imgbuf_);
+        // 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];
+            instrs.push(DrawInstruction::Draw(mesh_img.draw_with_textures(vec![texture])));
+
+            self.mesh_cache = Some(instrs.clone());
+            // Image is downloaded so return
+            return instrs;
         }
-        drop(imgbuf_);
 
-        let mut mesh = MeshBuilder::new(gfxtag!("chatview_filemsg"));
+        // 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<ImageBuffer<Rgba<u8>, Vec<u8>>> {
@@ -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
             }