Răsfoiți Sursa

wallet: chatview proper preloading of pages and inserting lines

darkfi 1 an în urmă
părinte
comite
5e991ba50c

+ 1 - 0
bin/darkwallet/Cargo.toml

@@ -52,6 +52,7 @@ halo2_gadgets = {git="https://github.com/parazyd/halo2", branch="v4"}
 [target.'cfg(target_os = "android")'.dependencies]
 [target.'cfg(target_os = "android")'.dependencies]
 android_logger = "0.13.3"
 android_logger = "0.13.3"
 #openssl = { version = "*", features = ["vendored"] }
 #openssl = { version = "*", features = ["vendored"] }
+rusqlite = {version = "0.31.0", features = ["bundled"]}
 
 
 [target.'cfg(target_os = "linux")'.dependencies]
 [target.'cfg(target_os = "linux")'.dependencies]
 simplelog = "0.12.1"
 simplelog = "0.12.1"

+ 5 - 2
bin/darkwallet/insert_line.py

@@ -3,6 +3,7 @@ from gui import *
 import time
 import time
 
 
 def send(timest, nick, msg):
 def send(timest, nick, msg):
+    print(timest, nick, msg)
     node_id = api.lookup_node_id("/window/view/chatty")
     node_id = api.lookup_node_id("/window/view/chatty")
 
 
     arg_data = bytearray()
     arg_data = bytearray()
@@ -14,6 +15,8 @@ def send(timest, nick, msg):
     api.call_method(node_id, "insert_line", arg_data)
     api.call_method(node_id, "insert_line", arg_data)
 
 
 for i in range(27):
 for i in range(27):
-    send(1722944640000 + i, "hhi12", "hello 123")
-    time.sleep(0.1)
+    name = f"bob-{i}"
+    send(1722944640000 + i*60000, "hhi12", f"hello {name}")
+    #time.sleep(0.4)
+    input("> ")
 
 

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

@@ -17,6 +17,8 @@
  */
  */
 
 
 use async_recursion::async_recursion;
 use async_recursion::async_recursion;
+use chrono::{Local, NaiveDate, NaiveDateTime, TimeZone};
+use darkfi_serial::Encodable;
 use futures::{stream::FuturesUnordered, StreamExt};
 use futures::{stream::FuturesUnordered, StreamExt};
 use smol::Task;
 use smol::Task;
 use std::{
 use std::{
@@ -35,7 +37,7 @@ use crate::{
         SceneNodeType, Slot,
         SceneNodeType, Slot,
     },
     },
     text::TextShaperPtr,
     text::TextShaperPtr,
-    ui::{Button, ChatView, EditBox, Image, Mesh, RenderLayer, Stoppable, Text, Window},
+    ui::{chatview, Button, ChatView, EditBox, Image, Mesh, RenderLayer, Stoppable, Text, Window},
     ExecutorPtr,
     ExecutorPtr,
 };
 };
 
 
@@ -719,7 +721,7 @@ impl App {
         node.set_property_f32(Role::App, "line_height", 30.).unwrap();
         node.set_property_f32(Role::App, "line_height", 30.).unwrap();
         node.set_property_f32(Role::App, "baseline", 20.).unwrap();
         node.set_property_f32(Role::App, "baseline", 20.).unwrap();
         node.set_property_u32(Role::App, "z_index", 1).unwrap();
         node.set_property_u32(Role::App, "z_index", 1).unwrap();
-        //node.set_property_bool(Role::App, "debug", true).unwrap();
+        node.set_property_bool(Role::App, "debug", true).unwrap();
 
 
         let prop = node.get_property("timestamp_color").unwrap();
         let prop = node.get_property("timestamp_color").unwrap();
         prop.set_f32(Role::App, 0, 0.5).unwrap();
         prop.set_f32(Role::App, 0, 0.5).unwrap();
@@ -810,7 +812,6 @@ impl Drop for App {
 }
 }
 
 
 // Just for testing
 // Just for testing
-/*
 fn populate_tree(tree: &sled::Tree) {
 fn populate_tree(tree: &sled::Tree) {
     let chat_txt = include_str!("../chat.txt");
     let chat_txt = include_str!("../chat.txt");
     for line in chat_txt.lines() {
     for line in chat_txt.lines() {
@@ -822,7 +823,7 @@ fn populate_tree(tree: &sled::Tree) {
         let min = min.parse::<u32>().unwrap();
         let min = min.parse::<u32>().unwrap();
         let dt: NaiveDateTime =
         let dt: NaiveDateTime =
             NaiveDate::from_ymd_opt(2024, 8, 6).unwrap().and_hms_opt(hour, min, 0).unwrap();
             NaiveDate::from_ymd_opt(2024, 8, 6).unwrap().and_hms_opt(hour, min, 0).unwrap();
-        let timest = dt.and_utc().timestamp() as u64;
+        let timest = dt.and_utc().timestamp_millis() as u64;
 
 
         let nick = parts[1].to_string();
         let nick = parts[1].to_string();
         let text = parts[2].to_string();
         let text = parts[2].to_string();
@@ -842,7 +843,6 @@ fn populate_tree(tree: &sled::Tree) {
     // O(n)
     // O(n)
     debug!(target: "app", "populated db with {} lines", tree.len());
     debug!(target: "app", "populated db with {} lines", tree.len());
 }
 }
-*/
 
 
 pub fn create_layer(sg: &mut SceneGraph, name: &str) -> SceneNodeId {
 pub fn create_layer(sg: &mut SceneGraph, name: &str) -> SceneNodeId {
     debug!(target: "app", "create_layer({name})");
     debug!(target: "app", "create_layer({name})");

+ 1 - 0
bin/darkwallet/src/mesh.rs

@@ -32,6 +32,7 @@ pub const COLOR_DARKGREY: Color = [0.2, 0.2, 0.2, 1.];
 pub const COLOR_LIGHTGREY: Color = [0.7, 0.7, 0.7, 1.];
 pub const COLOR_LIGHTGREY: Color = [0.7, 0.7, 0.7, 1.];
 pub const COLOR_GREEN: Color = [0., 1., 0., 1.];
 pub const COLOR_GREEN: Color = [0., 1., 0., 1.];
 pub const COLOR_BLUE: Color = [0., 0., 1., 1.];
 pub const COLOR_BLUE: Color = [0., 0., 1., 1.];
+pub const COLOR_PINK: Color = [0.8, 0.3, 0.8, 1.];
 pub const COLOR_WHITE: Color = [1., 1., 1., 1.];
 pub const COLOR_WHITE: Color = [1., 1., 1., 1.];
 #[allow(dead_code)]
 #[allow(dead_code)]
 pub const COLOR_BLACK: Color = [1., 1., 1., 1.];
 pub const COLOR_BLACK: Color = [1., 1., 1., 1.];

+ 51 - 61
bin/darkwallet/src/ui/chatview/mod.rs

@@ -44,6 +44,7 @@ use crate::{
     pubsub::Subscription,
     pubsub::Subscription,
     scene::{Pimpl, SceneGraph, SceneGraphPtr2, SceneNodeId},
     scene::{Pimpl, SceneGraph, SceneGraphPtr2, SceneNodeId},
     text::{self, Glyph, GlyphPositionIter, TextShaperPtr},
     text::{self, Glyph, GlyphPositionIter, TextShaperPtr},
+    util::enumerate,
     ExecutorPtr,
     ExecutorPtr,
 };
 };
 
 
@@ -60,6 +61,15 @@ fn is_zero(x: f32) -> bool {
     x.abs() < EPSILON
     x.abs() < EPSILON
 }
 }
 
 
+/// std::cmp::max() doesn't work on f32
+fn max(a: f32, b: f32) -> f32 {
+    if a > b {
+        a
+    } else {
+        b
+    }
+}
+
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
 pub struct ChatMsg {
 pub struct ChatMsg {
     pub nick: String,
     pub nick: String,
@@ -124,6 +134,10 @@ pub struct ChatView {
     motion_cv: Arc<CondVar>,
     motion_cv: Arc<CondVar>,
     accel: AtomicF32,
     accel: AtomicF32,
     speed: AtomicF32,
     speed: AtomicF32,
+
+    /// Used for correct converting input event pos from screen to widget space.
+    /// We also use it when we re-eval rect when its changed via property.
+    parent_rect: SyncMutex<Option<Rectangle>>,
 }
 }
 
 
 impl ChatView {
 impl ChatView {
@@ -258,11 +272,12 @@ impl ChatView {
                 motion_cv,
                 motion_cv,
                 accel: AtomicF32::new(0.),
                 accel: AtomicF32::new(0.),
                 speed: AtomicF32::new(0.),
                 speed: AtomicF32::new(0.),
+
+                parent_rect: SyncMutex::new(None),
             }
             }
         });
         });
 
 
         let timer = std::time::Instant::now();
         let timer = std::time::Instant::now();
-        self_.populate().await;
         debug!(target: "ui::chatview", "populate() took {:?}", timer.elapsed());
         debug!(target: "ui::chatview", "populate() took {:?}", timer.elapsed());
 
 
         Pimpl::ChatView(self_)
         Pimpl::ChatView(self_)
@@ -382,9 +397,9 @@ impl ChatView {
     }
     }
 
 
     async fn handle_mouse_wheel(&self, wheel_x: f32, wheel_y: f32) {
     async fn handle_mouse_wheel(&self, wheel_x: f32, wheel_y: f32) {
-        debug!(target: "ui::chatview", "handle_mouse_wheel({wheel_x}, {wheel_y})");
+        //debug!(target: "ui::chatview", "handle_mouse_wheel({wheel_x}, {wheel_y})");
 
 
-        let Some(rect) = self.get_cached_world_rect().await else { return };
+        let Some(rect) = self.parent_rect.lock().unwrap().clone() else { return };
 
 
         let mouse_pos = self.mouse_pos.lock().unwrap().clone();
         let mouse_pos = self.mouse_pos.lock().unwrap().clone();
         if !rect.contains(&mouse_pos) {
         if !rect.contains(&mouse_pos) {
@@ -413,7 +428,8 @@ impl ChatView {
             return
             return
         }
         }
 
 
-        let Some(rect) = self.get_cached_world_rect().await else { return };
+        let Some(rect) = self.parent_rect.lock().unwrap().clone() else { return };
+
         let touch_pos = Point { x: touch_x, y: touch_y };
         let touch_pos = Point { x: touch_x, y: touch_y };
         if !rect.contains(&touch_pos) {
         if !rect.contains(&touch_pos) {
             //debug!(target: "ui::chatview", "not inside rect");
             //debug!(target: "ui::chatview", "not inside rect");
@@ -562,61 +578,33 @@ impl ChatView {
         self.line_height.get() - self.baseline.get()
         self.line_height.get() - self.baseline.get()
     }
     }
 
 
-    async fn get_parent_rect(&self) -> Option<Rectangle> {
-        let sg = self.sg.lock().await;
-        let node = sg.get_node(self.node_id).unwrap();
-        let Some(parent_rect) = get_parent_rect(&sg, node) else {
-            return None;
-        };
-        drop(sg);
-        Some(parent_rect)
-    }
-    /// This calc is wrong since the element position is offset by all parents. We need to fix this.
-    async fn get_cached_world_rect(&self) -> Option<Rectangle> {
-        // NBD if it's slightly wrong
-        let mut rect = read_rect(self.rect.clone()).ok()?;
-
-        // If layers can be nested and we use offsets for (x, y)
-        // then this will be incorrect for nested layers.
-        // For now we don't allow nesting of layers.
-        let parent_rect = self.get_parent_rect().await?;
-
-        // Offset rect which is now in world coords
-        rect.x += parent_rect.x;
-        rect.y += parent_rect.y;
-
-        Some(rect)
-    }
-
-    async fn populate(&self) {
-        debug!(target: "ui::chatview", "populating pages");
-        let iter = self.tree.iter().rev();
-        self.load_n_pages(iter, PRELOAD_PAGES).await;
-    }
-
     /// Load extra pages
     /// Load extra pages
-    async fn preload_pages(&self) -> usize {
-        // Get last page
-        // get the current earliest timestamp
-        let last_timest = self.pages.lock().await.last_timestamp();
-
-        // iterate from there
-        let key = last_timest.to_be_bytes();
-        debug!(target: "ui::chatview", "preloading from {key:?}");
-        let iter = self.tree.range(..key).rev();
+    async fn preload_pages(&self, pages: &mut PageManager) -> usize {
+        // Get the current earliest timestamp
+        let iter = match pages.last_timestamp() {
+            Some(last_timest) => {
+                // iterate from there
+                let key = last_timest.to_be_bytes();
+                debug!(target: "ui::chatview", "preloading from {key:?}");
+                let iter = self.tree.range(..key).rev();
+                iter
+            }
+            None => self.tree.iter().rev(),
+        };
 
 
-        self.load_n_pages(iter, PRELOAD_PAGES).await
+        self.load_n_pages(pages, iter, PRELOAD_PAGES).await
     }
     }
 
 
     async fn load_n_pages<I: Iterator<Item = sled::Result<(sled::IVec, sled::IVec)>>>(
     async fn load_n_pages<I: Iterator<Item = sled::Result<(sled::IVec, sled::IVec)>>>(
         &self,
         &self,
+        pages: &mut PageManager,
         iter: I,
         iter: I,
         n: usize,
         n: usize,
     ) -> usize {
     ) -> usize {
         let mut pages_len = 0;
         let mut pages_len = 0;
         let mut msgs = vec![];
         let mut msgs = vec![];
 
 
-        let line_width = self.pages.lock().await.line_width;
+        let line_width = pages.line_width;
 
 
         for entry in iter {
         for entry in iter {
             let Ok((k, v)) = entry else { break };
             let Ok((k, v)) = entry else { break };
@@ -627,7 +615,7 @@ impl ChatView {
             let chatmsg: ChatMsg = deserialize(&v).unwrap();
             let chatmsg: ChatMsg = deserialize(&v).unwrap();
             debug!(target: "ui::chatview", "{timest:?} {chatmsg:?}");
             debug!(target: "ui::chatview", "{timest:?} {chatmsg:?}");
 
 
-            let dt = Local.timestamp_opt(timest as i64, 0).unwrap();
+            let dt = Local.timestamp_millis_opt(timest as i64).unwrap();
             let timestr = dt.format("%H:%M").to_string();
             let timestr = dt.format("%H:%M").to_string();
 
 
             let text = format!("{} {} {}", timestr, chatmsg.nick, chatmsg.text);
             let text = format!("{} {} {}", timestr, chatmsg.nick, chatmsg.text);
@@ -650,7 +638,7 @@ impl ChatView {
 
 
                 let msgs = std::mem::take(&mut msgs);
                 let msgs = std::mem::take(&mut msgs);
                 let page = Page::new(msgs, &self.render_api).await;
                 let page = Page::new(msgs, &self.render_api).await;
-                self.pages.lock().await.push(page);
+                pages.push(page);
 
 
                 pages_len += 1;
                 pages_len += 1;
                 if pages_len >= n {
                 if pages_len >= n {
@@ -664,7 +652,7 @@ impl ChatView {
             debug!(target: "ui::chatview", "added final page. page_len={pages_len}");
             debug!(target: "ui::chatview", "added final page. page_len={pages_len}");
 
 
             let page = Page::new(msgs, &self.render_api).await;
             let page = Page::new(msgs, &self.render_api).await;
-            self.pages.lock().await.push(page);
+            pages.push(page);
             pages_len += 1;
             pages_len += 1;
         }
         }
 
 
@@ -713,7 +701,7 @@ impl ChatView {
 
 
     /// Basically a version of redraw() which doesn't invalidate the cache
     /// Basically a version of redraw() which doesn't invalidate the cache
     async fn scrollview(&self, mut scroll: f32) -> f32 {
     async fn scrollview(&self, mut scroll: f32) -> f32 {
-        debug!(target: "ui::chatview", "scrollview()");
+        //debug!(target: "ui::chatview", "scrollview()");
         let old_scroll = self.scroll.get();
         let old_scroll = self.scroll.get();
 
 
         let rect = read_rect(self.rect.clone()).expect("bad rect property");
         let rect = read_rect(self.rect.clone()).expect("bad rect property");
@@ -758,17 +746,15 @@ impl ChatView {
         let line_height = self.line_height.get();
         let line_height = self.line_height.get();
         let baseline = self.baseline.get();
         let baseline = self.baseline.get();
 
 
-        // When scrolling it can go negative so clamp it here
-        if scroll < 0. {
-            scroll = 0.;
-        }
+        // We still wish to preload pages to fill the screen, so we just adjust it up to 0.
+        let nonneg_scroll = max(scroll, 0.);
 
 
         let mut total_height = pages.calc_total_height(line_height, baseline);
         let mut total_height = pages.calc_total_height(line_height, baseline);
         // Load pages until we run out or we have enough
         // Load pages until we run out or we have enough
-        while total_height < scroll + rect_h {
+        while total_height < nonneg_scroll + rect_h {
             debug!(target: "ui::chatview", "set_adjusted_scroll() loading more pages");
             debug!(target: "ui::chatview", "set_adjusted_scroll() loading more pages");
 
 
-            let n_loaded_pages = self.preload_pages().await;
+            let n_loaded_pages = self.preload_pages(pages).await;
 
 
             // We need this value after so first update it
             // We need this value after so first update it
             total_height = pages.calc_total_height(line_height, baseline);
             total_height = pages.calc_total_height(line_height, baseline);
@@ -779,6 +765,10 @@ impl ChatView {
             }
             }
         }
         }
 
 
+        if scroll < 0. {
+            return Some(0.)
+        }
+
         let max_allowed_scroll = if total_height > rect_h { total_height - rect_h } else { 0. };
         let max_allowed_scroll = if total_height > rect_h { total_height - rect_h } else { 0. };
 
 
         if scroll > max_allowed_scroll {
         if scroll > max_allowed_scroll {
@@ -829,7 +819,7 @@ impl ChatView {
             .await;
             .await;
 
 
         let mut current_height = 0.;
         let mut current_height = 0.;
-        for (height, mesh) in meshes {
+        for (i, (height, mesh)) in enumerate(meshes) {
             // Apply scroll and scissor
             // Apply scroll and scissor
             // We use the scissor for scrolling
             // We use the scissor for scrolling
             // Because we use the scissor, our actual rect is now rect instead of parent_rect
             // Because we use the scissor, our actual rect is now rect instead of parent_rect
@@ -844,6 +834,7 @@ impl ChatView {
             instrs.push(DrawInstruction::ApplyMatrix(model));
             instrs.push(DrawInstruction::ApplyMatrix(model));
 
 
             instrs.push(DrawInstruction::Draw(mesh));
             instrs.push(DrawInstruction::Draw(mesh));
+            //debug!(target: "ui::chatview", "mesh-{i}: {height} {current_height}");
 
 
             current_height += height;
             current_height += height;
         }
         }
@@ -856,6 +847,8 @@ impl ChatView {
     pub async fn draw(&self, sg: &SceneGraph, parent_rect: &Rectangle) -> Option<DrawUpdate> {
     pub async fn draw(&self, sg: &SceneGraph, parent_rect: &Rectangle) -> Option<DrawUpdate> {
         debug!(target: "ui::chatview", "ChatView::draw()");
         debug!(target: "ui::chatview", "ChatView::draw()");
 
 
+        *self.parent_rect.lock().unwrap() = Some(parent_rect.clone());
+
         let rect = eval_rect(self.rect.clone(), parent_rect).expect("bad rect property");
         let rect = eval_rect(self.rect.clone(), parent_rect).expect("bad rect property");
 
 
         let mut pages = self.pages.lock().await;
         let mut pages = self.pages.lock().await;
@@ -866,9 +859,6 @@ impl ChatView {
             self.scroll.set(scroll);
             self.scroll.set(scroll);
         }
         }
 
 
-        // Drop all meshes which forces a redraw of everything
-        pages.invalidate_caches();
-
         let (mut mesh_instrs, freed) = self.get_meshes(&mut pages, &rect).await;
         let (mut mesh_instrs, freed) = self.get_meshes(&mut pages, &rect).await;
         drop(pages);
         drop(pages);
 
 

+ 50 - 35
bin/darkwallet/src/ui/chatview/page.rs

@@ -30,11 +30,12 @@ use crate::{
         DrawCall, DrawInstruction, DrawMesh, GraphicsEventPublisherPtr, Point, Rectangle,
         DrawCall, DrawInstruction, DrawMesh, GraphicsEventPublisherPtr, Point, Rectangle,
         RenderApi, RenderApiPtr,
         RenderApi, RenderApiPtr,
     },
     },
-    mesh::{Color, MeshBuilder, COLOR_BLUE, COLOR_GREEN},
+    mesh::{Color, MeshBuilder, COLOR_BLUE, COLOR_GREEN, COLOR_PINK},
     prop::{PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr, PropertyUint32, Role},
     prop::{PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr, PropertyUint32, Role},
     pubsub::Subscription,
     pubsub::Subscription,
     scene::{Pimpl, SceneGraph, SceneGraphPtr2, SceneNodeId},
     scene::{Pimpl, SceneGraph, SceneGraphPtr2, SceneNodeId},
     text::{self, Glyph, GlyphPositionIter, TextShaper, TextShaperPtr},
     text::{self, Glyph, GlyphPositionIter, TextShaper, TextShaperPtr},
+    util::enumerate_mut,
     ExecutorPtr,
     ExecutorPtr,
 };
 };
 
 
@@ -73,7 +74,7 @@ impl Message {
         line_width: f32,
         line_width: f32,
         text_shaper: &TextShaper,
         text_shaper: &TextShaper,
     ) -> Self {
     ) -> Self {
-        let dt = Local.timestamp_opt(timestamp as i64, 0).unwrap();
+        let dt = Local.timestamp_millis_opt(timestamp as i64).unwrap();
         let timestr = dt.format("%H:%M").to_string();
         let timestr = dt.format("%H:%M").to_string();
 
 
         let linetext = format!("{} {} {}", timestr, nick, text);
         let linetext = format!("{} {} {}", timestr, nick, text);
@@ -210,6 +211,13 @@ impl Page {
             }
             }
         }
         }
 
 
+        // debug draw page outline
+        if debug_render {
+            let height = line_idx as f32 * line_height;
+            let page_rect = Rectangle::from_array([0., -height, clip.w, height]);
+            mesh.draw_outline(&page_rect, COLOR_PINK, 1.);
+        }
+
         let mesh = mesh.alloc(render_api).await.unwrap();
         let mesh = mesh.alloc(render_api).await.unwrap();
         let mesh = mesh.draw_with_texture(self.atlas.texture_id);
         let mesh = mesh.draw_with_texture(self.atlas.texture_id);
         self.mesh_cache = Some(mesh.clone());
         self.mesh_cache = Some(mesh.clone());
@@ -278,12 +286,12 @@ pub(super) struct FreedData {
 }
 }
 
 
 impl FreedData {
 impl FreedData {
-    fn add(&mut self, mesh: DrawMesh) {
+    fn add_mesh(&mut self, mesh: DrawMesh) {
         self.buffers.push(mesh.vertex_buffer);
         self.buffers.push(mesh.vertex_buffer);
         self.buffers.push(mesh.index_buffer);
         self.buffers.push(mesh.index_buffer);
-        if let Some(texture_id) = mesh.texture {
-            self.textures.push(texture_id);
-        }
+    }
+    fn add_texture(&mut self, texture_id: TextureId) {
+        self.textures.push(texture_id);
     }
     }
 }
 }
 
 
@@ -317,6 +325,17 @@ impl PageManager {
                 msg.adjust_line_width(line_width);
                 msg.adjust_line_width(line_width);
             }
             }
         }
         }
+
+        self.clear_meshes();
+    }
+
+    /// Clear all meshes and caches. Returns data that needs to be freed.
+    fn clear_meshes(&mut self) {
+        for page in &mut self.pages {
+            if let Some(mesh) = page.clear_mesh() {
+                self.freed.add_mesh(mesh);
+            }
+        }
     }
     }
 
 
     pub(super) fn calc_total_height(&self, line_height: f32, baseline: f32) -> f32 {
     pub(super) fn calc_total_height(&self, line_height: f32, baseline: f32) -> f32 {
@@ -352,39 +371,40 @@ impl PageManager {
 
 
         // Now add message to page
         // Now add message to page
 
 
-        // Maybe we can write this code below better
+        // Create our very first page
         if self.pages.is_empty() {
         if self.pages.is_empty() {
             let page = Page::new(vec![msg], &self.render_api).await;
             let page = Page::new(vec![msg], &self.render_api).await;
             self.pages.push(page);
             self.pages.push(page);
             return;
             return;
         }
         }
 
 
+        // We only add lines inside pages.
+        // Calling the appropriate draw() function after should preload any missing pages.
+        // When a line is before the first page, it will get preloaded as a new page.
+        let first_timest = self.pages.first().unwrap().msgs.last().unwrap().timestamp;
+        if timest < first_timest {
+            return;
+        }
+
         let mut idx = None;
         let mut idx = None;
-        for (i, page) in self.pages.iter_mut().enumerate() {
-            let first_timest = page.msgs.last().unwrap().timestamp;
+        for (i, page) in enumerate_mut(&mut self.pages) {
+            //let first_timest = page.msgs.last().unwrap().timestamp;
             let last_timest = page.msgs.first().unwrap().timestamp;
             let last_timest = page.msgs.first().unwrap().timestamp;
 
 
-            if timest < first_timest {
-                // It does not belong to any current page
-                // Create page only if there's not enough pages for the screen rect
-                // current_height < rect.h
-                // Otherwise it just means the page wasn't loaded
-                // Maybe we need to have a flag indicating there's pages not loaded in the DB still
-            }
-
             //debug!(target: "ui::chatview", "page {i} [{first_timest}, {last_timest}]");
             //debug!(target: "ui::chatview", "page {i} [{first_timest}, {last_timest}]");
-            if first_timest <= timest && timest <= last_timest {
+            if timest <= last_timest {
                 //debug!(target: "ui::chatview", "found page {i} [{first_timest}, {last_timest}]");
                 //debug!(target: "ui::chatview", "found page {i} [{first_timest}, {last_timest}]");
                 idx = Some(i);
                 idx = Some(i);
                 break
                 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 idx = match idx {
+            Some(idx) => idx,
+            None => {
+                let last_page_idx = 0;
+                last_page_idx
+            }
         };
         };
 
 
         let old_pages_len = self.pages.len();
         let old_pages_len = self.pages.len();
@@ -399,7 +419,8 @@ impl PageManager {
 
 
         // Free texture and mesh before dropping page
         // Free texture and mesh before dropping page
         if let Some(mesh) = old_page.mesh_cache {
         if let Some(mesh) = old_page.mesh_cache {
-            self.freed.add(mesh);
+            self.freed.add_mesh(mesh);
+            self.freed.add_texture(old_page.atlas.texture_id);
         }
         }
 
 
         let mut msgs = old_page.msgs;
         let mut msgs = old_page.msgs;
@@ -411,15 +432,6 @@ impl PageManager {
         self.pages.append(&mut tail);
         self.pages.append(&mut tail);
     }
     }
 
 
-    /// Clear all meshes and caches. Returns data that needs to be freed.
-    pub(super) fn invalidate_caches(&mut self) {
-        for page in &mut self.pages {
-            if let Some(mesh) = page.clear_mesh() {
-                self.freed.add(mesh);
-            }
-        }
-    }
-
     /// Generate caches and return meshes
     /// Generate caches and return meshes
     pub(super) async fn gen_meshes(
     pub(super) async fn gen_meshes(
         &mut self,
         &mut self,
@@ -467,7 +479,10 @@ impl PageManager {
         meshes
         meshes
     }
     }
 
 
-    pub(super) fn last_timestamp(&self) -> Timestamp {
-        self.pages.last().unwrap().msgs.last().unwrap().timestamp
+    pub(super) fn last_timestamp(&self) -> Option<Timestamp> {
+        let last_page_msgs = &self.pages.last()?.msgs;
+        // There should be no pages with 0 messages. We can unwrap here.
+        debug!(target: "ui::chatview", "last page has {} msgs", last_page_msgs.len());
+        Some(last_page_msgs.last().unwrap().timestamp)
     }
     }
 }
 }

+ 10 - 0
bin/darkwallet/src/util.rs

@@ -114,3 +114,13 @@ where
 {
 {
     TupleIterStruct3 { idx: 0, i1, i2, i3 }
     TupleIterStruct3 { idx: 0, i1, i2, i3 }
 }
 }
+
+pub fn enumerate<X>(v: Vec<X>) -> impl Iterator<Item = (usize, X)> {
+    v.into_iter().enumerate()
+}
+pub fn enumerate_ref<X>(v: &Vec<X>) -> impl Iterator<Item = (usize, &X)> {
+    v.iter().enumerate()
+}
+pub fn enumerate_mut<X>(v: &mut Vec<X>) -> impl Iterator<Item = (usize, &mut X)> {
+    v.iter_mut().enumerate()
+}