Browse Source

app/chatview: migrate PrivMessage to text2 API

jkds 7 months ago
parent
commit
a46fc2963b
2 changed files with 111 additions and 206 deletions
  1. 27 0
      bin/app/src/text2/mod.rs
  2. 84 206
      bin/app/src/ui/chatview/page.rs

+ 27 - 0
bin/app/src/text2/mod.rs

@@ -105,6 +105,29 @@ impl TextContext {
         window_scale: f32,
         window_scale: f32,
         width: Option<f32>,
         width: Option<f32>,
         underlines: &[Range<usize>],
         underlines: &[Range<usize>],
+    ) -> parley::Layout<Color> {
+        self.make_layout2(
+            text,
+            text_color,
+            font_size,
+            lineheight,
+            window_scale,
+            width,
+            underlines,
+            &[],
+        )
+    }
+
+    pub fn make_layout2(
+        &mut self,
+        text: &str,
+        text_color: Color,
+        font_size: f32,
+        lineheight: f32,
+        window_scale: f32,
+        width: Option<f32>,
+        underlines: &[Range<usize>],
+        foreground_colors: &[(Range<usize>, Color)],
     ) -> parley::Layout<Color> {
     ) -> parley::Layout<Color> {
         let mut builder =
         let mut builder =
             self.layout_ctx.ranged_builder(&mut self.font_ctx, &text, window_scale, false);
             self.layout_ctx.ranged_builder(&mut self.font_ctx, &text, window_scale, false);
@@ -120,6 +143,10 @@ impl TextContext {
             builder.push(parley::StyleProperty::Underline(true), underline.clone());
             builder.push(parley::StyleProperty::Underline(true), underline.clone());
         }
         }
 
 
+        for (range, color) in foreground_colors {
+            builder.push(parley::StyleProperty::Brush(*color), range.clone());
+        }
+
         let mut layout: parley::Layout<Color> = builder.build(&text);
         let mut layout: parley::Layout<Color> = builder.build(&text);
         layout.break_all_lines(width);
         layout.break_all_lines(width);
         layout.align(width, parley::Alignment::Start, parley::AlignmentOptions::default());
         layout.align(width, parley::Alignment::Start, parley::AlignmentOptions::default());

+ 84 - 206
bin/app/src/ui/chatview/page.rs

@@ -70,12 +70,7 @@ pub struct PrivMessage {
 
 
     is_selected: bool,
     is_selected: bool,
 
 
-    time_glyphs: Vec<Glyph>,
-    unwrapped_glyphs: Vec<Glyph>,
-    wrapped_lines: Vec<Vec<Glyph>>,
-
-    atlas: text::RenderedAtlas,
-    mesh_cache: Option<DrawMesh>,
+    mesh_cache: Option<Vec<DrawInstruction>>,
 }
 }
 
 
 impl PrivMessage {
 impl PrivMessage {
@@ -89,27 +84,17 @@ impl PrivMessage {
         nick: String,
         nick: String,
         text: String,
         text: String,
 
 
-        line_width: f32,
-        timestamp_width: f32,
+        _line_width: f32,
+        _timestamp_width: f32,
 
 
-        text_shaper: &TextShaper,
-        render_api: &RenderApi,
+        _text_shaper: &TextShaper,
+        _render_api: &RenderApi,
     ) -> Message {
     ) -> Message {
-        let timestr = Self::gen_timestr(timestamp);
-        let time_glyphs = text_shaper.shape(timestr, timestamp_font_size, window_scale);
-
-        let linetext = if nick == "NOTICE" { text.clone() } else { format!("{nick} {text}") };
         if nick == "NOTICE" {
         if nick == "NOTICE" {
             font_size *= 0.8;
             font_size *= 0.8;
         }
         }
-        let unwrapped_glyphs = text_shaper.shape(linetext, font_size, window_scale);
-
-        let mut atlas = text::Atlas::new(render_api, gfxtag!("chatview_privmsg"));
-        atlas.push(&time_glyphs);
-        atlas.push(&unwrapped_glyphs);
-        let atlas = atlas.make();
 
 
-        let mut self_ = Self {
+        Message::Priv(Self {
             font_size,
             font_size,
             timestamp_font_size,
             timestamp_font_size,
             window_scale,
             window_scale,
@@ -119,14 +104,8 @@ impl PrivMessage {
             text,
             text,
             confirmed: true,
             confirmed: true,
             is_selected: false,
             is_selected: false,
-            time_glyphs,
-            unwrapped_glyphs,
-            wrapped_lines: vec![],
-            atlas,
             mesh_cache: None,
             mesh_cache: None,
-        };
-        self_.adjust_width(line_width, timestamp_width);
-        Message::Priv(self_)
+        })
     }
     }
 
 
     fn gen_timestr(timestamp: Timestamp) -> String {
     fn gen_timestr(timestamp: Timestamp) -> String {
@@ -136,226 +115,125 @@ impl PrivMessage {
     }
     }
 
 
     fn height(&self, line_height: f32) -> f32 {
     fn height(&self, line_height: f32) -> f32 {
-        self.wrapped_lines.len() as f32 * line_height
+        // TODO: calculate based on actual layout height
+        // For now, assume a single line
+        line_height
     }
     }
 
 
-    fn gen_mesh(
+    async fn gen_mesh(
         &mut self,
         &mut self,
         clip: &Rectangle,
         clip: &Rectangle,
         line_height: f32,
         line_height: f32,
         msg_spacing: f32,
         msg_spacing: f32,
-        baseline: f32,
+        _baseline: f32,
         timestamp_width: f32,
         timestamp_width: f32,
         nick_colors: &[Color],
         nick_colors: &[Color],
         timestamp_color: Color,
         timestamp_color: Color,
         text_color: Color,
         text_color: Color,
         hi_bg_color: Color,
         hi_bg_color: Color,
-        debug_render: bool,
+        _debug_render: bool,
         render_api: &RenderApi,
         render_api: &RenderApi,
-    ) -> DrawMesh {
-        if let Some(mesh) = &self.mesh_cache {
-            return mesh.clone()
+    ) -> Vec<DrawInstruction> {
+        if let Some(instrs) = &self.mesh_cache {
+            return instrs.clone()
         }
         }
 
 
-        //t!("gen_mesh({})", glyph_str(&self.unwrapped_glyphs));
-        let mut mesh = MeshBuilder::new(gfxtag!("chatview_privmsg"));
+        let mut all_instrs = vec![DrawInstruction::Move(Point::new(0., -line_height))];
 
 
+        // Draw selection background if selected
         if self.is_selected {
         if self.is_selected {
             let height = self.height(line_height) + msg_spacing;
             let height = self.height(line_height) + msg_spacing;
+            let mut mesh = MeshBuilder::new(gfxtag!("chatview_privmsg_sel"));
             mesh.draw_filled_box(
             mesh.draw_filled_box(
                 &Rectangle { x: 0., y: -height, w: clip.w, h: height },
                 &Rectangle { x: 0., y: -height, w: clip.w, h: height },
                 hi_bg_color,
                 hi_bg_color,
             );
             );
+            all_instrs
+                .push(DrawInstruction::Draw(mesh.alloc(render_api).draw_with_textures(vec![])));
         }
         }
 
 
-        self.render_timestamp(&mut mesh, baseline, line_height, timestamp_color);
-        let off_x = timestamp_width;
-
-        let nick_color = select_nick_color(&self.nick, nick_colors);
-
-        let last_idx = self.wrapped_lines.len() - 1;
-        for (i, line) in self.wrapped_lines.iter().rev().enumerate() {
-            let off_y = (i + 1) as f32 * line_height;
-            let is_last_line = i == last_idx;
-
-            // debug draw baseline
-            if debug_render {
-                let y = baseline - off_y;
-                mesh.draw_filled_box(&Rectangle { x: 0., y: y - 1., w: clip.w, h: 1. }, COLOR_BLUE);
-            }
-
-            self.render_line(
-                &mut mesh,
-                line,
-                off_x,
-                off_y,
-                is_last_line,
-                baseline,
-                nick_color,
-                text_color,
-                debug_render,
-            );
-        }
-
-        if debug_render {
-            let height = self.height(line_height);
-            mesh.draw_outline(
-                &Rectangle { x: 0., y: -height, w: clip.w, h: height },
-                COLOR_PINK,
-                1.,
-            );
-        }
-
-        let mesh = mesh.alloc(render_api);
-        let mesh = mesh.draw_with_textures(vec![self.atlas.texture.clone()]);
-        self.mesh_cache = Some(mesh.clone());
-
-        mesh
-    }
-
-    fn render_timestamp(
-        &self,
-        mesh: &mut MeshBuilder,
-        baseline: f32,
-        line_height: f32,
-        timestamp_color: Color,
-    ) {
-        let off_y = self.wrapped_lines.len() as f32 * line_height;
+        let mut txt_ctx = text2::TEXT_CTX.get().await;
 
 
-        let glyph_pos_iter = GlyphPositionIter::new(
+        // Timestamp layout
+        let timestr = Self::gen_timestr(self.timestamp);
+        let timestamp_layout = txt_ctx.make_layout(
+            &timestr,
+            timestamp_color,
             self.timestamp_font_size,
             self.timestamp_font_size,
+            line_height / self.timestamp_font_size,
             self.window_scale,
             self.window_scale,
-            &self.time_glyphs,
-            baseline,
+            None,
+            &[],
         );
         );
-        for (mut glyph_rect, glyph) in glyph_pos_iter.zip(self.time_glyphs.iter()) {
-            let uv_rect = self.atlas.fetch_uv(glyph.glyph_id).expect("missing glyph UV rect");
-            glyph_rect.y -= off_y;
-
-            mesh.draw_box(&glyph_rect, timestamp_color, uv_rect);
-        }
-    }
 
 
-    fn render_line(
-        &self,
-        mesh: &mut MeshBuilder,
-        line: &Vec<Glyph>,
-        off_x: f32,
-        off_y: f32,
-        is_last: bool,
-        baseline: f32,
-        nick_color: Color,
-        mut text_color: Color,
-        _debug_render: bool,
-    ) {
-        //debug!(target: "ui::chatview", "render_line({})", glyph_str(line));
-        // Keep track of the 'section'
-        // Section 0   is the nickname (colorized)
-        // Section >=1 is just the message itself
-        let mut section = 1;
-        if is_last && self.nick != "NOTICE" {
-            section = 0;
-        }
-
-        if self.nick == "NOTICE" {
-            text_color[0] = 0.35;
-            text_color[1] = 0.81;
-            text_color[2] = 0.89;
-        }
-
-        let glyph_pos_iter =
-            GlyphPositionIter::new(self.font_size, self.window_scale, line, baseline);
-        let Some(last_rect) = glyph_pos_iter.last() else { return };
-        let rhs = last_rect.rhs();
-        if self.nick == "NOTICE" {
-            mesh.draw_box(
-                &Rectangle::new(off_x, -off_y, rhs, baseline),
-                [0., 0.14, 0.16, 1.],
-                &Rectangle::zero(),
-            );
-        }
-
-        let glyph_pos_iter =
-            GlyphPositionIter::new(self.font_size, self.window_scale, line, baseline);
-        for (mut glyph_rect, glyph) in glyph_pos_iter.zip(line.iter()) {
-            let uv_rect = self.atlas.fetch_uv(glyph.glyph_id).expect("missing glyph UV rect");
-
-            glyph_rect.x += off_x;
-            glyph_rect.y -= off_y;
+        // Message text layout
+        let linetext = if self.nick == "NOTICE" {
+            self.text.clone()
+        } else {
+            format!("{} {}", self.nick, self.text)
+        };
 
 
-            let mut color = match section {
-                0 => nick_color,
-                _ => {
-                    if self.confirmed {
-                        text_color
-                    } else {
-                        UNCONF_COLOR
-                    }
-                }
-            };
+        let nick_color = select_nick_color(&self.nick, nick_colors);
 
 
-            //if debug_render {
-            //    mesh.draw_outline(&glyph_rect, COLOR_BLUE, 2.);
-            //}
+        let text_layout = if self.nick == "NOTICE" {
+            txt_ctx.make_layout(
+                &linetext,
+                text_color,
+                self.font_size,
+                line_height / self.font_size,
+                self.window_scale,
+                Some(clip.w - timestamp_width),
+                &[],
+            )
+        } else {
+            let body_color = if self.confirmed { text_color } else { UNCONF_COLOR };
+            let nick_end = self.nick.len() + 1;
+            txt_ctx.make_layout2(
+                &linetext,
+                body_color,
+                self.font_size,
+                line_height / self.font_size,
+                self.window_scale,
+                Some(clip.w - timestamp_width),
+                &[],
+                &[(0..nick_end, nick_color)],
+            )
+        };
 
 
-            if glyph.sprite.has_color {
-                color = COLOR_WHITE;
-            }
+        // Render timestamp
+        let timestamp_instrs =
+            text2::render_layout(&timestamp_layout, render_api, gfxtag!("chatview_privmsg_ts"));
+        all_instrs.extend(timestamp_instrs);
 
 
-            mesh.draw_box(&glyph_rect, color, uv_rect);
+        // Render message text offset by timestamp_width
+        all_instrs.push(DrawInstruction::Move(Point::new(timestamp_width, 0.)));
+        let text_instrs =
+            text2::render_layout(&text_layout, render_api, gfxtag!("chatview_privmsg_text"));
+        all_instrs.extend(text_instrs);
 
 
-            if is_last && section < 1 && is_whitespace(&glyph.substr) {
-                section += 1;
-            }
-        }
+        self.mesh_cache = Some(all_instrs.clone());
+        all_instrs
     }
     }
 
 
-    /// clear_mesh() must be called after this.
     fn adjust_params(
     fn adjust_params(
         &mut self,
         &mut self,
-        mut font_size: f32,
+        font_size: f32,
         timestamp_font_size: f32,
         timestamp_font_size: f32,
         window_scale: f32,
         window_scale: f32,
-        line_width: f32,
-        timestamp_width: f32,
-        text_shaper: &TextShaper,
-        render_api: &RenderApi,
+        _line_width: f32,
+        _timestamp_width: f32,
+        _text_shaper: &TextShaper,
+        _render_api: &RenderApi,
     ) {
     ) {
-        if self.nick == "NOTICE" {
-            font_size *= 0.8;
-        }
+        let font_size = if self.nick == "NOTICE" { font_size * 0.8 } else { font_size };
         self.font_size = font_size;
         self.font_size = font_size;
         self.timestamp_font_size = timestamp_font_size;
         self.timestamp_font_size = timestamp_font_size;
         self.window_scale = window_scale;
         self.window_scale = window_scale;
-
-        let timestr = Self::gen_timestr(self.timestamp);
-        self.time_glyphs = text_shaper.shape(timestr, timestamp_font_size, window_scale);
-
-        let linetext = if self.nick == "NOTICE" {
-            self.text.clone()
-        } else {
-            format!("{} {}", self.nick, self.text)
-        };
-        self.unwrapped_glyphs = text_shaper.shape(linetext, font_size, window_scale);
-
-        let mut atlas = text::Atlas::new(render_api, gfxtag!("chatview_privmsg"));
-        atlas.push(&self.time_glyphs);
-        atlas.push(&self.unwrapped_glyphs);
-        self.atlas = atlas.make();
-
-        // We need to rewrap the glyphs since they've been reloaded
-        self.adjust_width(line_width, timestamp_width);
     }
     }
 
 
-    /// clear_mesh() must be called after this.
-    fn adjust_width(&mut self, line_width: f32, timestamp_width: f32) {
-        let width = line_width - timestamp_width;
-        // clamp to > 0
-        let width = max(width, 0.);
-
-        // Invalidate wrapped_glyphs and recalc
-        self.wrapped_lines =
-            text::wrap(width, self.font_size, self.window_scale, &self.unwrapped_glyphs);
+    fn adjust_width(&mut self, _line_width: f32, _timestamp_width: f32) {
+        // Width changes affect wrapping, so clear the mesh cache
+        self.mesh_cache = None;
     }
     }
 
 
     fn clear_mesh(&mut self) {
     fn clear_mesh(&mut self) {
@@ -850,7 +728,7 @@ impl Message {
     ) -> Vec<DrawInstruction> {
     ) -> Vec<DrawInstruction> {
         match self {
         match self {
             Self::Priv(m) => {
             Self::Priv(m) => {
-                let mesh = m.gen_mesh(
+                m.gen_mesh(
                     clip,
                     clip,
                     line_height,
                     line_height,
                     msg_spacing,
                     msg_spacing,
@@ -862,8 +740,8 @@ impl Message {
                     hi_bg_color,
                     hi_bg_color,
                     debug_render,
                     debug_render,
                     render_api,
                     render_api,
-                );
-                vec![DrawInstruction::Draw(mesh)]
+                )
+                .await
             }
             }
             Self::Date(m) => {
             Self::Date(m) => {
                 m.gen_mesh(
                 m.gen_mesh(