Procházet zdrojové kódy

wallet: replace eval_rect()/read_rect() with PropertyRect wrapper

darkfi před 1 rokem
rodič
revize
735c7709dd

+ 14 - 3
bin/darkwallet/src/prop/mod.rs

@@ -30,7 +30,9 @@ use crate::{
 };
 
 mod wrap;
-pub use wrap::{PropertyBool, PropertyColor, PropertyFloat32, PropertyStr, PropertyUint32};
+pub use wrap::{
+    PropertyBool, PropertyColor, PropertyFloat32, PropertyRect, PropertyStr, PropertyUint32,
+};
 
 type Buffer = Arc<Vec<u8>>;
 
@@ -211,6 +213,7 @@ pub enum ModifyAction {
 
 pub type PropertyPtr = Arc<Property>;
 
+#[derive(Debug)]
 pub struct Property {
     pub name: String,
     pub typ: PropertyType,
@@ -551,11 +554,19 @@ impl Property {
         if i >= vals.len() {
             return Err(Error::PropertyWrongIndex)
         }
-        Ok(vals[i].clone())
+        let val = vals[i].clone();
+        Ok(val)
     }
 
     pub fn get_value(&self, i: usize) -> Result<PropertyValue> {
         let val = self.get_raw_value(i)?;
+        if val.is_expr() {
+            let cached = self.get_cached(i)?;
+            if cached.is_null() {
+                return Ok(self.defaults[i].clone())
+            }
+            return Ok(cached)
+        }
         if val.is_unset() {
             return Ok(self.defaults[i].clone())
         }
@@ -634,7 +645,7 @@ impl Property {
     }
 
     pub fn get_expr(&self, i: usize) -> Result<Arc<SExprCode>> {
-        self.get_value(i)?.as_sexpr()
+        self.get_raw_value(i)?.as_sexpr()
     }
 
     pub fn get_cached(&self, i: usize) -> Result<PropertyValue> {

+ 60 - 1
bin/darkwallet/src/prop/wrap.rs

@@ -16,12 +16,15 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use super::{PropertyPtr, Role};
 use crate::{
     error::{Error, Result},
+    expr::{SExprMachine, SExprVal},
+    gfx::Rectangle,
     scene::SceneNode,
 };
 
+use super::{PropertyPtr, Role};
+
 #[derive(Clone)]
 pub struct PropertyBool {
     prop: PropertyPtr,
@@ -189,3 +192,59 @@ impl PropertyColor {
         self.prop.clone()
     }
 }
+
+#[derive(Clone)]
+pub struct PropertyRect {
+    prop: PropertyPtr,
+    role: Role,
+}
+
+impl PropertyRect {
+    pub fn wrap(node: &SceneNode, role: Role, prop_name: &str) -> Result<Self> {
+        let prop = node.get_property(prop_name).ok_or(Error::PropertyNotFound)?;
+
+        if !prop.is_bounded() || prop.get_len() != 4 {
+            return Err(Error::PropertyWrongLen)
+        }
+
+        // Test if it works
+        let _ = prop.get_f32(0)?;
+
+        Ok(Self { prop, role })
+    }
+
+    pub fn eval(&self, parent_rect: &Rectangle) -> Result<()> {
+        for i in 0..4 {
+            if !self.prop.is_expr(i)? {
+                continue
+            }
+
+            let expr = self.prop.get_expr(i).unwrap();
+
+            let machine = SExprMachine {
+                globals: vec![
+                    ("w".to_string(), SExprVal::Float32(parent_rect.w)),
+                    ("h".to_string(), SExprVal::Float32(parent_rect.h)),
+                ],
+                stmts: &expr,
+            };
+
+            let v = machine.call()?.as_f32()?;
+            self.prop.set_cache_f32(i, v).unwrap();
+        }
+        Ok(())
+    }
+
+    pub fn get(&self) -> Rectangle {
+        Rectangle::from_array([
+            self.prop.get_f32(0).unwrap(),
+            self.prop.get_f32(1).unwrap(),
+            self.prop.get_f32(2).unwrap(),
+            self.prop.get_f32(3).unwrap(),
+        ])
+    }
+
+    pub fn prop(&self) -> PropertyPtr {
+        self.prop.clone()
+    }
+}

+ 8 - 18
bin/darkwallet/src/ui/button.rs

@@ -25,13 +25,13 @@ use std::sync::{
 
 use crate::{
     gfx::{GraphicsEventPublisherPtr, Point, Rectangle},
-    prop::{PropertyBool, PropertyPtr, PropertyUint32, Role},
+    prop::{PropertyBool, PropertyPtr, PropertyRect, PropertyUint32, Role},
     pubsub::Subscription,
     scene::{Pimpl, SceneGraph, SceneGraphPtr2, SceneNodeId},
     ExecutorPtr,
 };
 
-use super::{eval_rect, read_rect, DrawUpdate, UIObject};
+use super::{DrawUpdate, UIObject};
 
 pub type ButtonPtr = Arc<Button>;
 
@@ -40,7 +40,7 @@ pub struct Button {
     sg: SceneGraphPtr2,
 
     is_active: PropertyBool,
-    rect: PropertyPtr,
+    rect: PropertyRect,
     z_index: PropertyUint32,
 
     mouse_btn_held: AtomicBool,
@@ -57,7 +57,7 @@ impl Button {
         let node = scene_graph.get_node(node_id).unwrap();
         //let node_name = node.name.clone();
         let is_active = PropertyBool::wrap(node, Role::Internal, "is_active", 0).unwrap();
-        let rect = node.get_property("rect").expect("Button::rect");
+        let rect = PropertyRect::wrap(node, Role::Internal, "rect").unwrap();
         let z_index = PropertyUint32::wrap(node, Role::Internal, "z_index", 0).unwrap();
         //let sig = node.get_signal("click").expect("Button::click");
         drop(scene_graph);
@@ -73,14 +73,6 @@ impl Button {
 
         Pimpl::Button(self_)
     }
-
-    fn get_cached_rect(&self) -> Option<Rectangle> {
-        let Ok(rect) = read_rect(self.rect.clone()) else {
-            error!(target: "ui::button", "cached_rect is None");
-            return None
-        };
-        Some(rect)
-    }
 }
 
 #[async_trait]
@@ -90,9 +82,7 @@ impl UIObject for Button {
     }
 
     async fn draw(&self, _: &SceneGraph, parent_rect: &Rectangle) -> Option<DrawUpdate> {
-        if let Err(err) = eval_rect(self.rect.clone(), parent_rect) {
-            panic!("Button bad rect property: {}", err);
-        }
+        let _ = self.rect.eval(parent_rect);
         None
     }
 
@@ -110,7 +100,7 @@ impl UIObject for Button {
             return false
         }
 
-        let Some(rect) = self.get_cached_rect() else { return false };
+        let rect = self.rect.get();
         if !rect.contains(mouse_pos) {
             return false
         }
@@ -140,7 +130,7 @@ impl UIObject for Button {
         }
 
         // Are we releasing the click inside the button?
-        let Some(rect) = self.get_cached_rect() else { return false };
+        let rect = self.rect.get();
         if !rect.contains(mouse_pos) {
             return false
         }
@@ -168,7 +158,7 @@ impl UIObject for Button {
             return false
         }
 
-        let Some(rect) = self.get_cached_rect() else { return false };
+        let rect = self.rect.get();
         if !rect.contains(touch_pos) {
             //debug!(target: "ui::chatview", "not inside rect");
             return false

+ 17 - 13
bin/darkwallet/src/ui/chatview/mod.rs

@@ -44,7 +44,10 @@ use crate::{
         RenderApi, RenderApiPtr,
     },
     mesh::{Color, MeshBuilder, COLOR_BLUE, COLOR_GREEN},
-    prop::{PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr, PropertyUint32, Role},
+    prop::{
+        PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr, PropertyRect, PropertyUint32,
+        Role,
+    },
     pubsub::Subscription,
     ringbuf::RingBuffer,
     scene::{Pimpl, SceneGraph, SceneGraphPtr2, SceneNodeId},
@@ -53,7 +56,7 @@ use crate::{
     ExecutorPtr,
 };
 
-use super::{eval_rect, get_parent_rect, read_rect, DrawUpdate, OnModify, UIObject};
+use super::{get_parent_rect, DrawUpdate, OnModify, UIObject};
 
 const EPSILON: f32 = 0.001;
 const BIG_EPSILON: f32 = 0.05;
@@ -148,7 +151,7 @@ pub struct ChatView {
     touch_info: SyncMutex<Option<TouchInfo>>,
     touch_is_active: AtomicBool,
 
-    rect: PropertyPtr,
+    rect: PropertyRect,
     scroll: PropertyFloat32,
     font_size: PropertyFloat32,
     line_height: PropertyFloat32,
@@ -194,7 +197,7 @@ impl ChatView {
         let node = scene_graph.get_node(node_id).unwrap();
         let node_name = node.name.clone();
 
-        let rect = node.get_property("rect").expect("ChatView::rect");
+        let rect = PropertyRect::wrap(node, Role::Internal, "rect").unwrap();
         let scroll = PropertyFloat32::wrap(node, Role::Internal, "scroll", 0).unwrap();
         let font_size = PropertyFloat32::wrap(node, Role::Internal, "font_size", 0).unwrap();
         let line_height = PropertyFloat32::wrap(node, Role::Internal, "line_height", 0).unwrap();
@@ -358,7 +361,7 @@ impl ChatView {
     async fn select_line(&self, mut y: f32) {
         // The cursor is inside the rect. We just have to find which line it clicked.
         let scroll = self.scroll.get();
-        let rect = read_rect(self.rect.clone()).expect("bad rect property");
+        let rect = self.rect.get();
         let bottom = scroll + rect.y + rect.h;
 
         assert!(bottom >= y);
@@ -494,7 +497,7 @@ impl ChatView {
         //debug!(target: "ui::chatview", "ChatView::handle_bgload()");
         // Do we need to load some more?
         let scroll = self.scroll.get();
-        let mut rect = read_rect(self.rect.clone()).expect("bad rect property");
+        let rect = self.rect.get();
         let top = scroll + rect.h;
 
         let preload_height = PRELOAD_PAGES as f32 * rect.h;
@@ -564,7 +567,7 @@ impl ChatView {
         //debug!(target: "ui::chatview", "scrollview()");
         let old_scroll = self.scroll.get();
 
-        let rect = read_rect(self.rect.clone()).expect("bad rect property");
+        let rect = self.rect.get();
 
         let mut msgbuf = self.msgbuf.lock().await;
 
@@ -654,7 +657,7 @@ impl ChatView {
     }
 
     async fn redraw_cached(&self, msgbuf: &mut MessageBuffer) {
-        let rect = read_rect(self.rect.clone()).expect("bad rect property");
+        let rect = self.rect.get();
 
         let (mut mesh_instrs, freed) = self.get_meshes(msgbuf, &rect).await;
 
@@ -691,7 +694,8 @@ impl UIObject for ChatView {
         debug!(target: "ui::chatview", "ChatView::draw()");
 
         *self.parent_rect.lock().unwrap() = Some(parent_rect.clone());
-        let rect = eval_rect(self.rect.clone(), parent_rect).expect("bad rect property");
+        self.rect.eval(parent_rect).ok()?;
+        let rect = self.rect.get();
 
         let mut msgbuf = self.msgbuf.lock().await;
         msgbuf.adjust_width(rect.w);
@@ -758,7 +762,7 @@ impl UIObject for ChatView {
             return false
         }
 
-        let Ok(rect) = read_rect(self.rect.clone()) else { return false };
+        let rect = self.rect.get();
         if !rect.contains(mouse_pos) {
             return false
         }
@@ -792,7 +796,7 @@ impl UIObject for ChatView {
             return false
         }
 
-        let Ok(rect) = read_rect(self.rect.clone()) else { return false };
+        let rect = self.rect.get();
         if !rect.contains(mouse_pos) {
             return false
         }
@@ -804,7 +808,7 @@ impl UIObject for ChatView {
     async fn handle_mouse_wheel(&self, sg: &SceneGraph, wheel_pos: &Point) -> bool {
         //debug!(target: "ui::chatview", "handle_mouse_wheel({wheel_x}, {wheel_y})");
 
-        let Ok(rect) = read_rect(self.rect.clone()) else { return false };
+        let rect = self.rect.get();
 
         let mouse_pos = self.mouse_pos.lock().unwrap().clone();
         if !rect.contains(&mouse_pos) {
@@ -829,7 +833,7 @@ impl UIObject for ChatView {
             return false
         }
 
-        let Ok(rect) = read_rect(self.rect.clone()) else { return false };
+        let rect = self.rect.get();
         //debug!(target: "ui::chatview", "handle_touch({phase:?}, {touch_x}, {touch_y})");
 
         let touch_y = touch_pos.y;

+ 11 - 27
bin/darkwallet/src/ui/editbox.rs

@@ -36,8 +36,8 @@ use crate::{
     },
     mesh::{MeshBuilder, MeshInfo, COLOR_BLUE, COLOR_WHITE},
     prop::{
-        PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr, PropertyStr, PropertyUint32,
-        Role,
+        PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr, PropertyRect, PropertyStr,
+        PropertyUint32, Role,
     },
     pubsub::Subscription,
     scene::{Pimpl, SceneGraph, SceneGraphPtr2, SceneNodeId},
@@ -46,7 +46,7 @@ use crate::{
     ExecutorPtr,
 };
 
-use super::{eval_rect, get_parent_rect, read_rect, DrawUpdate, OnModify, Stoppable, UIObject};
+use super::{DrawUpdate, OnModify, Stoppable, UIObject};
 
 // Pixel width of the cursor
 const CURSOR_WIDTH: f32 = 2.;
@@ -163,7 +163,7 @@ pub struct EditBox {
 
     is_active: PropertyBool,
     is_focused: PropertyBool,
-    rect: PropertyPtr,
+    rect: PropertyRect,
     baseline: PropertyFloat32,
     scroll: PropertyFloat32,
     cursor_pos: PropertyUint32,
@@ -198,7 +198,7 @@ impl EditBox {
 
         let is_active = PropertyBool::wrap(node, Role::Internal, "is_active", 0).unwrap();
         let is_focused = PropertyBool::wrap(node, Role::Internal, "is_focused", 0).unwrap();
-        let rect = node.get_property("rect").expect("EditBox::rect");
+        let rect = PropertyRect::wrap(node, Role::Internal, "rect").unwrap();
         let baseline = PropertyFloat32::wrap(node, Role::Internal, "baseline", 0).unwrap();
         let scroll = PropertyFloat32::wrap(node, Role::Internal, "scroll", 0).unwrap();
         let cursor_pos = PropertyUint32::wrap(node, Role::Internal, "cursor_pos", 0).unwrap();
@@ -233,7 +233,7 @@ impl EditBox {
             async fn redraw(self_: Arc<EditBox>) {
                 self_.redraw().await;
             }
-            on_modify.when_change(rect.clone(), redraw);
+            on_modify.when_change(rect.prop(), redraw);
             on_modify.when_change(baseline.prop(), redraw);
             // The commented properties are modified on input events
             // So then redraw() will get repeatedly triggered when these properties
@@ -437,7 +437,7 @@ impl EditBox {
             return
         }
 
-        let Some(rect) = self.cached_rect() else { return };
+        let rect = self.rect.get();
 
         // clicking inside box will:
         // 1. make it active
@@ -494,7 +494,7 @@ impl EditBox {
         // just scroll to the end
         // also set cursor_pos too
 
-        let Some(rect) = self.cached_rect() else { return };
+        let rect = self.rect.get();
         let cpos = self.find_closest_glyph_idx(pos.x, &rect);
 
         self.cursor_pos.set(cpos);
@@ -885,24 +885,10 @@ impl EditBox {
         self.redraw().await;
     }
 
-    /// Beware of this method. Here be dragons.
-    /// Possibly racy so we limit it just to cursor scrolling.
-    fn cached_rect(&self) -> Option<Rectangle> {
-        let Ok(rect) = read_rect(self.rect.clone()) else {
-            error!(target: "ui::editbox", "cached_rect is None");
-            return None
-        };
-        Some(rect)
-    }
-
     /// Whenever the cursor property is modified this MUST be called
     /// to recalculate the scroll x property.
     fn apply_cursor_scrolling(&self) {
-        // This may need updating but yolo rite
-        let Some(rect) = self.cached_rect() else {
-            error!(target: "ui::editbox", "cached_rect() returned None");
-            return
-        };
+        let rect = self.rect.get();
 
         let cursor_pos = self.cursor_pos.get() as usize;
         let mut scroll = self.scroll.get();
@@ -964,7 +950,7 @@ impl EditBox {
     }
 
     fn draw_cached(&self) -> Option<DrawUpdate> {
-        let Ok(rect) = read_rect(self.rect.clone()) else { panic!("Node bad rect property") };
+        let rect = self.rect.get();
 
         // draw will recalc this when it's None
         let render_info = self.regen_mesh(rect.clone());
@@ -1060,9 +1046,7 @@ impl UIObject for EditBox {
         // Only used for debug messages
         let node = sg.get_node(self.node_id).unwrap();
 
-        if let Err(err) = eval_rect(self.rect.clone(), parent_rect) {
-            panic!("Node {:?} bad rect property: {}", node, err);
-        }
+        self.rect.eval(parent_rect).ok()?;
 
         self.draw_cached()
     }

+ 7 - 12
bin/darkwallet/src/ui/image.rs

@@ -27,12 +27,12 @@ use std::{
 use crate::{
     gfx::{GfxDrawCall, GfxDrawInstruction, GfxDrawMesh, GfxTextureId, Rectangle, RenderApiPtr},
     mesh::{MeshBuilder, MeshInfo, COLOR_WHITE},
-    prop::{PropertyPtr, PropertyStr, PropertyUint32, Role},
+    prop::{PropertyPtr, PropertyRect, PropertyStr, PropertyUint32, Role},
     scene::{Pimpl, SceneGraph, SceneGraphPtr2, SceneNodeId},
     ExecutorPtr,
 };
 
-use super::{eval_rect, get_parent_rect, read_rect, DrawUpdate, OnModify, UIObject};
+use super::{get_parent_rect, DrawUpdate, OnModify, UIObject};
 
 pub type ImagePtr = Arc<Image>;
 
@@ -47,7 +47,7 @@ pub struct Image {
     dc_key: u64,
 
     node_id: SceneNodeId,
-    rect: PropertyPtr,
+    rect: PropertyRect,
     z_index: PropertyUint32,
     path: PropertyStr,
 }
@@ -62,14 +62,14 @@ impl Image {
         let scene_graph = sg.lock().await;
         let node = scene_graph.get_node(node_id).unwrap();
         let node_name = node.name.clone();
-        let rect = node.get_property("rect").expect("Text::rect");
+        let rect = PropertyRect::wrap(node, Role::Internal, "rect").unwrap();
         let z_index = PropertyUint32::wrap(node, Role::Internal, "z_index", 0).unwrap();
         let path = PropertyStr::wrap(node, Role::Internal, "path", 0).unwrap();
         drop(scene_graph);
 
         let self_ = Arc::new_cyclic(|me: &Weak<Self>| {
             let mut on_modify = OnModify::new(ex, node_name, node_id, me.clone());
-            on_modify.when_change(rect.clone(), Self::redraw);
+            on_modify.when_change(rect.prop(), Self::redraw);
             on_modify.when_change(z_index.prop(), Self::redraw);
             on_modify.when_change(path.prop(), Self::reload);
 
@@ -181,13 +181,8 @@ impl UIObject for Image {
         // Only used for debug messages
         let node = sg.get_node(self.node_id).unwrap();
 
-        if let Err(err) = eval_rect(self.rect.clone(), parent_rect) {
-            panic!("Node {:?} bad rect property: {}", node, err);
-        }
-
-        let Ok(rect) = read_rect(self.rect.clone()) else {
-            panic!("Node {:?} bad rect property", node);
-        };
+        self.rect.eval(parent_rect).ok()?;
+        let rect = self.rect.get();
 
         // draw will recalc this when it's None
         let mesh = self.regen_mesh(rect.clone());

+ 8 - 13
bin/darkwallet/src/ui/layer.rs

@@ -24,14 +24,14 @@ use std::sync::{Arc, Weak};
 
 use crate::{
     gfx::{GfxDrawCall, GfxDrawInstruction, Point, Rectangle, RenderApiPtr},
-    prop::{PropertyBool, PropertyPtr, PropertyUint32, Role},
+    prop::{PropertyBool, PropertyPtr, PropertyRect, PropertyUint32, Role},
     scene::{Pimpl, SceneGraph, SceneGraphPtr2, SceneNodeId},
     ExecutorPtr,
 };
 
 use super::{
-    eval_rect, get_child_nodes_ordered, get_parent_rect, get_ui_object, read_rect, DrawUpdate,
-    OnModify, Stoppable, UIObject,
+    get_child_nodes_ordered, get_parent_rect, get_ui_object, DrawUpdate, OnModify, Stoppable,
+    UIObject,
 };
 
 pub type LayerPtr = Arc<Layer>;
@@ -47,7 +47,7 @@ pub struct Layer {
     dc_key: u64,
 
     is_visible: PropertyBool,
-    rect: PropertyPtr,
+    rect: PropertyRect,
     z_index: PropertyUint32,
 }
 
@@ -64,13 +64,13 @@ impl Layer {
 
         let is_visible =
             PropertyBool::wrap(node, Role::Internal, "is_visible", 0).expect("Layer::is_visible");
-        let rect = node.get_property("rect").expect("Layer::rect");
+        let rect = PropertyRect::wrap(node, Role::Internal, "rect").unwrap();
         let z_index = PropertyUint32::wrap(node, Role::Internal, "z_index", 0).unwrap();
         drop(sg);
 
         let self_ = Arc::new_cyclic(|me: &Weak<Self>| {
             let mut on_modify = OnModify::new(ex.clone(), node_name, node_id, me.clone());
-            on_modify.when_change(rect.clone(), Self::redraw);
+            on_modify.when_change(rect.prop(), Self::redraw);
 
             Self {
                 sg: sg_ptr,
@@ -134,13 +134,8 @@ impl UIObject for Layer {
             return None
         }
 
-        if let Err(err) = eval_rect(self.rect.clone(), parent_rect) {
-            panic!("Node {:?} bad rect property: {}", node, err);
-        }
-
-        let Ok(mut rect) = read_rect(self.rect.clone()) else {
-            panic!("Node {:?} bad rect property", node);
-        };
+        self.rect.eval(parent_rect).ok()?;
+        let mut rect = self.rect.get();
 
         rect.x += parent_rect.x;
         rect.y += parent_rect.x;

+ 1 - 32
bin/darkwallet/src/ui/mod.rs

@@ -167,38 +167,7 @@ impl<T: Send + Sync + 'static> OnModify<T> {
     }
 }
 
-pub fn eval_rect(rect: PropertyPtr, parent_rect: &Rectangle) -> Result<Rectangle> {
-    if rect.array_len != 4 {
-        return Err(Error::PropertyWrongLen)
-    }
-
-    let mut rect_arr = [0.; 4];
-
-    for i in 0..4 {
-        if !rect.is_expr(i)? {
-            rect_arr[i] = rect.get_f32(i)?;
-            continue
-        }
-
-        let expr = rect.get_expr(i).unwrap();
-
-        let machine = SExprMachine {
-            globals: vec![
-                ("w".to_string(), SExprVal::Float32(parent_rect.w)),
-                ("h".to_string(), SExprVal::Float32(parent_rect.h)),
-            ],
-            stmts: &expr,
-        };
-
-        let v = machine.call()?.as_f32()?;
-        rect.set_cache_f32(i, v).unwrap();
-
-        rect_arr[i] = v;
-    }
-    Ok(Rectangle::from_array(rect_arr))
-}
-
-pub fn read_rect(rect_prop: PropertyPtr) -> Result<Rectangle> {
+fn read_rect(rect_prop: PropertyPtr) -> Result<Rectangle> {
     if rect_prop.array_len != 4 {
         return Err(Error::PropertyWrongLen)
     }

+ 8 - 13
bin/darkwallet/src/ui/text.rs

@@ -27,15 +27,15 @@ use crate::{
     },
     mesh::{Color, MeshBuilder, MeshInfo, COLOR_BLUE, COLOR_WHITE},
     prop::{
-        PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr, PropertyStr, PropertyUint32,
-        Role,
+        PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr, PropertyRect, PropertyStr,
+        PropertyUint32, Role,
     },
     scene::{Pimpl, SceneGraph, SceneGraphPtr2, SceneNodeId},
     text::{self, GlyphPositionIter, TextShaper, TextShaperPtr},
     ExecutorPtr,
 };
 
-use super::{eval_rect, get_parent_rect, read_rect, DrawUpdate, OnModify, Stoppable, UIObject};
+use super::{get_parent_rect, DrawUpdate, OnModify, Stoppable, UIObject};
 
 pub type TextPtr = Arc<Text>;
 
@@ -55,7 +55,7 @@ pub struct Text {
     dc_key: u64,
 
     node_id: SceneNodeId,
-    rect: PropertyPtr,
+    rect: PropertyRect,
     z_index: PropertyUint32,
     text: PropertyStr,
     font_size: PropertyFloat32,
@@ -75,7 +75,7 @@ impl Text {
         let scene_graph = sg.lock().await;
         let node = scene_graph.get_node(node_id).unwrap();
         let node_name = node.name.clone();
-        let rect = node.get_property("rect").expect("Text::rect");
+        let rect = PropertyRect::wrap(node, Role::Internal, "rect").unwrap();
         let z_index = PropertyUint32::wrap(node, Role::Internal, "z_index", 0).unwrap();
         let text = PropertyStr::wrap(node, Role::Internal, "text", 0).unwrap();
         let font_size = PropertyFloat32::wrap(node, Role::Internal, "font_size", 0).unwrap();
@@ -97,7 +97,7 @@ impl Text {
 
         let self_ = Arc::new_cyclic(|me: &Weak<Self>| {
             let mut on_modify = OnModify::new(ex, node_name, node_id, me.clone());
-            on_modify.when_change(rect.clone(), Self::redraw);
+            on_modify.when_change(rect.prop(), Self::redraw);
             on_modify.when_change(z_index.prop(), Self::redraw);
             on_modify.when_change(text.prop(), Self::redraw);
             on_modify.when_change(font_size.prop(), Self::redraw);
@@ -233,13 +233,8 @@ impl UIObject for Text {
             num_elements: render_info.mesh.num_elements,
         };
 
-        if let Err(err) = eval_rect(self.rect.clone(), parent_rect) {
-            panic!("Node {:?} bad rect property: {}", node, err);
-        }
-
-        let Ok(rect) = read_rect(self.rect.clone()) else {
-            panic!("Node {:?} bad rect property", node);
-        };
+        self.rect.eval(parent_rect).ok()?;
+        let rect = self.rect.get();
 
         let off_x = rect.x / parent_rect.w;
         let off_y = rect.y / parent_rect.h;

+ 7 - 12
bin/darkwallet/src/ui/vector_art/mod.rs

@@ -27,13 +27,13 @@ use crate::{
         GfxBufferId, GfxDrawCall, GfxDrawInstruction, GfxDrawMesh, Rectangle, RenderApiPtr, Vertex,
     },
     mesh::Color,
-    prop::{PropertyPtr, PropertyUint32, Role},
+    prop::{PropertyPtr, PropertyRect, PropertyUint32, Role},
     scene::{Pimpl, SceneGraph, SceneGraphPtr2, SceneNodeId},
     util::enumerate,
     ExecutorPtr,
 };
 
-use super::{eval_rect, get_parent_rect, read_rect, DrawUpdate, OnModify, Stoppable, UIObject};
+use super::{get_parent_rect, DrawUpdate, OnModify, Stoppable, UIObject};
 
 pub mod shape;
 use shape::VectorShape;
@@ -51,7 +51,7 @@ pub struct VectorArt {
     dc_key: u64,
 
     node_id: SceneNodeId,
-    rect: PropertyPtr,
+    rect: PropertyRect,
     z_index: PropertyUint32,
 }
 
@@ -66,13 +66,13 @@ impl VectorArt {
         let scene_graph = sg.lock().await;
         let node = scene_graph.get_node(node_id).unwrap();
         let node_name = node.name.clone();
-        let rect = node.get_property("rect").expect("Mesh::rect");
+        let rect = PropertyRect::wrap(node, Role::Internal, "rect").unwrap();
         let z_index = PropertyUint32::wrap(node, Role::Internal, "z_index", 0).unwrap();
         drop(scene_graph);
 
         let self_ = Arc::new_cyclic(|me: &Weak<Self>| {
             let mut on_modify = OnModify::new(ex, node_name, node_id, me.clone());
-            on_modify.when_change(rect.clone(), Self::redraw);
+            on_modify.when_change(rect.prop(), Self::redraw);
             on_modify.when_change(z_index.prop(), Self::redraw);
 
             Self {
@@ -132,13 +132,8 @@ impl UIObject for VectorArt {
         // Only used for debug messages
         let node = sg.get_node(self.node_id).unwrap();
 
-        if let Err(err) = eval_rect(self.rect.clone(), parent_rect) {
-            panic!("Node {:?} bad rect property: {}", node, err);
-        }
-
-        let Ok(mut rect) = read_rect(self.rect.clone()) else {
-            panic!("Node {:?} bad rect property", node);
-        };
+        self.rect.eval(parent_rect).ok()?;
+        let mut rect = self.rect.get();
 
         rect.x += parent_rect.x;
         rect.y += parent_rect.x;