Эх сурвалжийг харах

app: restore back the prod UI from test UI, and upgrade remaining code. App is fully persistent & UI is restartable now.

darkfi 1 жил өмнө
parent
commit
e26b82dbdb

+ 1 - 5
bin/app/src/android.rs

@@ -18,11 +18,7 @@
 
 use miniquad::native::android::{self, ndk_sys, ndk_utils};
 use parking_lot::Mutex as SyncMutex;
-use std::{
-    collections::HashMap,
-    path::PathBuf,
-    sync::LazyLock
-};
+use std::{collections::HashMap, path::PathBuf, sync::LazyLock};
 
 use crate::AndroidSuggestEvent;
 

+ 126 - 138
bin/app/src/app/mod.rs

@@ -80,13 +80,7 @@ impl App {
         text_shaper: TextShaperPtr,
         ex: ExecutorPtr,
     ) -> Arc<Self> {
-        Arc::new(Self {
-            sg_root,
-            ex,
-            render_api,
-            text_shaper,
-            tasks: SyncMutex::new(vec![]),
-        })
+        Arc::new(Self { sg_root, ex, render_api, text_shaper, tasks: SyncMutex::new(vec![]) })
     }
 
     /// Does not require miniquad to be init. Created the scene graph tree / schema and all
@@ -128,7 +122,6 @@ impl App {
         settings.load_settings();
 
         // Save app settings in sled when they change
-        /*
         for setting_node in settings.setting_root.get_children().iter() {
             let setting_sub = setting_node.get_property("value").unwrap().subscribe_modify();
             let settings2 = settings.clone();
@@ -139,7 +132,6 @@ impl App {
             });
             self.tasks.lock().unwrap().push(setting_task);
         }
-        */
 
         let window =
             window.setup(|me| Window::new(me, self.render_api.clone(), setting_root.clone())).await;
@@ -147,7 +139,7 @@ impl App {
         self.sg_root.clone().link(window.clone());
         self.sg_root.clone().link(setting_root.clone());
 
-        schema::test::make(&self, window.clone()).await;
+        schema::make(&self, window.clone()).await;
 
         d!("Schema loaded");
 
@@ -167,140 +159,138 @@ impl App {
     }
 
     /*
-    #[cfg(feature = "enable-plugins")]
-    async fn load_plugins(&self, plugin: SceneNodePtr) {
-        let darkirc = create_darkirc("darkirc");
-        let darkirc = darkirc
-            .setup(|me| async {
-                plugin::DarkIrc::new(me, self.ex.clone()).await.expect("DarkIrc pimpl setup")
-            })
-            .await;
-
-        let (slot, recvr) = Slot::new("recvmsg");
-        darkirc.register("recv", slot).unwrap();
-        let sg_root2 = self.sg_root.clone();
-        let darkirc_nick = PropertyStr::wrap(&darkirc, Role::App, "nick", 0).unwrap();
-        let listen_recv = self.ex.spawn(async move {
-            while let Ok(data) = recvr.recv().await {
-                let atom = &mut PropertyAtomicGuard::new();
-
-                let mut cur = Cursor::new(&data);
-                let channel = String::decode(&mut cur).unwrap();
-                let timestamp = chatview::Timestamp::decode(&mut cur).unwrap();
-                let id = chatview::MessageId::decode(&mut cur).unwrap();
-                let nick = String::decode(&mut cur).unwrap();
-                let msg = String::decode(&mut cur).unwrap();
-
-                let node_path = format!("/window/{channel}_chat_layer/content/chatty");
-                t!("Attempting to relay message to {node_path}");
-                let Some(chatview) = sg_root2.clone().lookup_node(&node_path) else {
-                    d!("Ignoring message since {node_path} doesn't exist");
-                    continue
-                };
-
-                // I prefer to just re-encode because the code is clearer.
-                let mut data = vec![];
-                timestamp.encode(&mut data).unwrap();
-                id.encode(&mut data).unwrap();
-                nick.encode(&mut data).unwrap();
-                msg.encode(&mut data).unwrap();
-                if let Err(err) = chatview.call_method("insert_line", data).await {
-                    error!(
-                        target: "app",
-                        "Call method {node_path}::insert_line({timestamp}, {id}, {nick}, '{msg}'): {err:?}"
-                    );
-                }
-
-                // Apply coloring when you get a message
-                let chat_path = format!("/window/{channel}_chat_layer");
-                let chat_layer = sg_root2.clone().lookup_node(chat_path).unwrap();
-                if chat_layer.get_property_bool("is_visible").unwrap() {
-                    continue
-                }
-
-                let node_path = format!("/window/menu_layer/{channel}_channel_label");
-                let menu_label = sg_root2.clone().lookup_node(&node_path).unwrap();
-                let prop = menu_label.get_property("text_color").unwrap();
-                if msg.contains(&darkirc_nick.get()) {
-                    // Nick highlight
-                    prop.clone().set_f32(atom, Role::App, 0, 0.56).unwrap();
-                    prop.clone().set_f32(atom, Role::App, 1, 0.61).unwrap();
-                    prop.clone().set_f32(atom, Role::App, 2, 1.).unwrap();
-                    prop.clone().set_f32(atom, Role::App, 3, 1.).unwrap();
-                } else {
-                    // Normal channel activity
-                    prop.clone().set_f32(atom, Role::App, 0, 0.36).unwrap();
-                    prop.clone().set_f32(atom, Role::App, 1, 1.).unwrap();
-                    prop.clone().set_f32(atom, Role::App, 2, 0.51).unwrap();
-                    prop.clone().set_f32(atom, Role::App, 3, 1.).unwrap();
-                }
-            }
-        });
-        self.tasks.lock().unwrap().push(listen_recv);
-
-        let (slot, recvr) = Slot::new("connect");
-        darkirc.register("connect", slot).unwrap();
-        let sg_root2 = self.sg_root.clone();
-        let listen_connect = self.ex.spawn(async move {
-            let net0 = sg_root2.clone().lookup_node("/window/netstatus_layer/net0").unwrap();
-            let net1 = sg_root2.clone().lookup_node("/window/netstatus_layer/net1").unwrap();
-            let net2 = sg_root2.clone().lookup_node("/window/netstatus_layer/net2").unwrap();
-            let net3 = sg_root2.clone().lookup_node("/window/netstatus_layer/net3").unwrap();
-
-            let net0_is_visible = PropertyBool::wrap(&net0, Role::App, "is_visible", 0).unwrap();
-            let net1_is_visible = PropertyBool::wrap(&net1, Role::App, "is_visible", 0).unwrap();
-            let net2_is_visible = PropertyBool::wrap(&net2, Role::App, "is_visible", 0).unwrap();
-            let net3_is_visible = PropertyBool::wrap(&net3, Role::App, "is_visible", 0).unwrap();
-
-            while let Ok(data) = recvr.recv().await {
-                let (peers_count, is_dag_synced): (u32, bool) = deserialize(&data).unwrap();
-
-                let atom = &mut PropertyAtomicGuard::new();
-
-                if peers_count == 0 {
-                    net0_is_visible.set(atom, true);
-                    net1_is_visible.set(atom, false);
-                    net2_is_visible.set(atom, false);
-                    net3_is_visible.set(atom, false);
-                    continue
-                }
-
-                assert!(peers_count > 0);
-                if !is_dag_synced {
-                    net0_is_visible.set(atom, false);
-                    net1_is_visible.set(atom, true);
-                    net2_is_visible.set(atom, false);
-                    net3_is_visible.set(atom, false);
-                    continue
+        #[cfg(feature = "enable-plugins")]
+        async fn load_plugins(&self, plugin: SceneNodePtr) {
+            let darkirc = create_darkirc("darkirc");
+            let darkirc = darkirc
+                .setup(|me| async {
+                    plugin::DarkIrc::new(me, self.ex.clone()).await.expect("DarkIrc pimpl setup")
+                })
+                .await;
+
+            let (slot, recvr) = Slot::new("recvmsg");
+            darkirc.register("recv", slot).unwrap();
+            let sg_root2 = self.sg_root.clone();
+            let darkirc_nick = PropertyStr::wrap(&darkirc, Role::App, "nick", 0).unwrap();
+            let listen_recv = self.ex.spawn(async move {
+                while let Ok(data) = recvr.recv().await {
+                    let atom = &mut PropertyAtomicGuard::new();
+
+                    let mut cur = Cursor::new(&data);
+                    let channel = String::decode(&mut cur).unwrap();
+                    let timestamp = chatview::Timestamp::decode(&mut cur).unwrap();
+                    let id = chatview::MessageId::decode(&mut cur).unwrap();
+                    let nick = String::decode(&mut cur).unwrap();
+                    let msg = String::decode(&mut cur).unwrap();
+
+                    let node_path = format!("/window/{channel}_chat_layer/content/chatty");
+                    t!("Attempting to relay message to {node_path}");
+                    let Some(chatview) = sg_root2.clone().lookup_node(&node_path) else {
+                        d!("Ignoring message since {node_path} doesn't exist");
+                        continue
+                    };
+
+                    // I prefer to just re-encode because the code is clearer.
+                    let mut data = vec![];
+                    timestamp.encode(&mut data).unwrap();
+                    id.encode(&mut data).unwrap();
+                    nick.encode(&mut data).unwrap();
+                    msg.encode(&mut data).unwrap();
+                    if let Err(err) = chatview.call_method("insert_line", data).await {
+                        error!(
+                            target: "app",
+                            "Call method {node_path}::insert_line({timestamp}, {id}, {nick}, '{msg}'): {err:?}"
+                        );
+                    }
+
+                    // Apply coloring when you get a message
+                    let chat_path = format!("/window/{channel}_chat_layer");
+                    let chat_layer = sg_root2.clone().lookup_node(chat_path).unwrap();
+                    if chat_layer.get_property_bool("is_visible").unwrap() {
+                        continue
+                    }
+
+                    let node_path = format!("/window/menu_layer/{channel}_channel_label");
+                    let menu_label = sg_root2.clone().lookup_node(&node_path).unwrap();
+                    let prop = menu_label.get_property("text_color").unwrap();
+                    if msg.contains(&darkirc_nick.get()) {
+                        // Nick highlight
+                        prop.clone().set_f32(atom, Role::App, 0, 0.56).unwrap();
+                        prop.clone().set_f32(atom, Role::App, 1, 0.61).unwrap();
+                        prop.clone().set_f32(atom, Role::App, 2, 1.).unwrap();
+                        prop.clone().set_f32(atom, Role::App, 3, 1.).unwrap();
+                    } else {
+                        // Normal channel activity
+                        prop.clone().set_f32(atom, Role::App, 0, 0.36).unwrap();
+                        prop.clone().set_f32(atom, Role::App, 1, 1.).unwrap();
+                        prop.clone().set_f32(atom, Role::App, 2, 0.51).unwrap();
+                        prop.clone().set_f32(atom, Role::App, 3, 1.).unwrap();
+                    }
                 }
+            });
+            self.tasks.lock().unwrap().push(listen_recv);
+
+            let (slot, recvr) = Slot::new("connect");
+            darkirc.register("connect", slot).unwrap();
+            let sg_root2 = self.sg_root.clone();
+            let listen_connect = self.ex.spawn(async move {
+                let net0 = sg_root2.clone().lookup_node("/window/netstatus_layer/net0").unwrap();
+                let net1 = sg_root2.clone().lookup_node("/window/netstatus_layer/net1").unwrap();
+                let net2 = sg_root2.clone().lookup_node("/window/netstatus_layer/net2").unwrap();
+                let net3 = sg_root2.clone().lookup_node("/window/netstatus_layer/net3").unwrap();
+
+                let net0_is_visible = PropertyBool::wrap(&net0, Role::App, "is_visible", 0).unwrap();
+                let net1_is_visible = PropertyBool::wrap(&net1, Role::App, "is_visible", 0).unwrap();
+                let net2_is_visible = PropertyBool::wrap(&net2, Role::App, "is_visible", 0).unwrap();
+                let net3_is_visible = PropertyBool::wrap(&net3, Role::App, "is_visible", 0).unwrap();
+
+                while let Ok(data) = recvr.recv().await {
+                    let (peers_count, is_dag_synced): (u32, bool) = deserialize(&data).unwrap();
+
+                    let atom = &mut PropertyAtomicGuard::new();
+
+                    if peers_count == 0 {
+                        net0_is_visible.set(atom, true);
+                        net1_is_visible.set(atom, false);
+                        net2_is_visible.set(atom, false);
+                        net3_is_visible.set(atom, false);
+                        continue
+                    }
+
+                    assert!(peers_count > 0);
+                    if !is_dag_synced {
+                        net0_is_visible.set(atom, false);
+                        net1_is_visible.set(atom, true);
+                        net2_is_visible.set(atom, false);
+                        net3_is_visible.set(atom, false);
+                        continue
+                    }
+
+                    assert!(peers_count > 0 && is_dag_synced);
+                    if peers_count == 1 {
+                        net0_is_visible.set(atom, false);
+                        net1_is_visible.set(atom, false);
+                        net2_is_visible.set(atom, true);
+                        net3_is_visible.set(atom, false);
+                        continue
+                    }
 
-                assert!(peers_count > 0 && is_dag_synced);
-                if peers_count == 1 {
                     net0_is_visible.set(atom, false);
                     net1_is_visible.set(atom, false);
-                    net2_is_visible.set(atom, true);
-                    net3_is_visible.set(atom, false);
-                    continue
+                    net2_is_visible.set(atom, false);
+                    net3_is_visible.set(atom, true);
                 }
+            });
+            self.tasks.lock().unwrap().push(listen_connect);
 
-                net0_is_visible.set(atom, false);
-                net1_is_visible.set(atom, false);
-                net2_is_visible.set(atom, false);
-                net3_is_visible.set(atom, true);
-            }
-        });
-        self.tasks.lock().unwrap().push(listen_connect);
-
-        plugin.link(darkirc);
+            plugin.link(darkirc);
 
-        i!("Plugins loaded");
-    }
-*/
+            i!("Plugins loaded");
+        }
+    */
 
     /// Begins the draw of the tree, and then starts the UI procs.
-    pub async fn start(self: Arc<Self>,
-        event_pub: GraphicsEventPublisherPtr
-        ) {
+    pub async fn start(self: Arc<Self>, event_pub: GraphicsEventPublisherPtr) {
         d!("Starting app");
         let atom = &mut PropertyAtomicGuard::new();
 
@@ -344,9 +334,7 @@ impl App {
             _ => panic!("wrong pimpl"),
         }
     }
-    async fn start_procs(&self,
-        event_pub: GraphicsEventPublisherPtr
-        ) {
+    async fn start_procs(&self, event_pub: GraphicsEventPublisherPtr) {
         let window_node = self.sg_root.clone().lookup_node("/window").unwrap();
         match window_node.pimpl() {
             Pimpl::Window(win) => win.clone().start(event_pub, self.ex.clone()).await,

+ 1 - 1
bin/app/src/app/schema/mod.rs

@@ -507,7 +507,7 @@ pub async fn make(app: &App, window: SceneNodePtr) {
     let emoji_meshes2 = emoji_meshes.clone();
     std::thread::spawn(move || {
         for i in (0..500).step_by(20) {
-            let mut emoji = emoji_meshes2.lock().unwrap();
+            let mut emoji = emoji_meshes2.lock();
             for j in i..(i + 20) {
                 emoji.get(j);
             }

+ 0 - 2
bin/app/src/app/schema/test.rs

@@ -481,7 +481,6 @@ pub async fn make(app: &App, window: SceneNodePtr) {
     layer_node.clone().link(node);
     */
 
-        /*
     // Text edit
     let node = create_chatedit("editz");
     node.set_property_bool(atom, Role::App, "is_active", true).unwrap();
@@ -554,5 +553,4 @@ pub async fn make(app: &App, window: SceneNodePtr) {
         })
         .await;
     layer_node.clone().link(node);
-    */
 }

+ 5 - 9
bin/app/src/gfx/mod.rs

@@ -43,11 +43,11 @@ pub use linalg::{Dimension, Point, Rectangle};
 mod shader;
 
 use crate::{
-    GOD,
     app::AppPtr,
     error::{Error, Result},
     pubsub::{Publisher, PublisherPtr, Subscription, SubscriptionId},
-    util::{AsyncRuntime, ansi_texture},
+    util::{ansi_texture, AsyncRuntime},
+    GOD,
 };
 
 // This is very noisy so suppress output by default
@@ -645,8 +645,7 @@ struct Stage {
 }
 
 impl Stage {
-    pub fn new(
-    ) -> Self {
+    pub fn new() -> Self {
         let mut ctx: Box<dyn RenderingBackend> = window::new_rendering_backend();
 
         // This will start the app to start. Needed since we cannot get window size for init
@@ -931,8 +930,7 @@ impl EventHandler for Stage {
     }
 }
 
-pub fn run_gui(
-) {
+pub fn run_gui() {
     let mut window_width = 1024;
     let mut window_height = 768;
     if let Ok(mut file) = File::open(get_window_size_filename()) {
@@ -963,7 +961,5 @@ pub fn run_gui(
     conf.platform.apple_gfx_api =
         if metal { conf::AppleGfxApi::Metal } else { conf::AppleGfxApi::OpenGl };
 
-    miniquad::start(conf, || {
-        Box::new(Stage::new())
-    });
+    miniquad::start(conf, || Box::new(Stage::new()));
 }

+ 7 - 2
bin/app/src/main.rs

@@ -63,7 +63,12 @@ mod text2;
 mod ui;
 mod util;
 
-use crate::{app::{App, AppPtr}, net::ZeroMQAdapter, text::TextShaper, util::AsyncRuntime};
+use crate::{
+    app::{App, AppPtr},
+    net::ZeroMQAdapter,
+    text::TextShaper,
+    util::AsyncRuntime,
+};
 
 // This is historical, but ideally we can fix the entire project and remove this import.
 pub use util::ExecutorPtr;
@@ -156,7 +161,7 @@ impl God {
 
             render_api,
             method_rep,
-            event_pub
+            event_pub,
         }
     }
 

+ 4 - 1
bin/app/src/text2/editor/android.rs

@@ -24,7 +24,10 @@ use crate::{
     text2::{TextContext, TEXT_CTX},
     AndroidSuggestEvent,
 };
-use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
+use std::sync::{
+    atomic::{AtomicBool, Ordering},
+    Arc,
+};
 
 macro_rules! t { ($($arg:tt)*) => { trace!(target: "text::editor::android", $($arg)*); } }
 macro_rules! w { ($($arg:tt)*) => { warn!(target: "text::editor::android", $($arg)*) } }

+ 8 - 2
bin/app/src/ui/chatedit.rs

@@ -27,12 +27,12 @@ use rand::{rngs::OsRng, Rng};
 use std::{
     collections::HashMap,
     io::Cursor,
+    ops::{Deref, DerefMut},
     sync::{
         atomic::{AtomicBool, Ordering},
         Arc, Weak,
     },
     time::Instant,
-    ops::{Deref, DerefMut}
 };
 
 use crate::{
@@ -1390,7 +1390,13 @@ impl UIObject for ChatEdit {
     fn init(&self) {
         let mut guard = self.editor.lock_blocking();
         assert!(guard.is_none());
-        *guard = Some(Editor::new(self.text.clone(), self.font_size.clone(), self.text_color.clone(), self.window_scale.clone(), self.lineheight.clone()));
+        *guard = Some(Editor::new(
+            self.text.clone(),
+            self.font_size.clone(),
+            self.text_color.clone(),
+            self.window_scale.clone(),
+            self.lineheight.clone(),
+        ));
     }
 
     async fn start(self: Arc<Self>, ex: ExecutorPtr) {

+ 22 - 13
bin/app/src/ui/chatview/mod.rs

@@ -23,6 +23,7 @@ use chrono::{Local, TimeZone};
 use darkfi::system::{msleep, CondVar};
 use darkfi_serial::{deserialize, Decodable, Encodable, SerialDecodable, SerialEncodable};
 use miniquad::{KeyCode, KeyMods, MouseButton, TouchPhase};
+use parking_lot::Mutex as SyncMutex;
 use rand::{rngs::OsRng, Rng};
 use sled_overlay::sled;
 use std::{
@@ -31,7 +32,7 @@ use std::{
     io::Cursor,
     sync::{
         atomic::{AtomicBool, Ordering},
-        Arc, Mutex as SyncMutex, OnceLock, Weak,
+        Arc, Weak,
     },
 };
 
@@ -151,7 +152,7 @@ pub type ChatViewPtr = Arc<ChatView>;
 
 pub struct ChatView {
     node: SceneNodeWeak,
-    tasks: OnceLock<Vec<smol::Task<()>>>,
+    tasks: SyncMutex<Vec<smol::Task<()>>>,
     render_api: RenderApi,
     text_shaper: TextShaperPtr,
 
@@ -181,7 +182,8 @@ pub struct ChatView {
 
     mouse_btn_held: AtomicBool,
 
-    /// Triggers the background loading task to wake up
+    /// Triggers the background loading task to wake up.
+    /// We use this since there should only ever be a single bg task loading.
     bgload_cv: Arc<CondVar>,
 
     /// We use it when we re-eval rect when its changed via property.
@@ -235,7 +237,7 @@ impl ChatView {
 
         let self_ = Arc::new(Self {
             node: node.clone(),
-            tasks: OnceLock::new(),
+            tasks: SyncMutex::new(vec![]),
             render_api: render_api.clone(),
             text_shaper: text_shaper.clone(),
 
@@ -370,7 +372,7 @@ impl ChatView {
 
     fn end_touch_phase(&self, touch_y: f32) {
         // Now calculate scroll acceleration
-        let touch_info = std::mem::replace(&mut *self.touch_info.lock().unwrap(), None);
+        let touch_info = std::mem::replace(&mut *self.touch_info.lock(), None);
         let Some(touch_info) = &touch_info else { return };
 
         self.touch_is_active.store(false, Ordering::Relaxed);
@@ -710,7 +712,7 @@ impl ChatView {
     async fn redraw_all(&self) {
         let trace_id = rand::random();
         t!("ChatView::redraw_all() [trace_id={trace_id}]");
-        let parent_rect = self.parent_rect.lock().unwrap().unwrap().clone();
+        let parent_rect = self.parent_rect.lock().unwrap().clone();
         self.rect.eval(&parent_rect).expect("unable to eval rect");
 
         let mut msgbuf = self.msgbuf.lock().await;
@@ -806,7 +808,14 @@ impl UIObject for ChatView {
             vec![insert_line_method_task, insert_unconf_line_method_task, motion_task, bgload_task];
         tasks.append(&mut on_modify.tasks);
 
-        self.tasks.set(tasks);
+        *self.tasks.lock() = tasks;
+    }
+
+    fn stop(&self) {
+        self.tasks.lock().clear();
+        *self.parent_rect.lock() = None;
+        // Clear mesh caches
+        self.msgbuf.lock_blocking().clear();
     }
 
     async fn draw(
@@ -817,7 +826,7 @@ impl UIObject for ChatView {
     ) -> Option<DrawUpdate> {
         t!("ChatView::draw({:?}, {trace_id})", self.node.upgrade().unwrap());
 
-        *self.parent_rect.lock().unwrap() = Some(parent_rect.clone());
+        *self.parent_rect.lock() = Some(parent_rect.clone());
         self.rect.eval(&parent_rect).ok()?;
         let rect = self.rect.get();
 
@@ -902,7 +911,7 @@ impl UIObject for ChatView {
         t!("handle_mouse_move({mouse_pos:?})");
 
         // We store the mouse pos for use in handle_mouse_wheel()
-        *self.mouse_pos.lock().unwrap() = mouse_pos.clone();
+        *self.mouse_pos.lock() = mouse_pos.clone();
 
         if !self.mouse_btn_held.load(Ordering::Relaxed) {
             return false
@@ -924,7 +933,7 @@ impl UIObject for ChatView {
 
         let rect = self.rect.get();
 
-        let mouse_pos = self.mouse_pos.lock().unwrap().clone();
+        let mouse_pos = self.mouse_pos.lock().clone();
         if !rect.contains(mouse_pos) {
             t!("not inside rect");
             return false
@@ -948,7 +957,7 @@ impl UIObject for ChatView {
 
         if !rect.contains(touch_pos) {
             match phase {
-                TouchPhase::Started => *self.touch_info.lock().unwrap() = None,
+                TouchPhase::Started => *self.touch_info.lock() = None,
                 _ => self.end_touch_phase(touch_y),
             }
             return false
@@ -961,12 +970,12 @@ impl UIObject for ChatView {
             TouchPhase::Started => {
                 self.touch_is_active.store(true, Ordering::Relaxed);
 
-                let mut touch_info = self.touch_info.lock().unwrap();
+                let mut touch_info = self.touch_info.lock();
                 *touch_info = Some(TouchInfo::new(self.scroll.get(), touch_y));
             }
             TouchPhase::Moved => {
                 let (start_scroll, start_y, start_elapsed, do_update, is_select_mode) = {
-                    let mut touch_info = self.touch_info.lock().unwrap();
+                    let mut touch_info = self.touch_info.lock();
                     let Some(touch_info) = &mut *touch_info else { return false };
 
                     touch_info.last_y = touch_y;

+ 5 - 0
bin/app/src/ui/chatview/page.rs

@@ -707,6 +707,11 @@ impl MessageBuffer {
         }
     }
 
+    pub fn clear(&mut self) {
+        self.msgs.clear();
+        self.date_msgs.clear();
+    }
+
     fn node(&self) -> SceneNodePtr {
         self.node.upgrade().unwrap()
     }

+ 6 - 1
bin/app/src/ui/emoji_picker/emoji.rs

@@ -16,11 +16,12 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
+use parking_lot::Mutex as SyncMutex;
 use std::{
     fs::File,
     io::{BufRead, BufReader},
     path::{Path, PathBuf},
-    sync::{Arc, LazyLock, Mutex as SyncMutex},
+    sync::{Arc, LazyLock},
 };
 
 use crate::{
@@ -76,6 +77,10 @@ impl EmojiMeshes {
         }))
     }
 
+    pub fn clear(&mut self) {
+        self.meshes.clear();
+    }
+
     pub fn get(&mut self, i: usize) -> GfxDrawMesh {
         let emoji_list = self.get_list();
         assert!(i < emoji_list.len());

+ 16 - 10
bin/app/src/ui/emoji_picker/mod.rs

@@ -20,12 +20,13 @@ use async_trait::async_trait;
 use darkfi_serial::Encodable;
 use image::ImageReader;
 use miniquad::{MouseButton, TouchPhase};
+use parking_lot::Mutex as SyncMutex;
 use rand::{rngs::OsRng, Rng};
 use std::{
     io::Cursor,
     sync::{
         atomic::{AtomicBool, Ordering},
-        Arc, Mutex as SyncMutex, OnceLock, Weak,
+        Arc, Weak,
     },
 };
 
@@ -65,7 +66,7 @@ pub type EmojiPickerPtr = Arc<EmojiPicker>;
 pub struct EmojiPicker {
     node: SceneNodeWeak,
     render_api: RenderApi,
-    tasks: OnceLock<Vec<smol::Task<()>>>,
+    tasks: SyncMutex<Vec<smol::Task<()>>>,
 
     dc_key: u64,
     emoji_meshes: EmojiMeshesPtr,
@@ -108,7 +109,7 @@ impl EmojiPicker {
         let self_ = Arc::new(Self {
             node,
             render_api,
-            tasks: OnceLock::new(),
+            tasks: SyncMutex::new(vec![]),
 
             dc_key: OsRng.gen(),
             emoji_meshes,
@@ -144,7 +145,7 @@ impl EmojiPicker {
     }
 
     fn max_scroll(&self) -> f32 {
-        let emojis_len = self.emoji_meshes.lock().unwrap().get_list().len() as f32;
+        let emojis_len = self.emoji_meshes.lock().get_list().len() as f32;
         let emoji_size = self.emoji_size.get();
         let cols = self.emojis_per_line();
         let rows = (emojis_len / cols).ceil();
@@ -178,7 +179,7 @@ impl EmojiPicker {
         //d!("    = {idx}, emoji_len = {}", emoji::EMOJI_LIST.len());
 
         let emoji_selected = {
-            let mut emoji_meshes = self.emoji_meshes.lock().unwrap();
+            let mut emoji_meshes = self.emoji_meshes.lock();
             let emoji_list = emoji_meshes.get_list();
 
             if idx < emoji_list.len() {
@@ -205,7 +206,7 @@ impl EmojiPicker {
         let trace_id = rand::random();
         let timest = unixtime();
         t!("redraw({:?}) [timest={timest}, trace_id={trace_id}]", self.node.upgrade().unwrap());
-        let Some(parent_rect) = self.parent_rect.lock().unwrap().clone() else { return };
+        let Some(parent_rect) = self.parent_rect.lock().clone() else { return };
 
         let Some(draw_update) = self.get_draw_calls(parent_rect, trace_id, atom) else {
             error!(target: "ui::emoji_picker", "Emoji picker failed to draw");
@@ -238,7 +239,7 @@ impl EmojiPicker {
         let off_x = self.calc_off_x();
         let emoji_size = self.emoji_size.get();
 
-        let mut emoji_meshes = self.emoji_meshes.lock().unwrap();
+        let mut emoji_meshes = self.emoji_meshes.lock();
         let emoji_list_len = emoji_meshes.get_list().len();
 
         let mut x = emoji_size / 2.;
@@ -290,7 +291,12 @@ impl UIObject for EmojiPicker {
         on_modify.when_change(self.rect.prop(), redraw);
         on_modify.when_change(self.z_index.prop(), redraw);
 
-        self.tasks.set(on_modify.tasks);
+        *self.tasks.lock() = on_modify.tasks;
+    }
+
+    fn stop(&self) {
+        self.tasks.lock().clear();
+        self.emoji_meshes.lock().clear();
     }
 
     async fn draw(
@@ -300,7 +306,7 @@ impl UIObject for EmojiPicker {
         atom: &mut PropertyAtomicGuard,
     ) -> Option<DrawUpdate> {
         t!("EmojiPicker::draw({parent_rect:?}, {trace_id})");
-        *self.parent_rect.lock().unwrap() = Some(parent_rect);
+        *self.parent_rect.lock() = Some(parent_rect);
         self.get_draw_calls(parent_rect, trace_id, atom)
     }
 
@@ -354,7 +360,7 @@ impl UIObject for EmojiPicker {
         // todo: clean this up
         let mut emoji_is_clicked = false;
         {
-            let mut touch_info = self.touch_info.lock().unwrap();
+            let mut touch_info = self.touch_info.lock();
             match phase {
                 TouchPhase::Started => {
                     if !rect.contains(touch_pos) {

+ 21 - 12
bin/app/src/ui/image.rs

@@ -18,10 +18,11 @@
 
 use async_trait::async_trait;
 use image::ImageReader;
+use parking_lot::Mutex as SyncMutex;
 use rand::{rngs::OsRng, Rng};
 use std::{
     io::Cursor,
-    sync::{Arc, Mutex as SyncMutex, OnceLock, Weak},
+    sync::{Arc, Weak},
 };
 
 use crate::{
@@ -46,7 +47,7 @@ pub type ImagePtr = Arc<Image>;
 pub struct Image {
     node: SceneNodeWeak,
     render_api: RenderApi,
-    tasks: OnceLock<Vec<smol::Task<()>>>,
+    tasks: SyncMutex<Vec<smol::Task<()>>>,
 
     texture: SyncMutex<Option<ManagedTexturePtr>>,
     dc_key: u64,
@@ -77,7 +78,7 @@ impl Image {
         let self_ = Arc::new(Self {
             node,
             render_api,
-            tasks: OnceLock::new(),
+            tasks: SyncMutex::new(vec![]),
 
             texture: SyncMutex::new(None),
             dc_key: OsRng.gen(),
@@ -91,14 +92,12 @@ impl Image {
             parent_rect: SyncMutex::new(None),
         });
 
-        *self_.texture.lock().unwrap() = Some(self_.load_texture());
-
         Pimpl::Image(self_)
     }
 
     async fn reload(self: Arc<Self>) {
         let texture = self.load_texture();
-        let old_texture = std::mem::replace(&mut *self.texture.lock().unwrap(), Some(texture));
+        *self.texture.lock() = Some(texture);
 
         self.clone().redraw().await;
     }
@@ -110,13 +109,13 @@ impl Image {
         let data = Arc::new(SyncMutex::new(vec![]));
         let data2 = data.clone();
         miniquad::fs::load_file(&path.clone(), move |res| match res {
-            Ok(res) => *data2.lock().unwrap() = res,
+            Ok(res) => *data2.lock() = res,
             Err(e) => {
                 error!(target: "ui::image", "Unable to open image: {path}");
                 panic!("Resource not found!");
             }
         });
-        let data = std::mem::take(&mut *data.lock().unwrap());
+        let data = std::mem::take(&mut *data.lock());
         let img =
             ImageReader::new(Cursor::new(data)).with_guessed_format().unwrap().decode().unwrap();
         let img = img.to_rgba8();
@@ -134,7 +133,7 @@ impl Image {
         let trace_id = rand::random();
         let timest = unixtime();
         t!("redraw({:?}) [trace_id={trace_id}]", self.node.upgrade().unwrap());
-        let Some(parent_rect) = self.parent_rect.lock().unwrap().clone() else { return };
+        let Some(parent_rect) = self.parent_rect.lock().clone() else { return };
 
         let Some(draw_update) = self.get_draw_calls(parent_rect, trace_id).await else {
             error!(target: "ui::image", "Image failed to draw");
@@ -160,7 +159,7 @@ impl Image {
         self.uv.eval(&rect).ok()?;
 
         let mesh = self.regen_mesh();
-        let texture = self.texture.lock().unwrap().clone().expect("Node missing texture_id!");
+        let texture = self.texture.lock().clone().expect("Node missing texture_id!");
 
         let mesh = GfxDrawMesh {
             vertex_buffer: mesh.vertex_buffer,
@@ -192,6 +191,10 @@ impl UIObject for Image {
         self.priority.get()
     }
 
+    fn init(&self) {
+        *self.texture.lock() = Some(self.load_texture());
+    }
+
     async fn start(self: Arc<Self>, ex: ExecutorPtr) {
         let me = Arc::downgrade(&self);
 
@@ -201,7 +204,13 @@ impl UIObject for Image {
         on_modify.when_change(self.z_index.prop(), Self::redraw);
         on_modify.when_change(self.path.prop(), Self::reload);
 
-        self.tasks.set(on_modify.tasks);
+        *self.tasks.lock() = on_modify.tasks;
+    }
+
+    fn stop(&self) {
+        self.tasks.lock().clear();
+        *self.parent_rect.lock() = None;
+        *self.texture.lock() = None;
     }
 
     async fn draw(
@@ -211,7 +220,7 @@ impl UIObject for Image {
         atom: &mut PropertyAtomicGuard,
     ) -> Option<DrawUpdate> {
         t!("Image::draw() [trace_id={trace_id}]");
-        *self.parent_rect.lock().unwrap() = Some(parent_rect);
+        *self.parent_rect.lock() = Some(parent_rect);
         self.get_draw_calls(parent_rect, trace_id).await
     }
 }

+ 2 - 2
bin/app/src/ui/layer.rs

@@ -20,9 +20,9 @@ use async_recursion::async_recursion;
 use async_trait::async_trait;
 use atomic_float::AtomicF32;
 use miniquad::{KeyCode, KeyMods, MouseButton, TouchPhase};
-use rand::{rngs::OsRng, Rng};
 use parking_lot::Mutex as SyncMutex;
-use std::sync::{atomic::Ordering, Arc, OnceLock, Weak};
+use rand::{rngs::OsRng, Rng};
+use std::sync::{atomic::Ordering, Arc, Weak};
 
 use crate::{
     gfx::{GfxDrawCall, GfxDrawInstruction, Point, Rectangle, RenderApi},

+ 1 - 1
bin/app/src/ui/text.rs

@@ -17,8 +17,8 @@
  */
 
 use async_trait::async_trait;
-use rand::{rngs::OsRng, Rng};
 use parking_lot::Mutex as SyncMutex;
+use rand::{rngs::OsRng, Rng};
 use std::sync::{Arc, Weak};
 
 use crate::{

+ 2 - 2
bin/app/src/ui/vector_art/mod.rs

@@ -17,9 +17,9 @@
  */
 
 use async_trait::async_trait;
-use rand::{rngs::OsRng, Rng};
 use parking_lot::Mutex as SyncMutex;
-use std::sync::{Arc, OnceLock, Weak};
+use rand::{rngs::OsRng, Rng};
+use std::sync::{Arc, Weak};
 
 use crate::{
     error::{Error, Result},

+ 4 - 2
bin/app/src/ui/win.rs

@@ -68,9 +68,11 @@ impl Window {
             Role::Internal,
             "value",
             0,
-        ).unwrap();
+        )
+        .unwrap();
 
-        let self_ = Arc::new(Self { node, tasks: SyncMutex::new(vec![]), screen_size, scale, render_api });
+        let self_ =
+            Arc::new(Self { node, tasks: SyncMutex::new(vec![]), screen_size, scale, render_api });
 
         Pimpl::Window(self_)
     }