소스 검색

wallet: introduce PropertyAtomicGuard which allows us to control *when* property updates are propagated throughout the scenegraph. See src/prop/guard.rs for a detailed desc.

darkfi 1 년 전
부모
커밋
617f786bf3

+ 20 - 11
bin/darkwallet/src/app/mod.rs

@@ -34,7 +34,10 @@ use crate::{
     expr::Op,
     gfx::{GraphicsEventPublisherPtr, RenderApi, Vertex},
     plugin::{self, PluginObject},
-    prop::{Property, PropertyBool, PropertyStr, PropertySubType, PropertyType, Role},
+    prop::{
+        Property, PropertyAtomicGuard, PropertyBool, PropertyStr, PropertySubType, PropertyType,
+        Role,
+    },
     scene::{Pimpl, SceneNode as SceneNode3, SceneNodePtr, SceneNodeType as SceneNodeType3, Slot},
     text::TextShaperPtr,
     ui::{chatview, Window},
@@ -155,6 +158,7 @@ impl App {
     /// the objects.
     pub async fn setup(&self) {
         t!("App::setup()");
+        let atom = &mut PropertyAtomicGuard::new();
 
         let mut window = SceneNode3::new("window", SceneNodeType3::Window);
 
@@ -193,6 +197,8 @@ impl App {
         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();
@@ -232,16 +238,16 @@ impl App {
                 let prop = menu_label.get_property("text_color").unwrap();
                 if msg.contains(&darkirc_nick.get()) {
                     // Nick highlight
-                    prop.set_f32(Role::App, 0, 0.56).unwrap();
-                    prop.set_f32(Role::App, 1, 0.61).unwrap();
-                    prop.set_f32(Role::App, 2, 1.).unwrap();
-                    prop.set_f32(Role::App, 3, 1.).unwrap();
+                    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.set_f32(Role::App, 0, 0.36).unwrap();
-                    prop.set_f32(Role::App, 1, 1.).unwrap();
-                    prop.set_f32(Role::App, 2, 0.51).unwrap();
-                    prop.set_f32(Role::App, 3, 1.).unwrap();
+                    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();
                 }
             }
         });
@@ -255,13 +261,16 @@ impl App {
     /// Begins the draw of the tree, and then starts the UI procs.
     pub async fn start(self: Arc<Self>) {
         d!("Starting app");
+        let atom = &mut PropertyAtomicGuard::new();
 
         let window_node = self.sg_root.clone().lookup_node("/window").unwrap();
         let prop = window_node.get_property("screen_size").unwrap();
         // We can only do this once the window has been created in miniquad.
         let (screen_width, screen_height) = miniquad::window::screen_size();
-        prop.set_f32(Role::App, 0, screen_width);
-        prop.set_f32(Role::App, 1, screen_height);
+        prop.clone().set_f32(atom, Role::App, 0, screen_width);
+        prop.clone().set_f32(atom, Role::App, 1, screen_height);
+
+        drop(atom);
 
         // Access drawable in window node and call draw()
         self.trigger_draw().await;

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

@@ -298,6 +298,9 @@ pub fn create_chatedit(name: &str) -> SceneNode {
     let prop = Property::new("max_height", PropertyType::Float32, PropertySubType::Pixel);
     node.add_property(prop).unwrap();
 
+    let prop = Property::new("height", PropertyType::Float32, PropertySubType::Pixel);
+    node.add_property(prop).unwrap();
+
     let mut prop = Property::new("rect", PropertyType::Float32, PropertySubType::Pixel);
     prop.set_array_len(4);
     prop.allow_exprs();

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 307 - 299
bin/darkwallet/src/app/schema/chat.rs


+ 69 - 66
bin/darkwallet/src/app/schema/menu.rs

@@ -31,7 +31,8 @@ use crate::{
     gfx::{GraphicsEventPublisherPtr, Rectangle, RenderApi, Vertex},
     mesh::{Color, MeshBuilder},
     prop::{
-        Property, PropertyBool, PropertyFloat32, PropertyStr, PropertySubType, PropertyType, Role,
+        Property, PropertyAtomicGuard, PropertyBool, PropertyFloat32, PropertyStr, PropertySubType,
+        PropertyType, Role,
     },
     scene::{SceneNodePtr, Slot},
     shape,
@@ -77,18 +78,19 @@ use ui_consts::*;
 
 pub async fn make(app: &App, window: SceneNodePtr) {
     let window_scale = PropertyFloat32::wrap(&window, Role::Internal, "scale", 0).unwrap();
+    let atom = &mut PropertyAtomicGuard::new();
 
     let mut cc = Compiler::new();
 
     // Main view
     let layer_node = create_layer("menu_layer");
     let prop = layer_node.get_property("rect").unwrap();
-    prop.set_f32(Role::App, 0, 0.).unwrap();
-    prop.set_f32(Role::App, 1, 0.).unwrap();
-    prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
-    prop.set_expr(Role::App, 3, expr::load_var("h")).unwrap();
-    layer_node.set_property_bool(Role::App, "is_visible", true).unwrap();
-    layer_node.set_property_u32(Role::App, "z_index", 1).unwrap();
+    prop.clone().set_f32(atom, Role::App, 0, 0.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 1, 0.).unwrap();
+    prop.clone().set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
+    prop.clone().set_expr(atom, Role::App, 3, expr::load_var("h")).unwrap();
+    layer_node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
+    layer_node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
     let layer_node =
         layer_node.setup(|me| Layer::new(me, app.render_api.clone(), app.ex.clone())).await;
     window.link(layer_node.clone());
@@ -98,11 +100,11 @@ pub async fn make(app: &App, window: SceneNodePtr) {
     // Channels label bg
     let node = create_vector_art("channels_label_bg");
     let prop = node.get_property("rect").unwrap();
-    prop.set_f32(Role::App, 0, 0.).unwrap();
-    prop.set_f32(Role::App, 1, channel_y).unwrap();
-    prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
-    prop.set_f32(Role::App, 3, CHANNEL_LABEL_LINESPACE).unwrap();
-    node.set_property_u32(Role::App, "z_index", 0).unwrap();
+    prop.clone().set_f32(atom, Role::App, 0, 0.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 1, channel_y).unwrap();
+    prop.clone().set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
+    prop.clone().set_f32(atom, Role::App, 3, CHANNEL_LABEL_LINESPACE).unwrap();
+    node.set_property_u32(atom, Role::App, "z_index", 0).unwrap();
 
     let mut shape = VectorShape::new();
 
@@ -139,28 +141,28 @@ pub async fn make(app: &App, window: SceneNodePtr) {
     // Create some text
     let node = create_text("channels_label");
     let prop = node.get_property("rect").unwrap();
-    prop.set_f32(Role::App, 0, CHANNEL_LABEL_X).unwrap();
-    prop.set_f32(Role::App, 1, channel_y).unwrap();
-    prop.set_f32(Role::App, 2, 1000.).unwrap();
-    prop.set_f32(Role::App, 3, 200.).unwrap();
-    node.set_property_u32(Role::App, "z_index", 1).unwrap();
-    node.set_property_f32(Role::App, "baseline", CHANNEL_LABEL_BASELINE).unwrap();
-    node.set_property_f32(Role::App, "font_size", CHANNEL_LABEL_FONTSIZE).unwrap();
-    node.set_property_str(Role::App, "text", "CHANNELS").unwrap();
-    //node.set_property_str(Role::App, "text", "anon1").unwrap();
+    prop.clone().set_f32(atom, Role::App, 0, CHANNEL_LABEL_X).unwrap();
+    prop.clone().set_f32(atom, Role::App, 1, channel_y).unwrap();
+    prop.clone().set_f32(atom, Role::App, 2, 1000.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 3, 200.).unwrap();
+    node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
+    node.set_property_f32(atom, Role::App, "baseline", CHANNEL_LABEL_BASELINE).unwrap();
+    node.set_property_f32(atom, Role::App, "font_size", CHANNEL_LABEL_FONTSIZE).unwrap();
+    node.set_property_str(atom, Role::App, "text", "CHANNELS").unwrap();
+    //node.set_property_str(atom, Role::App, "text", "anon1").unwrap();
     let prop = node.get_property("text_color").unwrap();
     if COLOR_SCHEME == ColorScheme::DarkMode {
-        prop.set_f32(Role::App, 0, 0.65).unwrap();
-        prop.set_f32(Role::App, 1, 0.87).unwrap();
-        prop.set_f32(Role::App, 2, 0.83).unwrap();
-        prop.set_f32(Role::App, 3, 1.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 0, 0.65).unwrap();
+        prop.clone().set_f32(atom, Role::App, 1, 0.87).unwrap();
+        prop.clone().set_f32(atom, Role::App, 2, 0.83).unwrap();
+        prop.clone().set_f32(atom, Role::App, 3, 1.).unwrap();
     } else if COLOR_SCHEME == ColorScheme::PaperLight {
-        prop.set_f32(Role::App, 0, 0.).unwrap();
-        prop.set_f32(Role::App, 1, 0.).unwrap();
-        prop.set_f32(Role::App, 2, 0.).unwrap();
-        prop.set_f32(Role::App, 3, 1.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 0, 0.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 1, 0.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 2, 0.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 3, 1.).unwrap();
     }
-    node.set_property_u32(Role::App, "z_index", 1).unwrap();
+    node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
 
     let node = node
         .setup(|me| {
@@ -182,11 +184,11 @@ pub async fn make(app: &App, window: SceneNodePtr) {
 
         let node = create_vector_art(&(channel.to_string() + "_channel_label_bg"));
         let prop = node.get_property("rect").unwrap();
-        prop.set_f32(Role::App, 0, 0.).unwrap();
-        prop.set_f32(Role::App, 1, channel_y).unwrap();
-        prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
-        prop.set_f32(Role::App, 3, CHANNEL_LABEL_LINESPACE).unwrap();
-        node.set_property_u32(Role::App, "z_index", 0).unwrap();
+        prop.clone().set_f32(atom, Role::App, 0, 0.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 1, channel_y).unwrap();
+        prop.clone().set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
+        prop.clone().set_f32(atom, Role::App, 3, CHANNEL_LABEL_LINESPACE).unwrap();
+        node.set_property_u32(atom, Role::App, "z_index", 0).unwrap();
 
         let mut shape = VectorShape::new();
         let bg_color = match COLOR_SCHEME {
@@ -220,32 +222,32 @@ pub async fn make(app: &App, window: SceneNodePtr) {
         // Create some text
         let node = create_text(&(channel.to_string() + "_channel_label"));
         let prop = node.get_property("rect").unwrap();
-        prop.set_f32(Role::App, 0, CHANNEL_LABEL_X).unwrap();
-        prop.set_f32(Role::App, 1, channel_y).unwrap();
-        prop.set_f32(Role::App, 2, 1000.).unwrap();
-        prop.set_f32(Role::App, 3, 200.).unwrap();
-        node.set_property_u32(Role::App, "z_index", 1).unwrap();
-        node.set_property_f32(Role::App, "baseline", CHANNEL_LABEL_BASELINE).unwrap();
-        node.set_property_f32(Role::App, "font_size", CHANNEL_LABEL_FONTSIZE).unwrap();
-        node.set_property_str(Role::App, "text", text).unwrap();
-        //node.set_property_bool(Role::App, "debug", true).unwrap();
-        //node.set_property_str(Role::App, "text", "anon1").unwrap();
+        prop.clone().set_f32(atom, Role::App, 0, CHANNEL_LABEL_X).unwrap();
+        prop.clone().set_f32(atom, Role::App, 1, channel_y).unwrap();
+        prop.clone().set_f32(atom, Role::App, 2, 1000.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 3, 200.).unwrap();
+        node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
+        node.set_property_f32(atom, Role::App, "baseline", CHANNEL_LABEL_BASELINE).unwrap();
+        node.set_property_f32(atom, Role::App, "font_size", CHANNEL_LABEL_FONTSIZE).unwrap();
+        node.set_property_str(atom, Role::App, "text", text).unwrap();
+        //node.set_property_bool(atom, Role::App, "debug", true).unwrap();
+        //node.set_property_str(atom, Role::App, "text", "anon1").unwrap();
         let color_prop = node.get_property("text_color").unwrap();
-        let set_normal_color = move || {
+        let set_normal_color = move |atom: &mut PropertyAtomicGuard| {
             if COLOR_SCHEME == ColorScheme::DarkMode {
-                color_prop.set_f32(Role::App, 0, 1.).unwrap();
-                color_prop.set_f32(Role::App, 1, 1.).unwrap();
-                color_prop.set_f32(Role::App, 2, 1.).unwrap();
-                color_prop.set_f32(Role::App, 3, 1.).unwrap();
+                color_prop.clone().set_f32(atom, Role::App, 0, 1.).unwrap();
+                color_prop.clone().set_f32(atom, Role::App, 1, 1.).unwrap();
+                color_prop.clone().set_f32(atom, Role::App, 2, 1.).unwrap();
+                color_prop.clone().set_f32(atom, Role::App, 3, 1.).unwrap();
             } else if COLOR_SCHEME == ColorScheme::PaperLight {
-                color_prop.set_f32(Role::App, 0, 0.).unwrap();
-                color_prop.set_f32(Role::App, 1, 0.).unwrap();
-                color_prop.set_f32(Role::App, 2, 0.).unwrap();
-                color_prop.set_f32(Role::App, 3, 1.).unwrap();
+                color_prop.clone().set_f32(atom, Role::App, 0, 0.).unwrap();
+                color_prop.clone().set_f32(atom, Role::App, 1, 0.).unwrap();
+                color_prop.clone().set_f32(atom, Role::App, 2, 0.).unwrap();
+                color_prop.clone().set_f32(atom, Role::App, 3, 1.).unwrap();
             }
         };
-        set_normal_color();
-        node.set_property_u32(Role::App, "z_index", 3).unwrap();
+        set_normal_color(atom);
+        node.set_property_u32(atom, Role::App, "z_index", 3).unwrap();
 
         let node = node
             .setup(|me| {
@@ -262,12 +264,12 @@ pub async fn make(app: &App, window: SceneNodePtr) {
 
         // Create the button
         let node = create_button(&(channel.to_string() + "_channel_btn"));
-        node.set_property_bool(Role::App, "is_active", true).unwrap();
+        node.set_property_bool(atom, Role::App, "is_active", true).unwrap();
         let prop = node.get_property("rect").unwrap();
-        prop.set_f32(Role::App, 0, 0.).unwrap();
-        prop.set_f32(Role::App, 1, channel_y).unwrap();
-        prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
-        prop.set_f32(Role::App, 3, CHANNEL_LABEL_LINESPACE).unwrap();
+        prop.clone().set_f32(atom, Role::App, 0, 0.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 1, channel_y).unwrap();
+        prop.clone().set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
+        prop.clone().set_f32(atom, Role::App, 3, CHANNEL_LABEL_LINESPACE).unwrap();
 
         let (slot, recvr) = Slot::new(channel.to_string() + "_clicked");
         node.register("click", slot).unwrap();
@@ -278,10 +280,11 @@ pub async fn make(app: &App, window: SceneNodePtr) {
         let menu_is_visible = PropertyBool::wrap(&layer_node, Role::App, "is_visible", 0).unwrap();
 
         let select_channel = move || {
+            let atom = &mut PropertyAtomicGuard::new();
             info!(target: "app::menu", "clicked: {channel}!");
-            chatview_is_visible.set(true);
-            menu_is_visible.set(false);
-            set_normal_color();
+            chatview_is_visible.set(atom, true);
+            menu_is_visible.set(atom, false);
+            set_normal_color(atom);
         };
 
         let select_channel2 = select_channel.clone();
@@ -299,8 +302,8 @@ pub async fn make(app: &App, window: SceneNodePtr) {
         let channel_id = i + 1;
         let node = create_shortcut(&format!("channel_shortcut_{channel_id}"));
         let key = format!("alt+{channel_id}");
-        node.set_property_str(Role::App, "key", key).unwrap();
-        node.set_property_u32(Role::App, "priority", 1).unwrap();
+        node.set_property_str(atom, Role::App, "key", key).unwrap();
+        node.set_property_u32(atom, Role::App, "priority", 1).unwrap();
 
         let (slot, recvr) = Slot::new("back_pressed");
         node.register("shortcut", slot).unwrap();

+ 31 - 29
bin/darkwallet/src/app/schema/mod.rs

@@ -32,7 +32,8 @@ use crate::{
     gfx::{GraphicsEventPublisherPtr, Rectangle, RenderApi, Vertex},
     mesh::{Color, MeshBuilder},
     prop::{
-        Property, PropertyBool, PropertyFloat32, PropertyStr, PropertySubType, PropertyType, Role,
+        Property, PropertyAtomicGuard, PropertyBool, PropertyFloat32, PropertyStr, PropertySubType,
+        PropertyType, Role,
     },
     scene::{SceneNodePtr, Slot},
     shape,
@@ -112,17 +113,18 @@ enum ColorScheme {
 
 pub async fn make(app: &App, window: SceneNodePtr) {
     let mut cc = Compiler::new();
+    let atom = &mut PropertyAtomicGuard::new();
 
     if COLOR_SCHEME == ColorScheme::DarkMode {
         // Bg layer
         let layer_node = create_layer("bg_layer");
         let prop = layer_node.get_property("rect").unwrap();
-        prop.set_f32(Role::App, 0, 0.).unwrap();
-        prop.set_f32(Role::App, 1, 0.).unwrap();
-        prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
-        prop.set_expr(Role::App, 3, expr::load_var("h")).unwrap();
-        layer_node.set_property_bool(Role::App, "is_visible", true).unwrap();
-        layer_node.set_property_u32(Role::App, "z_index", 0).unwrap();
+        prop.clone().set_f32(atom, Role::App, 0, 0.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 1, 0.).unwrap();
+        prop.clone().set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
+        prop.clone().set_expr(atom, Role::App, 3, expr::load_var("h")).unwrap();
+        layer_node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
+        layer_node.set_property_u32(atom, Role::App, "z_index", 0).unwrap();
         let layer_node =
             layer_node.setup(|me| Layer::new(me, app.render_api.clone(), app.ex.clone())).await;
         window.clone().link(layer_node.clone());
@@ -130,10 +132,10 @@ pub async fn make(app: &App, window: SceneNodePtr) {
         // Create a bg image
         let node = create_image("bg_image");
         let prop = node.get_property("rect").unwrap();
-        prop.set_f32(Role::App, 0, 0.).unwrap();
-        prop.set_f32(Role::App, 1, 0.).unwrap();
-        prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
-        prop.set_expr(Role::App, 3, expr::load_var("h")).unwrap();
+        prop.clone().set_f32(atom, Role::App, 0, 0.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 1, 0.).unwrap();
+        prop.clone().set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
+        prop.clone().set_expr(atom, Role::App, 3, expr::load_var("h")).unwrap();
 
         // Image aspect ratio
         //let R = 1.78;
@@ -141,8 +143,8 @@ pub async fn make(app: &App, window: SceneNodePtr) {
         cc.add_const_f32("R", R);
 
         let prop = node.get_property("uv").unwrap();
-        prop.set_f32(Role::App, 0, 0.).unwrap();
-        prop.set_f32(Role::App, 1, 0.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 0, 0.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 1, 0.).unwrap();
         #[rustfmt::skip]
     let code = cc.compile("
         r = w / h;
@@ -152,7 +154,7 @@ pub async fn make(app: &App, window: SceneNodePtr) {
             1
         }
     ").unwrap();
-        prop.set_expr(Role::App, 2, code).unwrap();
+        prop.clone().set_expr(atom, Role::App, 2, code).unwrap();
         #[rustfmt::skip]
     let code = cc.compile("
         r = w / h;
@@ -162,21 +164,21 @@ pub async fn make(app: &App, window: SceneNodePtr) {
             R / r
         }
     ").unwrap();
-        prop.set_expr(Role::App, 3, code).unwrap();
+        prop.clone().set_expr(atom, Role::App, 3, code).unwrap();
 
-        node.set_property_str(Role::App, "path", BG_PATH).unwrap();
-        node.set_property_u32(Role::App, "z_index", 0).unwrap();
+        node.set_property_str(atom, Role::App, "path", BG_PATH).unwrap();
+        node.set_property_u32(atom, Role::App, "z_index", 0).unwrap();
         let node = node.setup(|me| Image::new(me, app.render_api.clone(), app.ex.clone())).await;
         layer_node.clone().link(node);
 
         // Create a bg mesh on top to fade the bg image
         let node = create_vector_art("bg");
         let prop = node.get_property("rect").unwrap();
-        prop.set_f32(Role::App, 0, 0.).unwrap();
-        prop.set_f32(Role::App, 1, 0.).unwrap();
-        prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
-        prop.set_expr(Role::App, 3, expr::load_var("h")).unwrap();
-        node.set_property_u32(Role::App, "z_index", 1).unwrap();
+        prop.clone().set_f32(atom, Role::App, 0, 0.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 1, 0.).unwrap();
+        prop.clone().set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
+        prop.clone().set_expr(atom, Role::App, 3, expr::load_var("h")).unwrap();
+        node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
 
         //let c = if LIGHTMODE { 1. } else { 0. };
         let c = 0.;
@@ -197,11 +199,11 @@ pub async fn make(app: &App, window: SceneNodePtr) {
     } else if COLOR_SCHEME == ColorScheme::PaperLight {
         let node = create_vector_art("bg");
         let prop = node.get_property("rect").unwrap();
-        prop.set_f32(Role::App, 0, 0.).unwrap();
-        prop.set_f32(Role::App, 1, 0.).unwrap();
-        prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
-        prop.set_expr(Role::App, 3, expr::load_var("h")).unwrap();
-        node.set_property_u32(Role::App, "z_index", 1).unwrap();
+        prop.clone().set_f32(atom, Role::App, 0, 0.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 1, 0.).unwrap();
+        prop.clone().set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
+        prop.clone().set_expr(atom, Role::App, 3, expr::load_var("h")).unwrap();
+        node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
 
         let c = 1.;
         // Setup the pimpl
@@ -254,7 +256,7 @@ pub async fn make(app: &App, window: SceneNodePtr) {
 
     // @@@ Debug stuff @@@
     //let chatview_node = app.sg_root.clone().lookup_node("/window/dev_chat_layer").unwrap();
-    //chatview_node.set_property_bool(Role::App, "is_visible", true).unwrap();
+    //chatview_node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
     //let menu_node = app.sg_root.clone().lookup_node("/window/menu_layer").unwrap();
-    //menu_node.set_property_bool(Role::App, "is_visible", false).unwrap();
+    //menu_node.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
 }

+ 165 - 160
bin/darkwallet/src/app/schema/test.rs

@@ -31,7 +31,8 @@ use crate::{
     gfx::{GraphicsEventPublisherPtr, Rectangle, RenderApi, Vertex},
     mesh::{Color, MeshBuilder},
     prop::{
-        Property, PropertyBool, PropertyFloat32, PropertyStr, PropertySubType, PropertyType, Role,
+        Property, PropertyAtomicGuard, PropertyBool, PropertyFloat32, PropertyStr, PropertySubType,
+        PropertyType, Role,
     },
     scene::{SceneNodePtr, Slot},
     text::TextShaperPtr,
@@ -59,6 +60,8 @@ mod ui_consts {
 use ui_consts::*;
 
 pub async fn make(app: &App, window: SceneNodePtr) {
+    let atom = &mut PropertyAtomicGuard::new();
+
     let window_scale = PropertyFloat32::wrap(&window, Role::Internal, "scale", 0).unwrap();
 
     let mut cc = Compiler::new();
@@ -66,11 +69,11 @@ pub async fn make(app: &App, window: SceneNodePtr) {
     // Create a layer called view
     let layer_node = create_layer("view");
     let prop = layer_node.get_property("rect").unwrap();
-    prop.set_f32(Role::App, 0, 0.).unwrap();
-    prop.set_f32(Role::App, 1, 0.).unwrap();
-    prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
-    prop.set_expr(Role::App, 3, expr::load_var("h")).unwrap();
-    layer_node.set_property_bool(Role::App, "is_visible", true).unwrap();
+    prop.clone().set_f32(atom, Role::App, 0, 0.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 1, 0.).unwrap();
+    prop.clone().set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
+    prop.clone().set_expr(atom, Role::App, 3, expr::load_var("h")).unwrap();
+    layer_node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
     let layer_node =
         layer_node.setup(|me| Layer::new(me, app.render_api.clone(), app.ex.clone())).await;
     window.link(layer_node.clone());
@@ -78,11 +81,11 @@ pub async fn make(app: &App, window: SceneNodePtr) {
     // Create a bg mesh
     let node = create_vector_art("bg");
     let prop = node.get_property("rect").unwrap();
-    prop.set_f32(Role::App, 0, 0.).unwrap();
-    prop.set_f32(Role::App, 1, 0.).unwrap();
-    prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
-    prop.set_expr(Role::App, 3, expr::load_var("h")).unwrap();
-    node.set_property_u32(Role::App, "z_index", 0).unwrap();
+    prop.clone().set_f32(atom, Role::App, 0, 0.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 1, 0.).unwrap();
+    prop.clone().set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
+    prop.clone().set_expr(atom, Role::App, 3, expr::load_var("h")).unwrap();
+    node.set_property_u32(atom, Role::App, "z_index", 0).unwrap();
 
     let c = if LIGHTMODE { 1. } else { 0. };
     let mut shape = VectorShape::new();
@@ -101,11 +104,11 @@ pub async fn make(app: &App, window: SceneNodePtr) {
     let node = create_vector_art("btnbg");
     let prop = node.get_property("rect").unwrap();
     let code = cc.compile("w - 210").unwrap();
-    prop.set_expr(Role::App, 0, code).unwrap();
-    prop.set_f32(Role::App, 1, 10.).unwrap();
-    prop.set_f32(Role::App, 2, 200.).unwrap();
-    prop.set_f32(Role::App, 3, 60.).unwrap();
-    node.set_property_u32(Role::App, "z_index", 1).unwrap();
+    prop.clone().set_expr(atom, Role::App, 0, code).unwrap();
+    prop.clone().set_f32(atom, Role::App, 1, 10.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 2, 200.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 3, 60.).unwrap();
+    node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
 
     // Setup the pimpl
     let verts = if LIGHTMODE {
@@ -131,13 +134,13 @@ pub async fn make(app: &App, window: SceneNodePtr) {
 
     // Create the button
     let node = create_button("btn");
-    node.set_property_bool(Role::App, "is_active", true).unwrap();
+    node.set_property_bool(atom, Role::App, "is_active", true).unwrap();
     let prop = node.get_property("rect").unwrap();
     let code = cc.compile("w - 220").unwrap();
-    prop.set_expr(Role::App, 0, code).unwrap();
-    prop.set_f32(Role::App, 1, 10.).unwrap();
-    prop.set_f32(Role::App, 2, 200.).unwrap();
-    prop.set_f32(Role::App, 3, 60.).unwrap();
+    prop.clone().set_expr(atom, Role::App, 0, code).unwrap();
+    prop.clone().set_f32(atom, Role::App, 1, 10.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 2, 200.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 3, 60.).unwrap();
 
     //let (sender, btn_click_recvr) = async_channel::unbounded();
     //let slot_click = Slot { name: "button_clicked".to_string(), notify: sender };
@@ -149,11 +152,11 @@ pub async fn make(app: &App, window: SceneNodePtr) {
     // Create another mesh
     let node = create_vector_art("box");
     let prop = node.get_property("rect").unwrap();
-    prop.set_f32(Role::App, 0, 10.).unwrap();
-    prop.set_f32(Role::App, 1, 10.).unwrap();
-    prop.set_f32(Role::App, 2, 60.).unwrap();
-    prop.set_f32(Role::App, 3, 60.).unwrap();
-    node.set_property_u32(Role::App, "z_index", 1).unwrap();
+    prop.clone().set_f32(atom, Role::App, 0, 10.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 1, 10.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 2, 60.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 3, 60.).unwrap();
+    node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
 
     // Setup the pimpl
     let verts = if LIGHTMODE {
@@ -180,13 +183,13 @@ pub async fn make(app: &App, window: SceneNodePtr) {
     // Debugging tool
     let node = create_vector_art("debugtool");
     let prop = node.get_property("rect").unwrap();
-    prop.set_f32(Role::App, 0, 0.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 0, 0.).unwrap();
     let code = cc.compile("h/2").unwrap();
-    prop.set_expr(Role::App, 1, code).unwrap();
-    prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
+    prop.clone().set_expr(atom, Role::App, 1, code).unwrap();
+    prop.clone().set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
     let code = cc.compile("h/2 - 200").unwrap();
-    prop.set_expr(Role::App, 3, code).unwrap();
-    node.set_property_u32(Role::App, "z_index", 2).unwrap();
+    prop.clone().set_expr(atom, Role::App, 3, code).unwrap();
+    node.set_property_u32(atom, Role::App, "z_index", 2).unwrap();
 
     let mut shape = VectorShape::new();
     shape.add_filled_box(
@@ -210,11 +213,11 @@ pub async fn make(app: &App, window: SceneNodePtr) {
     // Another debug tool for the chatedit
     let node = create_vector_art("debugtool-chatedit");
     let prop = node.get_property("rect").unwrap();
-    prop.set_f32(Role::App, 0, 0.).unwrap();
-    prop.set_f32(Role::App, 1, 300. - 5.).unwrap();
-    prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
-    prop.set_f32(Role::App, 3, 400. + 10.).unwrap();
-    node.set_property_u32(Role::App, "z_index", 2).unwrap();
+    prop.clone().set_f32(atom, Role::App, 0, 0.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 1, 300. - 5.).unwrap();
+    prop.clone().set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
+    prop.clone().set_f32(atom, Role::App, 3, 400. + 10.).unwrap();
+    node.set_property_u32(atom, Role::App, "z_index", 2).unwrap();
 
     let mut shape = VectorShape::new();
     shape.add_filled_box(
@@ -238,38 +241,39 @@ pub async fn make(app: &App, window: SceneNodePtr) {
     // Create KING GNU!
     let node = create_image("king");
     let prop = node.get_property("rect").unwrap();
-    prop.set_f32(Role::App, 0, 80.).unwrap();
-    prop.set_f32(Role::App, 1, 10.).unwrap();
-    prop.set_f32(Role::App, 2, 60.).unwrap();
-    prop.set_f32(Role::App, 3, 60.).unwrap();
-    node.set_property_str(Role::App, "path", KING_PATH).unwrap();
-    node.set_property_u32(Role::App, "z_index", 1).unwrap();
+    prop.clone().set_f32(atom, Role::App, 0, 80.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 1, 10.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 2, 60.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 3, 60.).unwrap();
+    node.set_property_str(atom, Role::App, "path", KING_PATH).unwrap();
+    node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
     let node = node.setup(|me| Image::new(me, app.render_api.clone(), app.ex.clone())).await;
     layer_node.clone().link(node);
 
     // Create some text
     let node = create_text("label");
     let prop = node.get_property("rect").unwrap();
-    prop.set_f32(Role::App, 0, 100.).unwrap();
-    prop.set_f32(Role::App, 1, 100.).unwrap();
-    prop.set_f32(Role::App, 2, 2000.).unwrap();
-    prop.set_f32(Role::App, 3, 200.).unwrap();
-    node.set_property_f32(Role::App, "baseline", 40.).unwrap();
-    node.set_property_f32(Role::App, "font_size", 60.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 0, 100.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 1, 100.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 2, 2000.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 3, 200.).unwrap();
+    node.set_property_f32(atom, Role::App, "baseline", 40.).unwrap();
+    node.set_property_f32(atom, Role::App, "font_size", 60.).unwrap();
     node.set_property_str(
+        atom,
         Role::App,
         "text",
         "\u{f0007}",
         //"hel \u{01f3f3}\u{fe0f}\u{200d}\u{26a7}\u{fe0f} 123 '\u{01f44d}\u{01f3fe}' br",
     )
     .unwrap();
-    //node.set_property_str(Role::App, "text", "anon1").unwrap();
+    //node.set_property_str(atom, Role::App, "text", "anon1").unwrap();
     let prop = node.get_property("text_color").unwrap();
-    prop.set_f32(Role::App, 0, 0.).unwrap();
-    prop.set_f32(Role::App, 1, 1.).unwrap();
-    prop.set_f32(Role::App, 2, 0.).unwrap();
-    prop.set_f32(Role::App, 3, 1.).unwrap();
-    node.set_property_u32(Role::App, "z_index", 1).unwrap();
+    prop.clone().set_f32(atom, Role::App, 0, 0.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 1, 1.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 2, 0.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 3, 1.).unwrap();
+    node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
 
     let node = node
         .setup(|me| {
@@ -286,65 +290,66 @@ pub async fn make(app: &App, window: SceneNodePtr) {
 
     // Text edit
     let node = create_editbox("editz");
-    node.set_property_bool(Role::App, "is_active", true).unwrap();
-    node.set_property_bool(Role::App, "is_focused", true).unwrap();
+    node.set_property_bool(atom, Role::App, "is_active", true).unwrap();
+    node.set_property_bool(atom, Role::App, "is_focused", true).unwrap();
     let prop = node.get_property("rect").unwrap();
-    prop.set_f32(Role::App, 0, 150.).unwrap();
-    prop.set_f32(Role::App, 1, 150.).unwrap();
-    prop.set_f32(Role::App, 2, 380.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 0, 150.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 1, 150.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 2, 380.).unwrap();
     //let code = vec![Op::Sub((
     //    Box::new(Op::LoadVar("h".to_string())),
     //    Box::new(Op::ConstFloat32(60.)),
     //))];
-    //prop.set_expr(Role::App, 1, code).unwrap();
+    //prop.clone().set_expr(atom, Role::App, 1, code).unwrap();
     //let code = vec![Op::Sub((
     //    Box::new(Op::LoadVar("w".to_string())),
     //    Box::new(Op::ConstFloat32(120.)),
     //))];
-    //prop.set_expr(Role::App, 2, code).unwrap();
-    prop.set_f32(Role::App, 3, 60.).unwrap();
-    node.set_property_f32(Role::App, "baseline", 40.).unwrap();
-    node.set_property_f32(Role::App, "font_size", 20.).unwrap();
-    node.set_property_f32(Role::App, "font_size", 40.).unwrap();
-    node.set_property_str(Role::App, "text", "\u{01f3f3}\u{fe0f}\u{200d}\u{26a7}\u{fe0f}").unwrap();
+    //prop.clone().set_expr(atom, Role::App, 2, code).unwrap();
+    prop.clone().set_f32(atom, Role::App, 3, 60.).unwrap();
+    node.set_property_f32(atom, Role::App, "baseline", 40.).unwrap();
+    node.set_property_f32(atom, Role::App, "font_size", 20.).unwrap();
+    node.set_property_f32(atom, Role::App, "font_size", 40.).unwrap();
+    node.set_property_str(atom, Role::App, "text", "\u{01f3f3}\u{fe0f}\u{200d}\u{26a7}\u{fe0f}")
+        .unwrap();
     let prop = node.get_property("text_color").unwrap();
     if LIGHTMODE {
-        prop.set_f32(Role::App, 0, 0.).unwrap();
-        prop.set_f32(Role::App, 1, 0.).unwrap();
-        prop.set_f32(Role::App, 2, 0.).unwrap();
-        prop.set_f32(Role::App, 3, 1.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 0, 0.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 1, 0.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 2, 0.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 3, 1.).unwrap();
     } else {
-        prop.set_f32(Role::App, 0, 1.).unwrap();
-        prop.set_f32(Role::App, 1, 1.).unwrap();
-        prop.set_f32(Role::App, 2, 1.).unwrap();
-        prop.set_f32(Role::App, 3, 1.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 0, 1.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 1, 1.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 2, 1.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 3, 1.).unwrap();
     }
     let prop = node.get_property("cursor_color").unwrap();
-    prop.set_f32(Role::App, 0, 1.).unwrap();
-    prop.set_f32(Role::App, 1, 0.5).unwrap();
-    prop.set_f32(Role::App, 2, 0.5).unwrap();
-    prop.set_f32(Role::App, 3, 1.).unwrap();
-    node.set_property_f32(Role::App, "cursor_ascent", 40.).unwrap();
-    node.set_property_f32(Role::App, "cursor_descent", 40.).unwrap();
-    node.set_property_f32(Role::App, "select_ascent", 60.).unwrap();
-    node.set_property_f32(Role::App, "select_descent", 60.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 0, 1.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 1, 0.5).unwrap();
+    prop.clone().set_f32(atom, Role::App, 2, 0.5).unwrap();
+    prop.clone().set_f32(atom, Role::App, 3, 1.).unwrap();
+    node.set_property_f32(atom, Role::App, "cursor_ascent", 40.).unwrap();
+    node.set_property_f32(atom, Role::App, "cursor_descent", 40.).unwrap();
+    node.set_property_f32(atom, Role::App, "select_ascent", 60.).unwrap();
+    node.set_property_f32(atom, Role::App, "select_descent", 60.).unwrap();
     let prop = node.get_property("hi_bg_color").unwrap();
     if LIGHTMODE {
-        prop.set_f32(Role::App, 0, 0.5).unwrap();
-        prop.set_f32(Role::App, 1, 0.5).unwrap();
-        prop.set_f32(Role::App, 2, 0.5).unwrap();
-        prop.set_f32(Role::App, 3, 1.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 0, 0.5).unwrap();
+        prop.clone().set_f32(atom, Role::App, 1, 0.5).unwrap();
+        prop.clone().set_f32(atom, Role::App, 2, 0.5).unwrap();
+        prop.clone().set_f32(atom, Role::App, 3, 1.).unwrap();
     } else {
-        prop.set_f32(Role::App, 0, 1.).unwrap();
-        prop.set_f32(Role::App, 1, 1.).unwrap();
-        prop.set_f32(Role::App, 2, 1.).unwrap();
-        prop.set_f32(Role::App, 3, 0.5).unwrap();
+        prop.clone().set_f32(atom, Role::App, 0, 1.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 1, 1.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 2, 1.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 3, 0.5).unwrap();
     }
     let prop = node.get_property("selected").unwrap();
-    prop.set_null(Role::App, 0).unwrap();
-    prop.set_null(Role::App, 1).unwrap();
-    node.set_property_u32(Role::App, "z_index", 1).unwrap();
-    //node.set_property_bool(Role::App, "debug", true).unwrap();
+    prop.clone().set_null(atom, Role::App, 0).unwrap();
+    prop.clone().set_null(atom, Role::App, 1).unwrap();
+    node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
+    //node.set_property_bool(atom, Role::App, "debug", true).unwrap();
 
     //let editbox_text = PropertyStr::wrap(node, Role::App, "text", 0).unwrap();
     //let editbox_focus = PropertyBool::wrap(node, Role::App, "is_focused", 0).unwrap();
@@ -382,36 +387,36 @@ pub async fn make(app: &App, window: SceneNodePtr) {
     // ChatView
     let node = create_chatview("chatty");
     let prop = node.get_property("rect").unwrap();
-    prop.set_f32(Role::App, 0, 10.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 0, 10.).unwrap();
     let code = cc.compile("h/2").unwrap();
-    prop.set_expr(Role::App, 1, code).unwrap();
-    prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
+    prop.clone().set_expr(atom, Role::App, 1, code).unwrap();
+    prop.clone().set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
     let code = cc.compile("h/2 - 200").unwrap();
-    prop.set_expr(Role::App, 3, code).unwrap();
-    node.set_property_f32(Role::App, "font_size", 20.).unwrap();
-    node.set_property_f32(Role::App, "timestamp_font_size", 10.).unwrap();
-    node.set_property_f32(Role::App, "timestamp_width", 80.).unwrap();
-    node.set_property_f32(Role::App, "line_height", 30.).unwrap();
-    node.set_property_f32(Role::App, "baseline", 20.).unwrap();
-    node.set_property_u32(Role::App, "z_index", 1).unwrap();
-    //node.set_property_bool(Role::App, "debug", true).unwrap();
+    prop.clone().set_expr(atom, Role::App, 3, code).unwrap();
+    node.set_property_f32(atom, Role::App, "font_size", 20.).unwrap();
+    node.set_property_f32(atom, Role::App, "timestamp_font_size", 10.).unwrap();
+    node.set_property_f32(atom, Role::App, "timestamp_width", 80.).unwrap();
+    node.set_property_f32(atom, Role::App, "line_height", 30.).unwrap();
+    node.set_property_f32(atom, Role::App, "baseline", 20.).unwrap();
+    node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
+    //node.set_property_bool(atom, Role::App, "debug", true).unwrap();
 
     let prop = node.get_property("timestamp_color").unwrap();
-    prop.set_f32(Role::App, 0, 0.5).unwrap();
-    prop.set_f32(Role::App, 1, 0.5).unwrap();
-    prop.set_f32(Role::App, 2, 0.5).unwrap();
-    prop.set_f32(Role::App, 3, 0.5).unwrap();
+    prop.clone().set_f32(atom, Role::App, 0, 0.5).unwrap();
+    prop.clone().set_f32(atom, Role::App, 1, 0.5).unwrap();
+    prop.clone().set_f32(atom, Role::App, 2, 0.5).unwrap();
+    prop.clone().set_f32(atom, Role::App, 3, 0.5).unwrap();
     let prop = node.get_property("text_color").unwrap();
     if LIGHTMODE {
-        prop.set_f32(Role::App, 0, 0.).unwrap();
-        prop.set_f32(Role::App, 1, 0.).unwrap();
-        prop.set_f32(Role::App, 2, 0.).unwrap();
-        prop.set_f32(Role::App, 3, 1.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 0, 0.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 1, 0.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 2, 0.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 3, 1.).unwrap();
     } else {
-        prop.set_f32(Role::App, 0, 1.).unwrap();
-        prop.set_f32(Role::App, 1, 1.).unwrap();
-        prop.set_f32(Role::App, 2, 1.).unwrap();
-        prop.set_f32(Role::App, 3, 1.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 0, 1.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 1, 1.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 2, 1.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 3, 1.).unwrap();
     }
 
     let prop = node.get_property("nick_colors").unwrap();
@@ -434,15 +439,15 @@ pub async fn make(app: &App, window: SceneNodePtr) {
 
     let prop = node.get_property("hi_bg_color").unwrap();
     if LIGHTMODE {
-        prop.set_f32(Role::App, 0, 0.5).unwrap();
-        prop.set_f32(Role::App, 1, 0.5).unwrap();
-        prop.set_f32(Role::App, 2, 0.5).unwrap();
-        prop.set_f32(Role::App, 3, 1.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 0, 0.5).unwrap();
+        prop.clone().set_f32(atom, Role::App, 1, 0.5).unwrap();
+        prop.clone().set_f32(atom, Role::App, 2, 0.5).unwrap();
+        prop.clone().set_f32(atom, Role::App, 3, 1.).unwrap();
     } else {
-        prop.set_f32(Role::App, 0, 0.5).unwrap();
-        prop.set_f32(Role::App, 1, 0.5).unwrap();
-        prop.set_f32(Role::App, 2, 0.5).unwrap();
-        prop.set_f32(Role::App, 3, 1.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 0, 0.5).unwrap();
+        prop.clone().set_f32(atom, Role::App, 1, 0.5).unwrap();
+        prop.clone().set_f32(atom, Role::App, 2, 0.5).unwrap();
+        prop.clone().set_f32(atom, Role::App, 3, 1.).unwrap();
     }
 
     let db = sled::open(CHATDB_PATH).expect("cannot open sleddb");
@@ -467,48 +472,48 @@ pub async fn make(app: &App, window: SceneNodePtr) {
 
     // Text edit
     let node = create_chatedit("editz");
-    node.set_property_bool(Role::App, "is_active", true).unwrap();
-    node.set_property_bool(Role::App, "is_focused", true).unwrap();
+    node.set_property_bool(atom, Role::App, "is_active", true).unwrap();
+    node.set_property_bool(atom, Role::App, "is_focused", true).unwrap();
 
-    node.set_property_f32(Role::App, "max_height", 400.).unwrap();
+    node.set_property_f32(atom, Role::App, "max_height", 400.).unwrap();
 
     let prop = node.get_property("rect").unwrap();
-    prop.set_f32(Role::App, 0, 0.).unwrap();
-    prop.set_f32(Role::App, 1, 300.).unwrap();
-    prop.set_expr(Role::App, 2, expr::load_var("parent_w")).unwrap();
-    prop.set_f32(Role::App, 3, 50.).unwrap();
-
-    node.set_property_f32(Role::App, "baseline", 34.).unwrap();
-    node.set_property_f32(Role::App, "linespacing", 50.).unwrap();
-    node.set_property_f32(Role::App, "descent", 10.).unwrap();
-    node.set_property_f32(Role::App, "font_size", 40.).unwrap();
-    //node.set_property_str(Role::App, "text", "hello king!😁🍆jelly 🍆1234").unwrap();
+    prop.clone().set_f32(atom, Role::App, 0, 0.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 1, 300.).unwrap();
+    prop.clone().set_expr(atom, Role::App, 2, expr::load_var("parent_w")).unwrap();
+    prop.clone().set_f32(atom, Role::App, 3, 50.).unwrap();
+
+    node.set_property_f32(atom, Role::App, "baseline", 34.).unwrap();
+    node.set_property_f32(atom, Role::App, "linespacing", 50.).unwrap();
+    node.set_property_f32(atom, Role::App, "descent", 10.).unwrap();
+    node.set_property_f32(atom, Role::App, "font_size", 40.).unwrap();
+    //node.set_property_str(atom, Role::App, "text", "hello king!😁🍆jelly 🍆1234").unwrap();
     let prop = node.get_property("text_color").unwrap();
-    prop.set_f32(Role::App, 0, 1.).unwrap();
-    prop.set_f32(Role::App, 1, 1.).unwrap();
-    prop.set_f32(Role::App, 2, 1.).unwrap();
-    prop.set_f32(Role::App, 3, 1.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 0, 1.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 1, 1.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 2, 1.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 3, 1.).unwrap();
     let prop = node.get_property("text_hi_color").unwrap();
-    prop.set_f32(Role::App, 0, 0.44).unwrap();
-    prop.set_f32(Role::App, 1, 0.96).unwrap();
-    prop.set_f32(Role::App, 2, 1.).unwrap();
-    prop.set_f32(Role::App, 3, 1.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 0, 0.44).unwrap();
+    prop.clone().set_f32(atom, Role::App, 1, 0.96).unwrap();
+    prop.clone().set_f32(atom, Role::App, 2, 1.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 3, 1.).unwrap();
     let prop = node.get_property("cursor_color").unwrap();
-    prop.set_f32(Role::App, 0, 0.816).unwrap();
-    prop.set_f32(Role::App, 1, 0.627).unwrap();
-    prop.set_f32(Role::App, 2, 1.).unwrap();
-    prop.set_f32(Role::App, 3, 1.).unwrap();
-    node.set_property_f32(Role::App, "cursor_ascent", 50.).unwrap();
-    node.set_property_f32(Role::App, "cursor_descent", 20.).unwrap();
-    node.set_property_f32(Role::App, "select_ascent", 40.).unwrap();
-    node.set_property_f32(Role::App, "select_descent", 8.).unwrap();
-    node.set_property_f32(Role::App, "handle_descent", 25.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 0, 0.816).unwrap();
+    prop.clone().set_f32(atom, Role::App, 1, 0.627).unwrap();
+    prop.clone().set_f32(atom, Role::App, 2, 1.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 3, 1.).unwrap();
+    node.set_property_f32(atom, Role::App, "cursor_ascent", 50.).unwrap();
+    node.set_property_f32(atom, Role::App, "cursor_descent", 20.).unwrap();
+    node.set_property_f32(atom, Role::App, "select_ascent", 40.).unwrap();
+    node.set_property_f32(atom, Role::App, "select_descent", 8.).unwrap();
+    node.set_property_f32(atom, Role::App, "handle_descent", 25.).unwrap();
     let prop = node.get_property("hi_bg_color").unwrap();
-    prop.set_f32(Role::App, 0, 0.5).unwrap();
-    prop.set_f32(Role::App, 1, 0.5).unwrap();
-    prop.set_f32(Role::App, 2, 0.5).unwrap();
-    prop.set_f32(Role::App, 3, 1.).unwrap();
-    node.set_property_u32(Role::App, "z_index", 3).unwrap();
+    prop.clone().set_f32(atom, Role::App, 0, 0.5).unwrap();
+    prop.clone().set_f32(atom, Role::App, 1, 0.5).unwrap();
+    prop.clone().set_f32(atom, Role::App, 2, 0.5).unwrap();
+    prop.clone().set_f32(atom, Role::App, 3, 1.).unwrap();
+    node.set_property_u32(atom, Role::App, "z_index", 3).unwrap();
     let node = node
         .setup(|me| {
             ChatEdit::new(

+ 11 - 9
bin/darkwallet/src/net.rs

@@ -27,7 +27,7 @@ use zeromq::{Socket, SocketRecv, SocketSend};
 use crate::{
     error::{Error, Result},
     expr::SExprCode,
-    prop::{Property, PropertySubType, PropertyType, PropertyValue, Role},
+    prop::{Property, PropertyAtomicGuard, PropertySubType, PropertyType, PropertyValue, Role},
     scene::{SceneNodeId, SceneNodePtr, ScenePath},
     ExecutorPtr,
 };
@@ -241,38 +241,40 @@ impl ZeroMQAdapter {
                     self.sg_root.clone().lookup_node(node_path).ok_or(Error::NodeNotFound)?;
                 let prop = node.get_property(&prop_name).ok_or(Error::PropertyNotFound)?;
 
+                let atom = &mut PropertyAtomicGuard::new();
+
                 match prop_type {
                     PropertyType::Null => {
-                        prop.set_null(Role::User, prop_i)?;
+                        prop.set_null(atom, Role::User, prop_i)?;
                     }
                     PropertyType::Bool => {
                         let val = bool::decode(&mut cur).unwrap();
-                        prop.set_bool(Role::User, prop_i, val)?;
+                        prop.set_bool(atom, Role::User, prop_i, val)?;
                     }
                     PropertyType::Uint32 => {
                         let val = u32::decode(&mut cur).unwrap();
-                        prop.set_u32(Role::User, prop_i, val)?;
+                        prop.set_u32(atom, Role::User, prop_i, val)?;
                     }
                     PropertyType::Float32 => {
                         let val = f32::decode(&mut cur).unwrap();
-                        prop.set_f32(Role::User, prop_i, val)?;
+                        prop.set_f32(atom, Role::User, prop_i, val)?;
                     }
                     PropertyType::Str => {
                         let val = String::decode(&mut cur).unwrap();
-                        prop.set_str(Role::User, prop_i, val)?;
+                        prop.set_str(atom, Role::User, prop_i, val)?;
                     }
                     PropertyType::Enum => {
                         let val = String::decode(&mut cur).unwrap();
-                        prop.set_enum(Role::User, prop_i, val)?;
+                        prop.set_enum(atom, Role::User, prop_i, val)?;
                     }
                     PropertyType::SceneNodeId => {
                         let val = SceneNodeId::decode(&mut cur).unwrap();
-                        prop.set_node_id(Role::User, prop_i, val)?;
+                        prop.set_node_id(atom, Role::User, prop_i, val)?;
                     }
                     PropertyType::SExpr => {
                         let val = SExprCode::decode(&mut cur).unwrap();
                         debug!(target: "req", "  received code {:?}", val);
-                        prop.set_expr(Role::User, prop_i, val)?;
+                        prop.set_expr(atom, Role::User, prop_i, val)?;
                     }
                 }
             }

+ 2 - 2
bin/darkwallet/src/plugin/darkirc.rs

@@ -40,7 +40,7 @@ use std::{
 
 use crate::{
     error::{Error, Result},
-    prop::{PropertyStr, Role},
+    prop::{PropertyAtomicGuard, PropertyStr, Role},
     scene::{MethodCallSub, Pimpl, SceneNodePtr, SceneNodeWeak},
     ui::{
         chatview::{MessageId, Timestamp},
@@ -222,7 +222,7 @@ impl DarkIrc {
         };
 
         if let Ok(prev_nick) = std::fs::read_to_string(nick_filename()) {
-            nick.set(prev_nick);
+            nick.set(&mut PropertyAtomicGuard::new(), prev_nick);
         }
 
         let self_ = Arc::new(Self {

+ 63 - 0
bin/darkwallet/src/prop/guard.rs

@@ -0,0 +1,63 @@
+/* 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 super::{ModifyAction, ModifyPublisher, PropertyPtr, Role};
+
+macro_rules! t { ($($arg:tt)*) => { trace!(target: "prop", $($arg)*); } }
+
+/// This schedules all property updates to happen at the end of the scope.
+/// We can therefore have fine-grained control about when property updates are
+/// propagated to the rest of the scenegraph.
+///
+/// This way we avoid triggering draw updates mid draw, and changes are atomic.
+/// For example resizing the content view, will trigger the editbox background to
+/// redraw while a current window wide draw is in progress. Since the window draw
+/// triggered the change when we submit the draw update, it will be discarded by
+/// the editbox bg triggered update. However this update won't have the current
+/// rect and will be stale.
+///
+/// 1. Content draw starts
+/// 2. Dependent property triggers and submits editbox bg redraw.
+/// 3. Content draw continues and now draws editbox bg with updated rect.
+/// 4. Finished content draw's editbox bg update is discarded in favour of #2.
+///    However #2 used the pre-updated rect and is now stale.
+///
+/// We solve the above issue by batching all updates until after the draw call is finished.
+/// This also has the unintended side-effect of making draws much faster since they aren't
+/// interrupted halfway through by extra compute.
+pub struct PropertyAtomicGuard {
+    updates: Vec<(PropertyPtr, Role, ModifyAction)>,
+}
+
+impl PropertyAtomicGuard {
+    pub fn new() -> Self {
+        Self { updates: vec![] }
+    }
+
+    pub(super) fn add(&mut self, prop: PropertyPtr, role: Role, action: ModifyAction) {
+        self.updates.push((prop, role, action));
+    }
+}
+
+impl Drop for PropertyAtomicGuard {
+    fn drop(&mut self) {
+        for (prop, role, action) in std::mem::take(&mut self.updates) {
+            prop.on_modify.notify((role, action));
+        }
+    }
+}

+ 103 - 32
bin/darkwallet/src/prop/mod.rs

@@ -30,6 +30,8 @@ use crate::{
     scene::{SceneNodeId, SceneNodeWeak},
 };
 
+mod guard;
+pub use guard::PropertyAtomicGuard;
 mod wrap;
 pub use wrap::{
     PropertyBool, PropertyColor, PropertyDimension, PropertyFloat32, PropertyPoint, PropertyRect,
@@ -201,6 +203,8 @@ pub enum ModifyAction {
     Push(usize),
 }
 
+type ModifyPublisher = PublisherPtr<(Role, ModifyAction)>;
+
 pub type PropertyPtr = Arc<Property>;
 pub type PropertyWeak = Weak<Property>;
 
@@ -236,7 +240,7 @@ pub struct Property {
     // PropertyType must be Enum
     pub enum_items: Option<Vec<String>>,
 
-    on_modify: PublisherPtr<(Role, ModifyAction)>,
+    on_modify: ModifyPublisher,
     depends: SyncMutex<Vec<PropertyDepend>>,
 }
 
@@ -360,7 +364,7 @@ impl Property {
     // Set
 
     /// This will clear all values, resetting them to the default
-    pub fn clear_values(&self, role: Role) {
+    pub fn clear_values(self: Arc<Self>, role: Role) {
         let vals = &mut self.vals.lock().unwrap();
         vals.clear();
         vals.resize(self.array_len, PropertyValue::Unset);
@@ -377,21 +381,32 @@ impl Property {
             return Err(Error::PropertyWrongIndex)
         }
         vals[i] = val;
-        self.on_modify.notify((role, ModifyAction::Set(i)));
         Ok(())
     }
 
-    pub fn unset(&self, role: Role, i: usize) -> Result<()> {
-        let vals = &mut self.vals.lock().unwrap();
-        if i >= vals.len() {
-            return Err(Error::PropertyWrongIndex)
+    pub fn unset(
+        self: Arc<Self>,
+        atom: &mut PropertyAtomicGuard,
+        role: Role,
+        i: usize,
+    ) -> Result<()> {
+        {
+            let vals = &mut self.vals.lock().unwrap();
+            if i >= vals.len() {
+                return Err(Error::PropertyWrongIndex)
+            }
+            vals[i] = PropertyValue::Unset;
         }
-        vals[i] = PropertyValue::Unset;
-        self.on_modify.notify((role, ModifyAction::Set(i)));
+        atom.add(self, role, ModifyAction::Set(i));
         Ok(())
     }
 
-    pub fn set_null(&self, role: Role, i: usize) -> Result<()> {
+    pub fn set_null(
+        self: Arc<Self>,
+        atom: &mut PropertyAtomicGuard,
+        role: Role,
+        i: usize,
+    ) -> Result<()> {
         if !self.is_null_allowed {
             return Err(Error::PropertyNullNotAllowed)
         }
@@ -403,14 +418,28 @@ impl Property {
         vals[i] = PropertyValue::Null;
         drop(vals);
 
-        self.on_modify.notify((role, ModifyAction::Set(i)));
+        atom.add(self, role, ModifyAction::Set(i));
         Ok(())
     }
 
-    pub fn set_bool(&self, role: Role, i: usize, val: bool) -> Result<()> {
-        self.set_raw_value(role, i, PropertyValue::Bool(val))
+    pub fn set_bool(
+        self: Arc<Self>,
+        atom: &mut PropertyAtomicGuard,
+        role: Role,
+        i: usize,
+        val: bool,
+    ) -> Result<()> {
+        self.set_raw_value(role, i, PropertyValue::Bool(val))?;
+        atom.add(self, role, ModifyAction::Set(i));
+        Ok(())
     }
-    pub fn set_u32(&self, role: Role, i: usize, val: u32) -> Result<()> {
+    pub fn set_u32(
+        self: Arc<Self>,
+        atom: &mut PropertyAtomicGuard,
+        role: Role,
+        i: usize,
+        val: u32,
+    ) -> Result<()> {
         if self.min_val.is_some() {
             let min = self.min_val.as_ref().unwrap().as_u32()?;
             if val < min {
@@ -423,9 +452,17 @@ impl Property {
                 return Err(Error::PropertyOutOfRange);
             }
         }
-        self.set_raw_value(role, i, PropertyValue::Uint32(val))
+        self.set_raw_value(role, i, PropertyValue::Uint32(val))?;
+        atom.add(self, role, ModifyAction::Set(i));
+        Ok(())
     }
-    pub fn set_f32(&self, role: Role, i: usize, val: f32) -> Result<()> {
+    pub fn set_f32(
+        self: Arc<Self>,
+        atom: &mut PropertyAtomicGuard,
+        role: Role,
+        i: usize,
+        val: f32,
+    ) -> Result<()> {
         if self.min_val.is_some() {
             let min = self.min_val.as_ref().unwrap().as_f32()?;
             if val < min {
@@ -438,12 +475,28 @@ impl Property {
                 return Err(Error::PropertyOutOfRange);
             }
         }
-        self.set_raw_value(role, i, PropertyValue::Float32(val))
+        self.set_raw_value(role, i, PropertyValue::Float32(val))?;
+        atom.add(self, role, ModifyAction::Set(i));
+        Ok(())
     }
-    pub fn set_str<S: Into<String>>(&self, role: Role, i: usize, val: S) -> Result<()> {
-        self.set_raw_value(role, i, PropertyValue::Str(val.into()))
+    pub fn set_str<S: Into<String>>(
+        self: Arc<Self>,
+        atom: &mut PropertyAtomicGuard,
+        role: Role,
+        i: usize,
+        val: S,
+    ) -> Result<()> {
+        self.set_raw_value(role, i, PropertyValue::Str(val.into()))?;
+        atom.add(self, role, ModifyAction::Set(i));
+        Ok(())
     }
-    pub fn set_enum<S: Into<String>>(&self, role: Role, i: usize, val: S) -> Result<()> {
+    pub fn set_enum<S: Into<String>>(
+        self: Arc<Self>,
+        atom: &mut PropertyAtomicGuard,
+        role: Role,
+        i: usize,
+        val: S,
+    ) -> Result<()> {
         if self.typ != PropertyType::Enum {
             return Err(Error::PropertyWrongType)
         }
@@ -451,21 +504,39 @@ impl Property {
         if !self.enum_items.as_ref().unwrap().contains(&val) {
             return Err(Error::PropertyWrongEnumItem)
         }
-        self.set_raw_value(role, i, PropertyValue::Enum(val.into()))
+        self.set_raw_value(role, i, PropertyValue::Enum(val.into()))?;
+        atom.add(self, role, ModifyAction::Set(i));
+        Ok(())
     }
-    pub fn set_node_id(&self, role: Role, i: usize, val: SceneNodeId) -> Result<()> {
-        self.set_raw_value(role, i, PropertyValue::SceneNodeId(val))
+    pub fn set_node_id(
+        self: Arc<Self>,
+        atom: &mut PropertyAtomicGuard,
+        role: Role,
+        i: usize,
+        val: SceneNodeId,
+    ) -> Result<()> {
+        self.set_raw_value(role, i, PropertyValue::SceneNodeId(val))?;
+        atom.add(self, role, ModifyAction::Set(i));
+        Ok(())
     }
-    pub fn set_expr(&self, role: Role, i: usize, val: SExprCode) -> Result<()> {
-        if !self.is_expr_allowed {
-            return Err(Error::PropertySExprNotAllowed)
-        }
-        let vals = &mut self.vals.lock().unwrap();
-        if i >= vals.len() {
-            return Err(Error::PropertyWrongIndex)
+    pub fn set_expr(
+        self: Arc<Self>,
+        atom: &mut PropertyAtomicGuard,
+        role: Role,
+        i: usize,
+        val: SExprCode,
+    ) -> Result<()> {
+        {
+            if !self.is_expr_allowed {
+                return Err(Error::PropertySExprNotAllowed)
+            }
+            let vals = &mut self.vals.lock().unwrap();
+            if i >= vals.len() {
+                return Err(Error::PropertyWrongIndex)
+            }
+            vals[i] = PropertyValue::SExpr(Arc::new(val));
         }
-        vals[i] = PropertyValue::SExpr(Arc::new(val));
-        self.on_modify.notify((role, ModifyAction::Set(i)));
+        atom.add(self, role, ModifyAction::Set(i));
         Ok(())
     }
 

+ 32 - 25
bin/darkwallet/src/prop/wrap.rs

@@ -25,7 +25,7 @@ use crate::{
     scene::SceneNode as SceneNode3,
 };
 
-use super::{PropertyPtr, Role};
+use super::{PropertyAtomicGuard, PropertyPtr, Role};
 
 #[derive(Clone)]
 pub struct PropertyBool {
@@ -48,10 +48,11 @@ impl PropertyBool {
         self.prop.get_bool(self.idx).unwrap()
     }
 
-    pub fn set(&self, val: bool) {
-        self.prop.set_bool(self.role, self.idx, val).unwrap()
+    pub fn set(&self, atom: &mut PropertyAtomicGuard, val: bool) {
+        self.prop().set_bool(atom, self.role, self.idx, val).unwrap()
     }
 
+    #[inline]
     pub fn prop(&self) -> PropertyPtr {
         self.prop.clone()
     }
@@ -85,10 +86,11 @@ impl PropertyUint32 {
         self.prop.get_u32(self.idx).unwrap()
     }
 
-    pub fn set(&self, val: u32) {
-        self.prop.set_u32(self.role, self.idx, val).unwrap()
+    pub fn set(&self, atom: &mut PropertyAtomicGuard, val: u32) {
+        self.prop().set_u32(atom, self.role, self.idx, val).unwrap()
     }
 
+    #[inline]
     pub fn prop(&self) -> PropertyPtr {
         self.prop.clone()
     }
@@ -115,8 +117,8 @@ impl PropertyFloat32 {
         self.prop.get_f32(self.idx).unwrap()
     }
 
-    pub fn set(&self, val: f32) {
-        self.prop.set_f32(self.role, self.idx, val).unwrap()
+    pub fn set(&self, atom: &mut PropertyAtomicGuard, val: f32) {
+        self.prop().set_f32(atom, self.role, self.idx, val).unwrap()
     }
 
     pub fn prop(&self) -> PropertyPtr {
@@ -145,10 +147,11 @@ impl PropertyStr {
         self.prop.get_str(self.idx).unwrap()
     }
 
-    pub fn set<S: Into<String>>(&self, val: S) {
-        self.prop.set_str(self.role, self.idx, val.into()).unwrap()
+    pub fn set<S: Into<String>>(&self, atom: &mut PropertyAtomicGuard, val: S) {
+        self.prop().set_str(atom, self.role, self.idx, val.into()).unwrap()
     }
 
+    #[inline]
     pub fn prop(&self) -> PropertyPtr {
         self.prop.clone()
     }
@@ -183,13 +186,14 @@ impl PropertyColor {
         ]
     }
 
-    pub fn set(&self, val: [f32; 4]) {
-        self.prop.set_f32(self.role, 0, val[0]).unwrap();
-        self.prop.set_f32(self.role, 1, val[1]).unwrap();
-        self.prop.set_f32(self.role, 2, val[2]).unwrap();
-        self.prop.set_f32(self.role, 3, val[3]).unwrap();
+    pub fn set(&self, atom: &mut PropertyAtomicGuard, val: [f32; 4]) {
+        self.prop().set_f32(atom, self.role, 0, val[0]).unwrap();
+        self.prop().set_f32(atom, self.role, 1, val[1]).unwrap();
+        self.prop().set_f32(atom, self.role, 2, val[2]).unwrap();
+        self.prop().set_f32(atom, self.role, 3, val[3]).unwrap();
     }
 
+    #[inline]
     pub fn prop(&self) -> PropertyPtr {
         self.prop.clone()
     }
@@ -219,11 +223,12 @@ impl PropertyDimension {
         [self.prop.get_f32(0).unwrap(), self.prop.get_f32(1).unwrap()].into()
     }
 
-    pub fn set(&self, dim: Dimension) {
-        self.prop.set_f32(self.role, 0, dim.w).unwrap();
-        self.prop.set_f32(self.role, 1, dim.h).unwrap();
+    pub fn set(&self, atom: &mut PropertyAtomicGuard, dim: Dimension) {
+        self.prop().set_f32(atom, self.role, 0, dim.w).unwrap();
+        self.prop().set_f32(atom, self.role, 1, dim.h).unwrap();
     }
 
+    #[inline]
     pub fn prop(&self) -> PropertyPtr {
         self.prop.clone()
     }
@@ -253,11 +258,12 @@ impl PropertyPoint {
         [self.prop.get_f32(0).unwrap(), self.prop.get_f32(1).unwrap()].into()
     }
 
-    pub fn set(&self, pos: Point) {
-        self.prop.set_f32(self.role, 0, pos.x).unwrap();
-        self.prop.set_f32(self.role, 1, pos.y).unwrap();
+    pub fn set(&self, atom: &mut PropertyAtomicGuard, pos: Point) {
+        self.prop().set_f32(atom, self.role, 0, pos.x).unwrap();
+        self.prop().set_f32(atom, self.role, 1, pos.y).unwrap();
     }
 
+    #[inline]
     pub fn prop(&self) -> PropertyPtr {
         self.prop.clone()
     }
@@ -342,13 +348,14 @@ impl PropertyRect {
         ]))
     }
 
-    pub fn set(&self, rect: &Rectangle) {
-        self.prop.set_f32(self.role, 0, rect.x).unwrap();
-        self.prop.set_f32(self.role, 1, rect.y).unwrap();
-        self.prop.set_f32(self.role, 2, rect.y).unwrap();
-        self.prop.set_f32(self.role, 3, rect.y).unwrap();
+    pub fn set(&self, atom: &mut PropertyAtomicGuard, rect: &Rectangle) {
+        self.prop().set_f32(atom, self.role, 0, rect.x).unwrap();
+        self.prop().set_f32(atom, self.role, 1, rect.y).unwrap();
+        self.prop().set_f32(atom, self.role, 2, rect.y).unwrap();
+        self.prop().set_f32(atom, self.role, 3, rect.y).unwrap();
     }
 
+    #[inline]
     pub fn prop(&self) -> PropertyPtr {
         self.prop.clone()
     }

+ 45 - 15
bin/darkwallet/src/scene.rs

@@ -32,7 +32,7 @@ use std::{
 use crate::{
     error::{Error, Result},
     plugin,
-    prop::{Property, PropertyPtr, Role},
+    prop::{Property, PropertyAtomicGuard, PropertyPtr, Role},
     pubsub::{Publisher, PublisherPtr, Subscription},
     ui,
 };
@@ -239,20 +239,50 @@ impl SceneNode {
         self.get_property(name).ok_or(Error::PropertyNotFound)?.get_node_id(0)
     }
     // Setters
-    pub fn set_property_bool(&self, role: Role, name: &str, val: bool) -> Result<()> {
-        self.get_property(name).ok_or(Error::PropertyNotFound)?.set_bool(role, 0, val)
-    }
-    pub fn set_property_u32(&self, role: Role, name: &str, val: u32) -> Result<()> {
-        self.get_property(name).ok_or(Error::PropertyNotFound)?.set_u32(role, 0, val)
-    }
-    pub fn set_property_f32(&self, role: Role, name: &str, val: f32) -> Result<()> {
-        self.get_property(name).ok_or(Error::PropertyNotFound)?.set_f32(role, 0, val)
-    }
-    pub fn set_property_str<S: Into<String>>(&self, role: Role, name: &str, val: S) -> Result<()> {
-        self.get_property(name).ok_or(Error::PropertyNotFound)?.set_str(role, 0, val)
-    }
-    pub fn set_property_node_id(&self, role: Role, name: &str, val: SceneNodeId) -> Result<()> {
-        self.get_property(name).ok_or(Error::PropertyNotFound)?.set_node_id(role, 0, val)
+    pub fn set_property_bool(
+        &self,
+        atom: &mut PropertyAtomicGuard,
+        role: Role,
+        name: &str,
+        val: bool,
+    ) -> Result<()> {
+        self.get_property(name).ok_or(Error::PropertyNotFound)?.set_bool(atom, role, 0, val)
+    }
+    pub fn set_property_u32(
+        &self,
+        atom: &mut PropertyAtomicGuard,
+        role: Role,
+        name: &str,
+        val: u32,
+    ) -> Result<()> {
+        self.get_property(name).ok_or(Error::PropertyNotFound)?.set_u32(atom, role, 0, val)
+    }
+    pub fn set_property_f32(
+        &self,
+        atom: &mut PropertyAtomicGuard,
+        role: Role,
+        name: &str,
+        val: f32,
+    ) -> Result<()> {
+        self.get_property(name).ok_or(Error::PropertyNotFound)?.set_f32(atom, role, 0, val)
+    }
+    pub fn set_property_str<S: Into<String>>(
+        &self,
+        atom: &mut PropertyAtomicGuard,
+        role: Role,
+        name: &str,
+        val: S,
+    ) -> Result<()> {
+        self.get_property(name).ok_or(Error::PropertyNotFound)?.set_str(atom, role, 0, val)
+    }
+    pub fn set_property_node_id(
+        &self,
+        atom: &mut PropertyAtomicGuard,
+        role: Role,
+        name: &str,
+        val: SceneNodeId,
+    ) -> Result<()> {
+        self.get_property(name).ok_or(Error::PropertyNotFound)?.set_node_id(atom, role, 0, val)
     }
 
     pub fn add_signal<S: Into<String>>(

+ 4 - 0
bin/darkwallet/src/text/mod.rs

@@ -165,6 +165,10 @@ impl TextShaper {
         let ft_face = ftlib.new_memory_face2(font_data, 0).unwrap();
         faces.push(ft_face);
 
+        //let font_data = include_bytes!("../../darkfi-custom-emoji.ttf") as &[u8];
+        //let ft_face = ftlib.new_memory_face2(font_data, 0).unwrap();
+        //faces.push(ft_face);
+
         let font_data = include_bytes!("../../NotoColorEmoji.ttf") as &[u8];
         let ft_face = ftlib.new_memory_face2(font_data, 0).unwrap();
         faces.push(ft_face);

+ 112 - 76
bin/darkwallet/src/ui/chatedit.rs

@@ -42,8 +42,8 @@ use crate::{
     },
     mesh::{Color, MeshBuilder, MeshInfo, COLOR_BLUE, COLOR_RED, COLOR_WHITE},
     prop::{
-        PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr, PropertyRect, PropertyStr,
-        PropertyUint32, Role,
+        PropertyAtomicGuard, PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr,
+        PropertyRect, PropertyStr, PropertyUint32, Role,
     },
     pubsub::Subscription,
     scene::{MethodCallSub, Pimpl, SceneNodePtr, SceneNodeWeak},
@@ -470,6 +470,7 @@ pub struct ChatEdit {
     is_active: PropertyBool,
     is_focused: PropertyBool,
     max_height: PropertyFloat32,
+    height: PropertyFloat32,
     rect: PropertyRect,
     baseline: PropertyFloat32,
     linespacing: PropertyFloat32,
@@ -530,6 +531,7 @@ impl ChatEdit {
         let is_active = PropertyBool::wrap(node_ref, Role::Internal, "is_active", 0).unwrap();
         let is_focused = PropertyBool::wrap(node_ref, Role::Internal, "is_focused", 0).unwrap();
         let max_height = PropertyFloat32::wrap(node_ref, Role::Internal, "max_height", 0).unwrap();
+        let height = PropertyFloat32::wrap(node_ref, Role::Internal, "height", 0).unwrap();
         let rect = PropertyRect::wrap(node_ref, Role::Internal, "rect").unwrap();
         let baseline = PropertyFloat32::wrap(node_ref, Role::Internal, "baseline", 0).unwrap();
         let linespacing =
@@ -592,6 +594,7 @@ impl ChatEdit {
             is_active,
             is_focused,
             max_height,
+            height,
             rect,
             baseline: baseline.clone(),
             linespacing: linespacing.clone(),
@@ -680,7 +683,7 @@ impl ChatEdit {
     }
 
     /// Called whenever the text or any text property changes.
-    fn regen_text_mesh(&self, trace_id: u32) -> GfxDrawMesh {
+    fn regen_text_mesh(&self, trace_id: u32, atom: &mut PropertyAtomicGuard) -> GfxDrawMesh {
         let is_focused = self.is_focused.get();
         let text = self.text.get();
         let font_size = self.font_size.get();
@@ -706,7 +709,7 @@ impl ChatEdit {
             let mut text_wrap = self.text_wrap.lock();
             // Must happen after rect eval, which is inside regen_text_mesh
             // Maybe we should take the eval out of here.
-            self.clamp_scroll(&mut text_wrap);
+            self.clamp_scroll(&mut text_wrap, atom);
 
             let rendered = text_wrap.get_render();
             let under_start = rendered.under_start;
@@ -720,7 +723,7 @@ impl ChatEdit {
         let mut height = wrapped_lines.height() + self.descent.get();
         height = height.clamp(0., self.max_height.get());
 
-        self.rect.prop().set_f32(Role::Internal, 3, height);
+        self.rect.prop().set_f32(atom, Role::Internal, 3, height);
 
         // Eval the rect
         let parent_rect = self.parent_rect.lock().clone().unwrap();
@@ -1091,13 +1094,13 @@ impl ChatEdit {
         self.redraw().await;
     }
 
-    async fn insert_char(&self, key: char) {
+    async fn insert_char(&self, key: char, atom: &mut PropertyAtomicGuard) {
         t!("insert_char({key})");
         let mut tmp = [0; 4];
         let key_str = key.encode_utf8(&mut tmp);
-        self.insert_text(key_str).await
+        self.insert_text(key_str, atom).await
     }
-    async fn insert_text(&self, text: &str) {
+    async fn insert_text(&self, text: &str, atom: &mut PropertyAtomicGuard) {
         t!("insert_text({text})");
         let text = {
             let mut text_wrap = &mut self.text_wrap.lock();
@@ -1105,7 +1108,7 @@ impl ChatEdit {
             if !text_wrap.select.is_empty() {
                 text_wrap.delete_selected();
 
-                self.update_select_text(&mut text_wrap);
+                self.update_select_text(&mut text_wrap, atom);
 
                 self.is_phone_select.store(false, Ordering::Relaxed);
                 // Reshow cursor (if hidden)
@@ -1114,13 +1117,18 @@ impl ChatEdit {
             text_wrap.editable.compose(text, true);
             text_wrap.editable.get_text()
         };
-        self.text.set(text);
+        self.text.set(atom, text);
 
         self.pause_blinking();
         self.redraw().await;
     }
 
-    async fn handle_shortcut(&self, key: char, mods: &KeyMods) -> bool {
+    async fn handle_shortcut(
+        &self,
+        key: char,
+        mods: &KeyMods,
+        atom: &mut PropertyAtomicGuard,
+    ) -> bool {
         t!("handle_shortcut({:?}, {:?})", key, mods);
 
         match key {
@@ -1135,7 +1143,7 @@ impl ChatEdit {
                         select.clear();
                         select.push(Selection::new(0, end_pos));
 
-                        self.update_select_text(&mut text_wrap);
+                        self.update_select_text(&mut text_wrap, atom);
                     }
 
                     self.redraw().await;
@@ -1152,7 +1160,7 @@ impl ChatEdit {
                 if mods.ctrl {
                     let mut clip = Clipboard::new();
                     if let Some(text) = clip.get() {
-                        self.insert_text(&text).await;
+                        self.insert_text(&text, atom).await;
                     }
                     return true
                 }
@@ -1162,11 +1170,16 @@ impl ChatEdit {
         false
     }
 
-    async fn handle_key(&self, key: &KeyCode, mods: &KeyMods) -> bool {
+    async fn handle_key(
+        &self,
+        key: &KeyCode,
+        mods: &KeyMods,
+        atom: &mut PropertyAtomicGuard,
+    ) -> bool {
         t!("handle_key({:?}, {:?})", key, mods);
         match key {
             KeyCode::Left => {
-                if !self.adjust_cursor(&mods, |editable| editable.move_cursor(-1)) {
+                if !self.adjust_cursor(&mods, |editable| editable.move_cursor(-1), atom) {
                     return false
                 }
                 self.pause_blinking();
@@ -1175,7 +1188,7 @@ impl ChatEdit {
                 return true
             }
             KeyCode::Right => {
-                if !self.adjust_cursor(&mods, |editable| editable.move_cursor(1)) {
+                if !self.adjust_cursor(&mods, |editable| editable.move_cursor(1), atom) {
                     return false
                 }
                 self.pause_blinking();
@@ -1184,47 +1197,47 @@ impl ChatEdit {
                 return true
             }
             KeyCode::Kp0 => {
-                self.insert_char('0').await;
+                self.insert_char('0', atom).await;
                 return true
             }
             KeyCode::Kp1 => {
-                self.insert_char('1').await;
+                self.insert_char('1', atom).await;
                 return true
             }
             KeyCode::Kp2 => {
-                self.insert_char('2').await;
+                self.insert_char('2', atom).await;
                 return true
             }
             KeyCode::Kp3 => {
-                self.insert_char('3').await;
+                self.insert_char('3', atom).await;
                 return true
             }
             KeyCode::Kp4 => {
-                self.insert_char('4').await;
+                self.insert_char('4', atom).await;
                 return true
             }
             KeyCode::Kp5 => {
-                self.insert_char('5').await;
+                self.insert_char('5', atom).await;
                 return true
             }
             KeyCode::Kp6 => {
-                self.insert_char('6').await;
+                self.insert_char('6', atom).await;
                 return true
             }
             KeyCode::Kp7 => {
-                self.insert_char('7').await;
+                self.insert_char('7', atom).await;
                 return true
             }
             KeyCode::Kp8 => {
-                self.insert_char('8').await;
+                self.insert_char('8', atom).await;
                 return true
             }
             KeyCode::Kp9 => {
-                self.insert_char('9').await;
+                self.insert_char('9', atom).await;
                 return true
             }
             KeyCode::KpDecimal => {
-                self.insert_char('.').await;
+                self.insert_char('.', atom).await;
                 return true
             }
             KeyCode::Enter | KeyCode::KpEnter => {
@@ -1233,28 +1246,28 @@ impl ChatEdit {
                 }
             }
             KeyCode::Delete => {
-                self.delete(0, 1);
-                self.clamp_scroll(&mut self.text_wrap.lock());
+                self.delete(0, 1, atom);
+                self.clamp_scroll(&mut self.text_wrap.lock(), atom);
                 self.pause_blinking();
                 self.redraw().await;
                 return true
             }
             KeyCode::Backspace => {
-                self.delete(1, 0);
-                self.clamp_scroll(&mut self.text_wrap.lock());
+                self.delete(1, 0, atom);
+                self.clamp_scroll(&mut self.text_wrap.lock(), atom);
                 self.pause_blinking();
                 self.redraw().await;
                 return true
             }
             KeyCode::Home => {
-                self.adjust_cursor(&mods, |editable| editable.move_start());
+                self.adjust_cursor(&mods, |editable| editable.move_start(), atom);
                 self.pause_blinking();
                 //self.apply_cursor_scrolling();
                 self.redraw().await;
                 return true
             }
             KeyCode::End => {
-                self.adjust_cursor(&mods, |editable| editable.move_end());
+                self.adjust_cursor(&mods, |editable| editable.move_end(), atom);
                 self.pause_blinking();
                 //self.apply_cursor_scrolling();
                 self.redraw().await;
@@ -1265,14 +1278,14 @@ impl ChatEdit {
         false
     }
 
-    fn delete(&self, before: usize, after: usize) {
+    fn delete(&self, before: usize, after: usize, atom: &mut PropertyAtomicGuard) {
         let mut text_wrap = &mut self.text_wrap.lock();
         if text_wrap.select.is_empty() {
             text_wrap.editable.delete(before, after);
             text_wrap.clear_cache();
         } else {
             text_wrap.delete_selected();
-            self.update_select_text(&mut text_wrap);
+            self.update_select_text(&mut text_wrap, atom);
         }
 
         self.is_phone_select.store(false, Ordering::Relaxed);
@@ -1280,10 +1293,15 @@ impl ChatEdit {
         self.hide_cursor.store(false, Ordering::Relaxed);
 
         let text = text_wrap.editable.get_text();
-        self.text.set(text);
+        self.text.set(atom, text);
     }
 
-    fn adjust_cursor(&self, mods: &KeyMods, move_cursor: impl Fn(&mut Editable)) -> bool {
+    fn adjust_cursor(
+        &self,
+        mods: &KeyMods,
+        move_cursor: impl Fn(&mut Editable),
+        atom: &mut PropertyAtomicGuard,
+    ) -> bool {
         if mods.ctrl || mods.alt || mods.logo {
             return false
         }
@@ -1310,12 +1328,12 @@ impl ChatEdit {
             select.clear();
         }
 
-        self.update_select_text(&mut text_wrap);
+        self.update_select_text(&mut text_wrap, atom);
         true
     }
 
     /// This will select the entire word rather than move the cursor to that location
-    fn start_touch_select(&self, touch_pos: Point) {
+    fn start_touch_select(&self, touch_pos: Point, atom: &mut PropertyAtomicGuard) {
         let mut text_wrap = &mut self.text_wrap.lock();
         text_wrap.clear_cache();
         text_wrap.editable.end_compose();
@@ -1338,14 +1356,14 @@ impl ChatEdit {
         }
 
         d!("Selected {select:?} from {touch_pos:?}");
-        self.update_select_text(&mut text_wrap);
+        self.update_select_text(&mut text_wrap, atom);
     }
 
     /// Call this whenever the selection changes to update the external property
-    fn update_select_text(&self, text_wrap: &mut TextWrap) {
+    fn update_select_text(&self, text_wrap: &mut TextWrap, atom: &mut PropertyAtomicGuard) {
         let select = &text_wrap.select;
         let Some(select) = select.first().cloned() else {
-            self.select_text.set_null(Role::Internal, 0).unwrap();
+            self.select_text.clone().set_null(atom, Role::Internal, 0).unwrap();
             return
         };
 
@@ -1355,13 +1373,13 @@ impl ChatEdit {
         let rendered = text_wrap.get_render();
         let glyphs = &rendered.glyphs[start..end];
         let text = text::glyph_str(glyphs);
-        self.select_text.set_str(Role::Internal, 0, text).unwrap();
+        self.select_text.clone().set_str(atom, Role::Internal, 0, text).unwrap();
     }
 
     /// Call this whenever the cursor pos changes to update the external property
-    fn update_cursor_pos(&self, text_wrap: &mut TextWrap) {
+    fn update_cursor_pos(&self, text_wrap: &mut TextWrap, atom: &mut PropertyAtomicGuard) {
         let cursor_off = text_wrap.editable.get_text_before().len() as u32;
-        self.cursor_pos.set(cursor_off);
+        self.cursor_pos.set(atom, cursor_off);
     }
 
     /*
@@ -1403,9 +1421,9 @@ impl ChatEdit {
             }
         }
 
-        self.text.set(text);
+        self.text.set(atom, text);
         // Not always true lol
-        self.cursor_pos.set(cursor_pos + 1);
+        self.cursor_pos.set(atom, cursor_pos + 1);
 
         self.apply_cursor_scrolling();
         self.redraw().await;
@@ -1487,6 +1505,7 @@ impl ChatEdit {
 
     async fn handle_touch_move(&self, mut touch_pos: Point) -> bool {
         t!("handle_touch_move({touch_pos:?})");
+        let atom = &mut PropertyAtomicGuard::new();
         // We must update with non relative touch_pos bcos when doing vertical scrolling
         // we will modify the scroll, which is used by abs_to_local(), which is used
         // to then calculate the max scroll. So it ends up jumping around.
@@ -1504,7 +1523,7 @@ impl ChatEdit {
                     node.trigger("paste_request", vec![]).await.unwrap();
                 } else {
                     self.abs_to_local(&mut touch_pos);
-                    self.start_touch_select(touch_pos);
+                    self.start_touch_select(touch_pos, atom);
                     self.redraw().await;
                 }
                 d!("touch state: StartSelect -> Select");
@@ -1552,7 +1571,7 @@ impl ChatEdit {
                         select.end = pos;
                     }
 
-                    self.update_select_text(&mut text_wrap);
+                    self.update_select_text(&mut text_wrap, atom);
                 }
                 self.redraw().await;
             }
@@ -1568,7 +1587,7 @@ impl ChatEdit {
                 if (self.scroll.get() - scroll).abs() < VERT_SCROLL_UPDATE_INC {
                     return true
                 }
-                self.scroll.set(scroll);
+                self.scroll.set(atom, scroll);
                 self.redraw().await;
             }
             TouchStateAction::SetCursorPos => {
@@ -1583,13 +1602,14 @@ impl ChatEdit {
     }
     async fn handle_touch_end(&self, mut touch_pos: Point) -> bool {
         t!("handle_touch_end({touch_pos:?})");
+        let atom = &mut PropertyAtomicGuard::new();
         self.abs_to_local(&mut touch_pos);
 
         let state = self.touch_info.lock().stop();
         match state {
             TouchStateAction::Inactive => return false,
             TouchStateAction::Started { pos: _, instant: _ } | TouchStateAction::SetCursorPos => {
-                self.touch_set_cursor_pos(touch_pos).await
+                self.touch_set_cursor_pos(touch_pos, atom).await
             }
             _ => {}
         }
@@ -1600,19 +1620,19 @@ impl ChatEdit {
         true
     }
 
-    async fn touch_set_cursor_pos(&self, mut touch_pos: Point) {
+    async fn touch_set_cursor_pos(&self, mut touch_pos: Point, atom: &mut PropertyAtomicGuard) {
         t!("touch_set_cursor_pos({touch_pos:?})");
         let width = self.wrap_width();
         {
             let mut text_wrap = self.text_wrap.lock();
             let cursor_pos = text_wrap.set_cursor_with_point(touch_pos, width);
-            self.update_cursor_pos(&mut text_wrap);
+            self.update_cursor_pos(&mut text_wrap, atom);
 
             let select = &mut text_wrap.select;
             let select_is_empty = select.is_empty();
             select.clear();
             if !select_is_empty {
-                self.update_select_text(&mut text_wrap);
+                self.update_select_text(&mut text_wrap, atom);
             }
         }
 
@@ -1626,7 +1646,7 @@ impl ChatEdit {
 
     /// Whenever the cursor property is modified this MUST be called
     /// to recalculate the scroll x property.
-    fn apply_cursor_scrolling(&self) {
+    fn apply_cursor_scrolling(&self, atom: &mut PropertyAtomicGuard) {
         let rect = self.rect.get();
 
         let cursor_pos = self.cursor_pos.get() as usize;
@@ -1672,7 +1692,7 @@ impl ChatEdit {
             scroll = cursor_x;
         }
 
-        self.scroll.set(scroll);
+        self.scroll.set(atom, scroll);
     }
 
     fn max_scroll(&self, text_wrap: &mut TextWrap) -> f32 {
@@ -1694,11 +1714,11 @@ impl ChatEdit {
 
     /// When we resize the screen, the rect changes so we may need to alter the scroll.
     /// Or if we delete text.
-    fn clamp_scroll(&self, text_wrap: &mut TextWrap) {
+    fn clamp_scroll(&self, text_wrap: &mut TextWrap, atom: &mut PropertyAtomicGuard) {
         let max_scroll = self.max_scroll(text_wrap);
         let mut scroll = self.scroll.get();
         if scroll > max_scroll {
-            self.scroll.set(max_scroll);
+            self.scroll.set(atom, max_scroll);
         }
     }
 
@@ -1708,10 +1728,11 @@ impl ChatEdit {
     }
 
     async fn redraw(&self) {
+        let atom = &mut PropertyAtomicGuard::new();
         let trace_id = rand::random();
         let timest = unixtime();
         t!("redraw()");
-        let Some(draw_update) = self.make_draw_calls(trace_id) else {
+        let Some(draw_update) = self.make_draw_calls(trace_id, atom) else {
             error!(target: "ui::chatedit", "Text failed to draw");
             return;
         };
@@ -1761,8 +1782,8 @@ impl ChatEdit {
         cursor_instrs
     }
 
-    fn make_draw_calls(&self, trace_id: u32) -> Option<DrawUpdate> {
-        let text_mesh = self.regen_text_mesh(trace_id);
+    fn make_draw_calls(&self, trace_id: u32, atom: &mut PropertyAtomicGuard) -> Option<DrawUpdate> {
+        let text_mesh = self.regen_text_mesh(trace_id, atom);
         let cursor_instrs = self.get_cursor_instrs();
 
         let rect = self.rect.get();
@@ -1822,7 +1843,8 @@ impl ChatEdit {
             panic!("self destroyed before insert_text_method_task was stopped!");
         };
 
-        self_.insert_text(&text).await;
+        let atom = &mut PropertyAtomicGuard::new();
+        self_.insert_text(&text, atom).await;
         true
     }
 }
@@ -1858,9 +1880,10 @@ impl UIObject for ChatEdit {
         // When text has been changed.
         // Cursor and selection might be invalidated.
         async fn reset(self_: Arc<ChatEdit>) {
-            self_.cursor_pos.set(0);
+            let atom = &mut PropertyAtomicGuard::new();
+            self_.cursor_pos.set(atom, 0);
             //self_.select_text.set_null(Role::Internal, 0).unwrap();
-            self_.scroll.set(0.);
+            self_.scroll.set(atom, 0.);
             self_.redraw();
         }
         async fn redraw(self_: Arc<ChatEdit>) {
@@ -1943,9 +1966,10 @@ impl UIObject for ChatEdit {
 
     async fn draw(&self, parent_rect: Rectangle, trace_id: u32) -> Option<DrawUpdate> {
         t!("ChatEdit::draw({:?}, {trace_id})", self.node.upgrade().unwrap());
+        let atom = &mut PropertyAtomicGuard::new();
         *self.parent_rect.lock() = Some(parent_rect);
 
-        self.make_draw_calls(trace_id)
+        self.make_draw_calls(trace_id, atom)
     }
 
     async fn handle_char(&self, key: char, mods: KeyMods, repeat: bool) -> bool {
@@ -1967,16 +1991,19 @@ impl UIObject for ChatEdit {
             repeater.key_down(PressedKey::Char(key), repeat)
         };
 
+        let atom = &mut PropertyAtomicGuard::new();
+
         if mods.ctrl || mods.alt {
             if repeat {
                 return false
             }
-            return self.handle_shortcut(key, &mods).await
+            return self.handle_shortcut(key, &mods, atom).await
         }
 
+        let atom = &mut PropertyAtomicGuard::new();
         t!("Key {:?} has {} actions", key, actions);
         for _ in 0..actions {
-            self.insert_char(key).await;
+            self.insert_char(key, atom).await;
         }
         true
     }
@@ -2003,9 +2030,11 @@ impl UIObject for ChatEdit {
             t!("Key {:?} has {} actions", key, actions);
         }
 
+        let atom = &mut PropertyAtomicGuard::new();
+
         let mut is_handled = false;
         for _ in 0..actions {
-            if self.handle_key(&key, &mods).await {
+            if self.handle_key(&key, &mods, atom).await {
                 is_handled = true;
             }
         }
@@ -2034,6 +2063,8 @@ impl UIObject for ChatEdit {
             return false
         }
 
+        let atom = &mut PropertyAtomicGuard::new();
+
         // clicking inside box will:
         // 1. make it active
         // 2. begin selection
@@ -2041,7 +2072,7 @@ impl UIObject for ChatEdit {
             d!("ChatEdit clicked");
         } else {
             d!("ChatEdit focused");
-            self.is_focused.set(true);
+            self.is_focused.set(atom, true);
         }
 
         // Move mouse pos within this widget
@@ -2052,7 +2083,7 @@ impl UIObject for ChatEdit {
         {
             let mut text_wrap = self.text_wrap.lock();
             let cursor_pos = text_wrap.set_cursor_with_point(mouse_pos, width);
-            self.update_cursor_pos(&mut text_wrap);
+            self.update_cursor_pos(&mut text_wrap, atom);
             d!("Mouse move cursor pos to {cursor_pos}");
 
             // begin selection
@@ -2060,7 +2091,7 @@ impl UIObject for ChatEdit {
             let select_is_empty = select.is_empty();
             select.clear();
             if !select_is_empty {
-                self.update_select_text(&mut text_wrap);
+                self.update_select_text(&mut text_wrap, atom);
             }
 
             self.mouse_btn_held.store(true, Ordering::Relaxed);
@@ -2093,6 +2124,8 @@ impl UIObject for ChatEdit {
             return false;
         }
 
+        let atom = &mut PropertyAtomicGuard::new();
+
         // if active and selection_active, then use x to modify the selection.
         // also implement scrolling when cursor is to the left or right
         // just scroll to the end
@@ -2106,7 +2139,7 @@ impl UIObject for ChatEdit {
         {
             let mut text_wrap = self.text_wrap.lock();
             let cursor_pos = text_wrap.set_cursor_with_point(mouse_pos, width);
-            self.update_cursor_pos(&mut text_wrap);
+            self.update_cursor_pos(&mut text_wrap, atom);
 
             // modify current selection
             let select = &mut text_wrap.select;
@@ -2114,7 +2147,7 @@ impl UIObject for ChatEdit {
                 select.push(Selection::new(cursor_pos, cursor_pos));
             }
             select.first_mut().unwrap().end = cursor_pos;
-            self.update_select_text(&mut text_wrap);
+            self.update_select_text(&mut text_wrap, atom);
         }
 
         self.pause_blinking();
@@ -2128,6 +2161,8 @@ impl UIObject for ChatEdit {
             return false
         }
 
+        let atom = &mut PropertyAtomicGuard::new();
+
         let max_scroll = {
             let mut text_wrap = self.text_wrap.lock();
             self.max_scroll(&mut text_wrap)
@@ -2136,7 +2171,7 @@ impl UIObject for ChatEdit {
         let mut scroll = self.scroll.get() - wheel_pos.y * self.scroll_speed.get();
         scroll = scroll.clamp(0., max_scroll);
         t!("handle_mouse_wheel({wheel_pos:?}) [scroll={scroll}]");
-        self.scroll.set(scroll);
+        self.scroll.set(atom, scroll);
         self.redraw().await;
 
         true
@@ -2162,6 +2197,7 @@ impl UIObject for ChatEdit {
 
     async fn handle_compose_text(&self, suggest_text: &str, is_commit: bool) -> bool {
         t!("handle_compose_text({suggest_text}, {is_commit})");
+        let atom = &mut PropertyAtomicGuard::new();
 
         if !self.is_active.get() {
             return false
@@ -2172,10 +2208,10 @@ impl UIObject for ChatEdit {
             text_wrap.clear_cache();
             text_wrap.editable.compose(suggest_text, is_commit);
 
-            self.clamp_scroll(&mut text_wrap);
+            self.clamp_scroll(&mut text_wrap, atom);
             text_wrap.editable.get_text()
         };
-        self.text.set(text);
+        self.text.set(atom, text);
 
         //self.apply_cursor_scrolling();
         self.redraw().await;

+ 14 - 10
bin/darkwallet/src/ui/chatview/mod.rs

@@ -45,8 +45,8 @@ use crate::{
     },
     mesh::{Color, MeshBuilder, COLOR_BLUE, COLOR_GREEN},
     prop::{
-        PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr, PropertyRect, PropertyUint32,
-        Role,
+        PropertyAtomicGuard, PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr,
+        PropertyRect, PropertyUint32, Role,
     },
     pubsub::Subscription,
     scene::{MethodCallSub, Pimpl, SceneNodeWeak},
@@ -492,7 +492,7 @@ impl ChatView {
         self.motion_cv.notify();
     }
 
-    async fn handle_movement(&self) {
+    async fn handle_movement(&self, atom: &mut PropertyAtomicGuard) {
         // We need to fix this impl because it depends very much on the speed of the device
         // that it's running on.
         // Look into optimizing scrollview() so scrolling is smooth.
@@ -520,7 +520,7 @@ impl ChatView {
             }
 
             let scroll = self.scroll.get() + speed;
-            let dist = self.scrollview(scroll).await;
+            let dist = self.scrollview(scroll, atom).await;
 
             // We reached the end so just stop
             if is_zero(dist) {
@@ -601,7 +601,7 @@ impl ChatView {
         }
     }
 
-    async fn scrollview(&self, mut scroll: f32) -> f32 {
+    async fn scrollview(&self, mut scroll: f32, atom: &mut PropertyAtomicGuard) -> f32 {
         let trace_id = rand::random();
         t!("scrollview({scroll}) [trace_id={trace_id}]");
         let old_scroll = self.scroll.get();
@@ -618,7 +618,7 @@ impl ChatView {
         // 2/3 of time spent here  ~3.3ms
         self.redraw_cached(&mut msgbuf, trace_id).await;
 
-        self.scroll.set(scroll);
+        self.scroll.set(atom, scroll);
         self.bgload_cv.notify();
 
         scroll - old_scroll
@@ -754,7 +754,8 @@ impl UIObject for ChatView {
                     // Should not happen
                     panic!("self destroyed before motion_task was stopped!");
                 };
-                self_.handle_movement().await;
+                let atom = &mut PropertyAtomicGuard::new();
+                self_.handle_movement(atom).await;
                 cv.reset();
             }
         });
@@ -776,7 +777,8 @@ impl UIObject for ChatView {
         let mut on_modify = OnModify::new(ex, self.node.clone(), me.clone());
 
         async fn reload_view(self_: Arc<ChatView>) {
-            self_.scrollview(self_.scroll.get()).await;
+            let atom = &mut PropertyAtomicGuard::new();
+            self_.scrollview(self_.scroll.get(), atom).await;
         }
         on_modify.when_change(self.scroll.prop(), reload_view);
 
@@ -809,6 +811,7 @@ impl UIObject for ChatView {
 
     async fn draw(&self, parent_rect: Rectangle, trace_id: u32) -> Option<DrawUpdate> {
         t!("ChatView::draw({:?}, {trace_id})", self.node.upgrade().unwrap());
+        let atom = &mut PropertyAtomicGuard::new();
 
         *self.parent_rect.lock().unwrap() = Some(parent_rect.clone());
         self.rect.eval(&parent_rect).ok()?;
@@ -821,7 +824,7 @@ impl UIObject for ChatView {
 
         let mut scroll = self.scroll.get();
         if let Some(scroll) = self.adjust_scroll(&mut msgbuf, scroll, rect.h).await {
-            self.scroll.set(scroll);
+            self.scroll.set(atom, scroll);
         }
 
         // We may need to load more messages since the screen size has changed.
@@ -935,6 +938,7 @@ impl UIObject for ChatView {
 
         let rect = self.rect.get();
         t!("handle_touch({phase:?}, {id},{id},  {touch_pos:?})");
+        let atom = &mut PropertyAtomicGuard::new();
 
         let touch_y = touch_pos.y;
 
@@ -1020,7 +1024,7 @@ impl UIObject for ChatView {
                 }
                 let scroll = start_scroll + dist;
                 // Redraws the screen from the cache
-                self.scrollview(scroll).await;
+                self.scrollview(scroll, atom).await;
             }
             TouchPhase::Ended | TouchPhase::Cancelled => {
                 self.end_touch_phase(touch_y);

+ 45 - 32
bin/darkwallet/src/ui/editbox/mod.rs

@@ -39,8 +39,8 @@ use crate::{
     },
     mesh::{MeshBuilder, MeshInfo, COLOR_BLUE, COLOR_WHITE},
     prop::{
-        PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr, PropertyRect, PropertyStr,
-        PropertyUint32, Role,
+        PropertyAtomicGuard, PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr,
+        PropertyRect, PropertyStr, PropertyUint32, Role,
     },
     pubsub::Subscription,
     scene::{Pimpl, SceneNodePtr, SceneNodeWeak},
@@ -637,7 +637,7 @@ impl EditBox {
         self.redraw().await;
     }
 
-    async fn handle_shortcut(&self, key: char, mods: &KeyMods) {
+    async fn handle_shortcut(&self, key: char, mods: &KeyMods, atom: &mut PropertyAtomicGuard) {
         t!("handle_shortcut({:?}, {:?})", key, mods);
 
         match key {
@@ -649,7 +649,7 @@ impl EditBox {
             'v' => {
                 if mods.ctrl {
                     if let Some(text) = window::clipboard_get() {
-                        self.paste_text(text).await;
+                        self.paste_text(text, atom).await;
                     }
                 }
             }
@@ -657,7 +657,7 @@ impl EditBox {
         }
     }
 
-    async fn handle_key(&self, key: &KeyCode, mods: &KeyMods) {
+    async fn handle_key(&self, key: &KeyCode, mods: &KeyMods, atom: &mut PropertyAtomicGuard) {
         t!("handle_key({:?}, {:?})", key, mods);
         match key {
             KeyCode::Left => {
@@ -689,13 +689,13 @@ impl EditBox {
             }
             KeyCode::Delete => {
                 self.delete(0, 1);
-                self.clamp_scroll();
+                self.clamp_scroll(atom);
                 self.pause_blinking();
                 self.redraw().await;
             }
             KeyCode::Backspace => {
                 self.delete(1, 0);
-                self.clamp_scroll();
+                self.clamp_scroll(atom);
                 self.pause_blinking();
                 self.redraw().await;
             }
@@ -837,7 +837,7 @@ impl EditBox {
         text
     }
 
-    fn delete_highlighted(&self) {
+    fn delete_highlighted(&self, atom: &mut PropertyAtomicGuard) {
         assert!(!self.selected.is_null(0).unwrap());
         assert!(!self.selected.is_null(1).unwrap());
 
@@ -852,11 +852,11 @@ impl EditBox {
 
         let text = Self::glyphs_to_string(&glyphs);
         t!("delete_highlighted() text='{text}', cursor_pos={sel_start}");
-        self.text.set(text);
+        self.text.set(atom, text);
 
-        self.selected.set_null(Role::Internal, 0).unwrap();
-        self.selected.set_null(Role::Internal, 1).unwrap();
-        self.cursor_pos.set(sel_start as u32);
+        self.selected.clone().set_null(atom, Role::Internal, 0).unwrap();
+        self.selected.clone().set_null(atom, Role::Internal, 1).unwrap();
+        self.cursor_pos.set(atom, sel_start as u32);
     }
 
     fn copy_highlighted(&self) -> Result<()> {
@@ -880,7 +880,7 @@ impl EditBox {
         Ok(())
     }
 
-    async fn paste_text(&self, key: String) {
+    async fn paste_text(&self, key: String, atom: &mut PropertyAtomicGuard) {
         let mut text = String::new();
 
         let cursor_pos = self.cursor_pos.get();
@@ -897,11 +897,11 @@ impl EditBox {
             }
         }
 
-        self.text.set(text);
+        self.text.set(atom, text);
         // Not always true lol
-        self.cursor_pos.set(cursor_pos + 1);
+        self.cursor_pos.set(atom, cursor_pos + 1);
 
-        self.apply_cursor_scrolling();
+        self.apply_cursor_scrolling(atom);
         self.redraw().await;
     }
 
@@ -974,6 +974,7 @@ impl EditBox {
 
     async fn handle_touch_move(&self, pos: Point) -> bool {
         t!("handle_touch_move({pos:?})");
+        let atom = &mut PropertyAtomicGuard::new();
         let touch_state = {
             let mut touch_info = self.touch_info.lock().unwrap();
             touch_info.update(&pos);
@@ -1028,7 +1029,7 @@ impl EditBox {
                 let x_dist = start_pos.x - pos.x;
                 let mut scroll = scroll_start + x_dist;
                 scroll = scroll.clamp(0., self.max_cursor_scroll());
-                self.scroll.set(scroll);
+                self.scroll.set(atom, scroll);
                 self.redraw().await;
             }
             _ => {}
@@ -1075,7 +1076,7 @@ impl EditBox {
 
     /// Whenever the cursor property is modified this MUST be called
     /// to recalculate the scroll x property.
-    fn apply_cursor_scrolling(&self) {
+    fn apply_cursor_scrolling(&self, atom: &mut PropertyAtomicGuard) {
         let rect = self.rect.get();
 
         let cursor_pos = self.cursor_pos.get() as usize;
@@ -1121,7 +1122,7 @@ impl EditBox {
             scroll = cursor_x;
         }
 
-        self.scroll.set(scroll);
+        self.scroll.set(atom, scroll);
     }
 
     fn max_cursor_scroll(&self) -> f32 {
@@ -1151,10 +1152,10 @@ impl EditBox {
         max_scroll
     }
 
-    fn clamp_scroll(&self) {
+    fn clamp_scroll(&self, atom: &mut PropertyAtomicGuard) {
         let mut scroll = self.scroll.get();
         scroll = scroll.clamp(0., self.max_cursor_scroll());
-        self.scroll.set(scroll);
+        self.scroll.set(atom, scroll);
     }
 
     fn pause_blinking(&self) {
@@ -1272,10 +1273,11 @@ impl UIObject for EditBox {
         // When text has been changed.
         // Cursor and selection might be invalidated.
         async fn reset(self_: Arc<EditBox>) {
-            self_.cursor_pos.set(0);
-            self_.selected.set_null(Role::Internal, 0).unwrap();
-            self_.selected.set_null(Role::Internal, 1).unwrap();
-            self_.scroll.set(0.);
+            let atom = &mut PropertyAtomicGuard::new();
+            self_.cursor_pos.set(atom, 0);
+            self_.selected.clone().set_null(atom, Role::Internal, 0).unwrap();
+            self_.selected.clone().set_null(atom, Role::Internal, 1).unwrap();
+            self_.scroll.set(atom, 0.);
             self_.redraw();
         }
         async fn redraw(self_: Arc<EditBox>) {
@@ -1332,10 +1334,12 @@ impl UIObject for EditBox {
 
     async fn draw(&self, parent_rect: Rectangle, trace_id: u32) -> Option<DrawUpdate> {
         t!("EditBox::draw() [trace_id={trace_id}]");
+        let atom = &mut PropertyAtomicGuard::new();
+
         *self.parent_rect.lock().unwrap() = Some(parent_rect);
         self.rect.eval(&parent_rect).ok()?;
 
-        self.clamp_scroll();
+        self.clamp_scroll(atom);
         self.make_draw_calls()
     }
 
@@ -1349,11 +1353,13 @@ impl UIObject for EditBox {
             return false
         }
 
+        let atom = &mut PropertyAtomicGuard::new();
+
         if mods.ctrl || mods.alt {
             if repeat {
                 return false
             }
-            self.handle_shortcut(key, &mods).await;
+            self.handle_shortcut(key, &mods, atom).await;
             return true
         }
 
@@ -1383,12 +1389,15 @@ impl UIObject for EditBox {
             let mut repeater = self.key_repeat.lock().unwrap();
             repeater.key_down(PressedKey::Key(key), repeat)
         };
+
+        let atom = &mut PropertyAtomicGuard::new();
+
         // Suppress noisy message
         if actions > 0 {
             t!("Key {key:?} has {actions} actions");
         }
         for _ in 0..actions {
-            self.handle_key(&key, &mods).await;
+            self.handle_key(&key, &mods, atom).await;
         }
         true
     }
@@ -1403,6 +1412,7 @@ impl UIObject for EditBox {
         }
 
         let rect = self.rect.get();
+        let atom = &mut PropertyAtomicGuard::new();
 
         // clicking inside box will:
         // 1. make it active
@@ -1410,7 +1420,7 @@ impl UIObject for EditBox {
         if !rect.contains(mouse_pos) {
             if self.is_focused.get() {
                 d!("EditBox unfocused");
-                self.is_focused.set(false);
+                self.is_focused.set(atom, false);
                 self.select.lock().unwrap().clear();
 
                 self.redraw().await;
@@ -1422,7 +1432,7 @@ impl UIObject for EditBox {
             d!("EditBox clicked");
         } else {
             d!("EditBox focused");
-            self.is_focused.set(true);
+            self.is_focused.set(atom, true);
         }
 
         let font_size = self.font_size.get();
@@ -1507,10 +1517,12 @@ impl UIObject for EditBox {
             return false
         }
 
+        let atom = &mut PropertyAtomicGuard::new();
+
         let mut scroll = self.scroll.get() + wheel_pos.y * self.scroll_speed.get();
         scroll = scroll.clamp(0., self.max_cursor_scroll());
         t!("handle_mouse_wheel({wheel_pos:?}) [scroll={scroll}]");
-        self.scroll.set(scroll);
+        self.scroll.set(atom, scroll);
         self.redraw().await;
 
         true
@@ -1546,8 +1558,9 @@ impl UIObject for EditBox {
             editable.compose(suggest_text, is_commit);
         }
 
+        let atom = &mut PropertyAtomicGuard::new();
         //self.apply_cursor_scrolling();
-        self.clamp_scroll();
+        self.clamp_scroll(atom);
         self.redraw().await;
 
         true

+ 49 - 1
bin/darkwallet/src/ui/emoji_picker/emoji.rs

@@ -1,5 +1,53 @@
 pub static EMOJI_LIST: &[&str] = &[
-    "\u{f0003}",
+    //"\u{F0000}",
+    //"\u{F0001}",
+    //"\u{F0002}",
+    //"\u{F0003}",
+    //"\u{F0004}",
+    //"\u{F0005}",
+    //"\u{F0006}",
+    //"\u{F0007}",
+    //"\u{F0008}",
+    //"\u{F0009}",
+    //"\u{F000A}",
+    //"\u{F000B}",
+    //"\u{F000C}",
+    //"\u{F000D}",
+    //"\u{F000E}",
+    //"\u{F000F}",
+    //"\u{F0010}",
+    //"\u{F0011}",
+    //"\u{F0012}",
+    //"\u{F0013}",
+    //"\u{F0014}",
+    //"\u{F0015}",
+    //"\u{F0016}",
+    //"\u{F0017}",
+    //"\u{F0018}",
+    //"\u{F0019}",
+    //"\u{F001A}",
+    //"\u{F001B}",
+    //"\u{F001C}",
+    //"\u{F001D}",
+    //"\u{F001E}",
+    //"\u{F001F}",
+    //"\u{F0020}",
+    //"\u{F0021}",
+    //"\u{F0022}",
+    //"\u{F0023}",
+    //"\u{F0024}",
+    //"\u{F0025}",
+    //"\u{F0026}",
+    //"\u{F0027}",
+    //"\u{F0028}",
+    //"\u{F0029}",
+    //"\u{F002A}",
+    //"\u{F002B}",
+    //"\u{F002C}",
+    //"\u{F002D}",
+    //"\u{F002E}",
+    //"\u{F002F}",
+    //"\u{F0030}",
     "😀",
     "😃",
     "😄",

+ 21 - 7
bin/darkwallet/src/ui/emoji_picker/mod.rs

@@ -35,7 +35,10 @@ use crate::{
         Rectangle, RenderApi,
     },
     mesh::{MeshBuilder, MeshInfo, COLOR_WHITE},
-    prop::{PropertyFloat32, PropertyPtr, PropertyRect, PropertyStr, PropertyUint32, Role},
+    prop::{
+        PropertyAtomicGuard, PropertyFloat32, PropertyPtr, PropertyRect, PropertyStr,
+        PropertyUint32, Role,
+    },
     scene::{Pimpl, SceneNodePtr, SceneNodeWeak},
     text::{self, GlyphPositionIter, TextShaper, TextShaperPtr},
     util::unixtime,
@@ -247,11 +250,12 @@ impl EmojiPicker {
     }
 
     fn redraw(&self) {
+        let atom = &mut PropertyAtomicGuard::new();
         let trace_id = rand::random();
         let timest = unixtime();
         let Some(parent_rect) = self.parent_rect.lock().unwrap().clone() else { return };
 
-        let Some(draw_update) = self.get_draw_calls(parent_rect, trace_id) else {
+        let Some(draw_update) = self.get_draw_calls(parent_rect, trace_id, atom) else {
             error!(target: "ui::emoji_picker", "Emoji picker failed to draw");
             return;
         };
@@ -259,7 +263,12 @@ impl EmojiPicker {
         t!("replace draw calls done");
     }
 
-    fn get_draw_calls(&self, parent_rect: Rectangle, trace_id: u32) -> Option<DrawUpdate> {
+    fn get_draw_calls(
+        &self,
+        parent_rect: Rectangle,
+        trace_id: u32,
+        atom: &mut PropertyAtomicGuard,
+    ) -> Option<DrawUpdate> {
         if let Err(e) = self.rect.eval(&parent_rect) {
             warn!(target: "ui::emoji_picker", "Rect eval failed: {e}");
             return None
@@ -268,7 +277,7 @@ impl EmojiPicker {
         // Clamp scroll if needed due to window size change
         let max_scroll = self.max_scroll();
         if self.scroll.get() > max_scroll {
-            self.scroll.set(max_scroll);
+            self.scroll.set(atom, max_scroll);
         }
 
         let rect = self.rect.get();
@@ -333,8 +342,10 @@ impl UIObject for EmojiPicker {
 
     async fn draw(&self, parent_rect: Rectangle, trace_id: u32) -> Option<DrawUpdate> {
         t!("EmojiPicker::draw({parent_rect:?}, {trace_id})");
+        let atom = &mut PropertyAtomicGuard::new();
+
         *self.parent_rect.lock().unwrap() = Some(parent_rect);
-        self.get_draw_calls(parent_rect, trace_id)
+        self.get_draw_calls(parent_rect, trace_id, atom)
     }
 
     async fn handle_mouse_move(&self, mut mouse_pos: Point) -> bool {
@@ -348,11 +359,12 @@ impl UIObject for EmojiPicker {
             return false
         }
         t!("handle_mouse_wheel()");
+        let atom = &mut PropertyAtomicGuard::new();
 
         let mut scroll = self.scroll.get();
         scroll -= self.mouse_scroll_speed.get() * wheel_pos.y;
         scroll = scroll.clamp(0., self.max_scroll());
-        self.scroll.set(scroll);
+        self.scroll.set(atom, scroll);
 
         self.redraw();
 
@@ -377,6 +389,8 @@ impl UIObject for EmojiPicker {
             return false
         }
 
+        let atom = &mut PropertyAtomicGuard::new();
+
         let rect = self.rect.get();
         let pos = touch_pos - Point::new(rect.x, rect.y);
 
@@ -407,7 +421,7 @@ impl UIObject for EmojiPicker {
                         if touch_info.is_scroll {
                             let mut scroll = touch_info.start_scroll + y_diff;
                             scroll = scroll.clamp(0., self.max_scroll());
-                            self.scroll.set(scroll);
+                            self.scroll.set(atom, scroll);
                             self.redraw();
                         }
                     } else {

+ 4 - 1
bin/darkwallet/src/ui/layer.rs

@@ -99,7 +99,10 @@ impl Layer {
             return;
         };
         self.render_api.replace_draw_calls(timest, draw_update.draw_calls);
-        t!("Layer::redraw({:?}) DONE [timest={timest}, trace_id={trace_id}]", self.node.upgrade().unwrap());
+        t!(
+            "Layer::redraw({:?}) DONE [timest={timest}, trace_id={trace_id}]",
+            self.node.upgrade().unwrap()
+        );
     }
 
     async fn get_draw_calls(&self, parent_rect: Rectangle, trace_id: u32) -> Option<DrawUpdate> {

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

@@ -23,7 +23,7 @@ use crate::{
     gfx::{
         GfxDrawCall, GfxDrawInstruction, GraphicsEventPublisherPtr, Point, Rectangle, RenderApi,
     },
-    prop::{PropertyDimension, PropertyFloat32, PropertyPtr, Role},
+    prop::{PropertyAtomicGuard, PropertyDimension, PropertyFloat32, PropertyPtr, Role},
     pubsub::Subscription,
     scene::{Pimpl, SceneNodePtr, SceneNodeWeak},
     util::unixtime,
@@ -81,8 +81,10 @@ impl Window {
                 };
 
                 d!("Window resized {size:?}");
+                let atom = &mut PropertyAtomicGuard::new();
+
                 // Now update the properties
-                screen_size2.set(size);
+                screen_size2.set(atom, size);
 
                 let Some(self_) = me2.upgrade() else {
                     // Should not happen

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.