Просмотр исходного кода

wallet: begin making a PageManager for chatview

darkfi 1 год назад
Родитель
Сommit
30d1184edb

+ 3 - 3
bin/darkwallet/src/app.rs

@@ -206,9 +206,9 @@ impl App {
         self.trigger_redraw().await;
 
         // Start the backend
-        if let Err(err) = self.darkirc_backend.start(self.sg.clone(), self.ex.clone()).await {
-            error!(target: "app", "backend error: {err}");
-        }
+        //if let Err(err) = self.darkirc_backend.start(self.sg.clone(), self.ex.clone()).await {
+        //    error!(target: "app", "backend error: {err}");
+        //}
     }
 
     pub fn stop(&self) {

+ 12 - 3
bin/darkwallet/src/ui/chatview.rs → bin/darkwallet/src/ui/chatview/mod.rs

@@ -31,6 +31,8 @@ use std::{
     sync::{atomic::Ordering, Arc, Mutex as SyncMutex, Weak},
 };
 
+mod page;
+
 use crate::{
     gfx::{
         DrawCall, DrawInstruction, DrawMesh, GraphicsEventPublisherPtr, Point, Rectangle,
@@ -257,6 +259,7 @@ pub struct ChatView {
     tree: sled::Tree,
 
     pages: AsyncMutex<Vec<PagePtr>>,
+    pages2: AsyncMutex<page::PageManager>,
     dc_key: u64,
 
     /// Used for detecting when scrolling view
@@ -389,11 +392,12 @@ impl ChatView {
                 node_id,
                 tasks,
                 sg,
-                render_api,
-                text_shaper,
+                render_api: render_api.clone(),
+                text_shaper: text_shaper.clone(),
                 tree,
 
                 pages: AsyncMutex::new(Vec::new()),
+                pages2: AsyncMutex::new(page::PageManager::new(render_api, text_shaper)),
                 dc_key: OsRng.gen(),
 
                 mouse_pos: SyncMutex::new(Point::from([0., 0.])),
@@ -662,6 +666,11 @@ impl ChatView {
             return
         }
 
+        // Add message to page
+        self.pages2.lock().await
+            .insert_line(self.font_size.get(), timest, message_id, nick.clone(), text.clone())
+            .await;
+
         let chatmsg = ChatMsg { nick, text };
 
         let dt = Local.timestamp_opt(timest as i64, 0).unwrap();
@@ -973,12 +982,12 @@ impl ChatView {
         while total_height < *scroll + rect.h {
             debug!(target: "ui::chatview", "draw_cached() loading more pages");
 
-            // No more pages available to load
             let n_loaded_pages = self.preload_pages().await;
 
             // We need this value after so first update it
             total_height = self.get_total_height(&rect, &pages).await;
 
+            // No more pages available to load
             if n_loaded_pages == 0 {
                 break
             }

+ 282 - 0
bin/darkwallet/src/ui/chatview/page.rs

@@ -0,0 +1,282 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2024 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use async_lock::Mutex as AsyncMutex;
+use chrono::{Local, TimeZone};
+use miniquad::{BufferId, TextureId};
+use std::{
+    hash::{DefaultHasher, Hash, Hasher},
+    io::Cursor,
+    sync::{atomic::Ordering, Arc, Mutex as SyncMutex, Weak},
+};
+
+use crate::{
+    gfx::{
+        DrawCall, DrawInstruction, DrawMesh, GraphicsEventPublisherPtr, Point, Rectangle,
+        RenderApi, RenderApiPtr,
+    },
+    mesh::{Color, MeshBuilder, COLOR_BLUE, COLOR_GREEN},
+    prop::{PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr, PropertyUint32, Role},
+    pubsub::Subscription,
+    scene::{Pimpl, SceneGraph, SceneGraphPtr2, SceneNodeId},
+    text::{self, Glyph, GlyphPositionIter, TextShaper, TextShaperPtr},
+    ExecutorPtr,
+};
+
+const PAGE_SIZE: usize = 10;
+const PRELOAD_PAGES: usize = 10;
+
+type Timestamp = u64;
+type MessageId = [u8; 32];
+
+#[derive(Clone)]
+struct Message {
+    font_size: f32,
+
+    timestamp: Timestamp,
+    id: MessageId,
+    nick: String,
+    text: String,
+
+    unwrapped_glyphs: Vec<Glyph>,
+    wrapped_lines: Vec<Vec<Glyph>>,
+    line_width: f32,
+}
+
+impl Message {
+    async fn new(
+        font_size: f32,
+
+        timestamp: Timestamp,
+        id: MessageId,
+        nick: String,
+        text: String,
+
+        text_shaper: &TextShaper,
+    ) -> Self {
+        let dt = Local.timestamp_opt(timestamp as i64, 0).unwrap();
+        let timestr = dt.format("%H:%M").to_string();
+
+        let linetext = format!("{} {} {}", timestr, nick, text);
+        let unwrapped_glyphs = text_shaper.shape(linetext, font_size).await;
+
+        Self {
+            font_size,
+            timestamp,
+            id,
+            nick,
+            text,
+            unwrapped_glyphs,
+            wrapped_lines: vec![],
+            line_width: 0.,
+        }
+    }
+
+    fn adjust_line_width(&mut self, line_width: f32) {
+        if (line_width - self.line_width).abs() < f32::EPSILON {
+            return;
+        }
+
+        // Invalidate wrapped_glyphs and recalc
+        self.wrapped_lines = text::wrap(line_width, self.font_size, &self.unwrapped_glyphs);
+        self.line_width = line_width;
+    }
+}
+
+#[derive(Clone)]
+struct PageMeshInfo {
+    px_height: f32,
+    mesh: DrawMesh,
+}
+
+struct Page {
+    msgs: Vec<Message>,
+    atlas: text::RenderedAtlas,
+    mesh: Option<PageMeshInfo>,
+}
+
+impl Page {
+    async fn new(msgs: Vec<Message>, render_api: &RenderApi) -> Self {
+        let mut atlas = text::Atlas::new(render_api);
+        for msg in &msgs {
+            atlas.push(&msg.unwrapped_glyphs);
+        }
+        let atlas = atlas.make().await.expect("unable to make atlas!");
+
+        Self { msgs, atlas, mesh: None }
+    }
+
+    fn invalidate(&mut self) -> Option<DrawMesh> {
+        std::mem::replace(&mut self.mesh, None).map(|m| m.mesh)
+    }
+
+    async fn split(mut msgs: Vec<Message>, render_api: &RenderApi) -> Vec<Self> {
+        msgs.sort_unstable_by_key(|msg| msg.timestamp);
+        msgs.reverse();
+
+        let chunk_size = if msgs.len() > PAGE_SIZE {
+            // Round up so we don't get a weird page with a single item
+            msgs.len() / 2 + 1
+        } else {
+            PAGE_SIZE
+        };
+
+        // Replace single page with N pages each with chunk_size messages
+        let mut new_pages = vec![];
+        for page_msgs in msgs.chunks(chunk_size).map(|m| m.to_vec()) {
+            debug!(target: "ui::chatview", "PAGE ==========================");
+            for msg in &page_msgs {
+                debug!(target: "ui::chatview", "{} {:?}", msg.timestamp, msg.text);
+            }
+            debug!(target: "ui::chatview", "===============================");
+
+            let new_page = Page::new(page_msgs, render_api).await;
+            new_pages.push(new_page);
+        }
+        assert!(!new_pages.is_empty());
+        assert!(new_pages.len() <= 2);
+        new_pages
+    }
+}
+
+struct FreedData {
+    buffers: Vec<BufferId>,
+    textures: Vec<TextureId>,
+}
+
+impl FreedData {
+    fn add(&mut self, mesh: DrawMesh) {
+        self.buffers.push(mesh.vertex_buffer);
+        self.buffers.push(mesh.index_buffer);
+        if let Some(texture_id) = mesh.texture {
+            self.textures.push(texture_id);
+        }
+    }
+}
+
+pub struct PageManager {
+    pages: Vec<Page>,
+    freed: FreedData,
+    render_api: RenderApiPtr,
+    text_shaper: TextShaperPtr,
+}
+
+impl PageManager {
+    pub fn new(
+    render_api: RenderApiPtr,
+        text_shaper: TextShaperPtr) -> Self {
+        Self { pages: vec![], freed: FreedData { buffers: vec![], textures: vec![] }, render_api, text_shaper }
+    }
+
+    /// For scrolling we want to be able to adjust and measure without
+    /// explicitly rendering since it may be off screen.
+    fn adjust_line_width(&mut self, line_width: f32) {
+        for page in &mut self.pages {
+            for msg in &mut page.msgs {
+                msg.adjust_line_width(line_width);
+            }
+        }
+    }
+
+    fn calc_total_height(&self) -> f32 {
+        0.
+    }
+
+    pub(super) async fn insert_line(
+        &mut self,
+        font_size: f32,
+        timestamp: Timestamp,
+        message_id: MessageId,
+        nick: String,
+        text: String,
+    ) {
+        let msg =
+            Message::new(font_size, timestamp, message_id, nick, text, &self.text_shaper).await;
+
+        // Now add message to page
+
+        // Maybe we can write this code below better
+        if self.pages.is_empty() {
+            let page = Page::new(vec![msg], &self.render_api).await;
+            self.pages.push(page);
+            return;
+        }
+
+        let mut idx = None;
+        for (i, page) in self.pages.iter_mut().enumerate() {
+            //let first_timest = page.msgs.last().unwrap().timest;
+            let last_timest = page.msgs.first().unwrap().timestamp;
+
+            //debug!(target: "ui::chatview", "page {i} [{first_timest}, {last_timest}]");
+            if timestamp <= last_timest {
+                //debug!(target: "ui::chatview", "found page {i} [{first_timest}, {last_timest}]");
+                idx = Some(i);
+                break
+            }
+        }
+
+        let Some(idx) = idx else {
+            // Add to the end
+            let page = Page::new(vec![msg], &self.render_api).await;
+            self.pages.push(page);
+            return
+        };
+
+        let old_pages_len = self.pages.len();
+        // We now want to replace this page by 1 or 2 pages
+        // Split pages into 3 parts: head, replaced page and tail
+        let mut head = std::mem::replace(&mut self.pages, vec![]);
+        let mut drain_iter = head.drain(idx..);
+        // Drop the item at idx which will be replaced
+        let old_page = drain_iter.next().unwrap();
+        let mut tail: Vec<_> = drain_iter.collect();
+        assert_eq!(old_pages_len, head.len() + 1 + tail.len());
+
+        let mut msgs = old_page.msgs;
+        msgs.push(msg);
+        let mut new_pages = Page::split(msgs, &self.render_api).await;
+
+        self.pages.append(&mut head);
+        self.pages.append(&mut new_pages);
+        self.pages.append(&mut tail);
+    }
+
+    /// Clear all meshes and caches. Returns data that needs to be freed.
+    fn invalidate_caches(&mut self) {
+        for page in &mut self.pages {
+            if let Some(mesh) = page.invalidate() {
+                self.freed.add(mesh);
+            }
+        }
+    }
+
+    /// Generate caches and return meshes
+    pub(super) async fn get_meshes(
+        &self,
+        clip: &Rectangle,
+        render_api: &RenderApi,
+        font_size: f32,
+        line_height: f32,
+        baseline: f32,
+        nick_colors: &[Color],
+        timestamp_color: Color,
+        text_color: Color,
+        debug_render: bool,
+    ) {
+    }
+}