Ver Fonte

wallet: make a more powerful property system

rsx há 2 anos atrás
pai
commit
2f58097787

+ 116 - 8
bin/darkwallet/gui/__init__.py

@@ -28,6 +28,11 @@ class App(EventLoop):
 
         self.cursor_layer = Layer("cursor_layer")
         self.cursor_layer.add_obj("cursor")
+        self.cursor_layer.resize(w, h)
+
+        self.rounded_box_layer = Layer("rounded_box_layer")
+        self.rounded_box_layer.add_obj("box")
+        self.rounded_box_layer.resize(w, h)
 
         self.user_input = ""
         self.last_keypress_time = 0
@@ -35,7 +40,9 @@ class App(EventLoop):
     def resize_event(self, w, h):
         self.chatbox_layer.resize(w, h)
         self.cursor_layer.resize(w, h)
+        self.rounded_box_layer.resize(w, h)
         resize_box()
+        resize_rounded_box()
         draw_txt(self.user_input)
 
     def mouse_click(self, x, y):
@@ -108,8 +115,19 @@ def draw_txt(user_input):
     rename_node("/window/chatbox_layer/user_input2/txt2", "txt")
     rename_node("/window/chatbox_layer/user_input2",      "user_input")
 
+    reposition_cursor()
+
+def reposition_cursor():
+    layer_h = get_property("/window/chatbox_layer", "rect_h")
+    y = layer_h - 20 - 30
+
     # Move the cursor
-    text_px_w = get_property("/window/chatbox_layer/user_input/txt", "width")
+    text_id = api.lookup_node_id("/window/chatbox_layer/user_input/txt")
+    text_px_w = 0
+    user_input = ""
+    if text_id is not None:
+        text_px_w = get_property(text_id, "width")
+        user_input = get_property(text_id, "text")
     x = text_px_w + 25
     if user_input and user_input[-1] == " ":
         x += 10
@@ -152,7 +170,7 @@ def resize_box():
 
     box_h = 60
     # Inner padding, so box inside will be (box_h - 2*padding) px high
-    padding = 5
+    padding = 10
 
     # Lets add a poly - must be counterclockwise
     x, y = 0, layer_h - box_h
@@ -177,12 +195,102 @@ def resize_box():
     api.set_property_buffer(mesh_id, "verts", vert1 + vert2 + vert3 + vert4)
     api.set_property_buffer(mesh_id, "faces", face(0, 2, 1) + face(1, 2, 3))
 
-def main():
-    garbage_collect()
+def draw_rounded_box():
+    mesh_id = api.add_node("box", SceneNodeType.RENDER_MESH)
+    api.add_property(mesh_id, "verts", PropertyType.BUFFER)
+    api.add_property(mesh_id, "faces", PropertyType.BUFFER)
+    link_node(mesh_id, "/window/rounded_box_layer/box")
+
+    mesh_id = api.add_node("inner_box", SceneNodeType.RENDER_MESH)
+    api.add_property(mesh_id, "verts", PropertyType.BUFFER)
+    api.add_property(mesh_id, "faces", PropertyType.BUFFER)
+    link_node(mesh_id, "/window/rounded_box_layer/box")
+
+    resize_rounded_box()
+
+def resize_rounded_box():
+    mesh_id = api.lookup_node_id("/window/rounded_box_layer/box/box")
+
+    layer_w = get_property("/window/rounded_box_layer", "rect_w")
+    layer_h = get_property("/window/rounded_box_layer", "rect_h")
 
-    app = App()
-    draw_box()
-    draw_cursor()
+    # Inner padding, so box inside will be (box_h - 2*padding) px high
+    padding = 5
+
+    bevel = 20
+
+    # Lets add a poly - must be counterclockwise
+    x, y = layer_w/4, layer_h/4
+    w, h = layer_w/2, layer_h/2
+    y0 = y + bevel
+    h0 = h - 2*bevel
+    verts = (
+        vertex(x,     y0,     1, 1, 1, 1, 0, 0) +
+        vertex(x + w, y0,     1, 1, 1, 1, 1, 0) +
+        vertex(x,     y0 + h0, 1, 1, 1, 1, 0, 1) +
+        vertex(x + w, y0 + h0, 1, 1, 1, 1, 1, 1)
+    )
+    faces = face(0, 2, 1) + face(1, 2, 3)
+    x0 = x + bevel
+    w0 = w - 2*bevel
+    h0 = bevel
+    verts += (
+        vertex(x0,      y,     1, 1, 1, 1, 0, 0) +
+        vertex(x0 + w0, y,     1, 1, 1, 1, 1, 0) +
+        vertex(x0,      y + h0, 1, 1, 1, 1, 0, 1) +
+        vertex(x0 + w0, y + h0, 1, 1, 1, 1, 1, 1)
+    )
+    o = 4
+    faces += face(o + 0, o + 2, o + 1) + face(o + 1, o + 2, o + 3)
+    y0 = y + h - bevel
+    x0 = x + bevel
+    w0 = w - 2*bevel
+    h0 = bevel
+    verts += (
+        vertex(x0,      y0,     1, 1, 1, 1, 0, 0) +
+        vertex(x0 + w0, y0,     1, 1, 1, 1, 1, 0) +
+        vertex(x0,      y0 + h0, 1, 1, 1, 1, 0, 1) +
+        vertex(x0 + w0, y0 + h0, 1, 1, 1, 1, 1, 1)
+    )
+    o = 8
+    faces += face(o + 0, o + 2, o + 1) + face(o + 1, o + 2, o + 3)
+    api.set_property_buffer(mesh_id, "verts", verts)
+    api.set_property_buffer(mesh_id, "faces", faces)
+
+    # Second mesh
+    mesh_id = api.lookup_node_id("/window/rounded_box_layer/box/inner_box")
+
+    x, y = x + padding, y + padding
+    w -= 2*padding
+    h -= 2*padding
+    vert1 = vertex(x,     y,     0, 0, 0, 1, 0, 0)
+    vert2 = vertex(x + w, y,     0, 0, 0, 1, 1, 0)
+    vert3 = vertex(x,     y + h, 0, 0, 0, 1, 0, 1)
+    vert4 = vertex(x + w, y + h, 0.3, 0.3, 0.3, 1, 1, 1)
+    #api.set_property_buffer(mesh_id, "verts", vert1 + vert2 + vert3 + vert4)
+    #api.set_property_buffer(mesh_id, "faces", face(0, 2, 1) + face(1, 2, 3))
+
+def main():
+    node_id = api.add_node("foo", SceneNodeType.WINDOW)
+    prop = Property(
+        "myprop", PropertyType.FLOAT32, PropertySubType.NULL,
+        None,
+        "myprop", "",
+        False, 2, None, None, []
+    )
+    api.add_property(1, prop)
+    api.link_node(node_id, 0)
+    api.set_property_f32(1, "myprop", 0, 4.0)
+    api.set_property_f32(1, "myprop", 1, 110.0)
+    print(api.get_property_value(1, "myprop"))
     print_tree()
-    app.run()
+    #garbage_collect()
+
+    #app = App()
+    #draw_box()
+    #draw_rounded_box()
+    #draw_cursor()
+    #reposition_cursor()
+    ##print_tree()
+    #app.run()
 

+ 3 - 3
bin/darkwallet/gui/api.py

@@ -1,5 +1,5 @@
 from collections import namedtuple
-from pydrk import Api, HostApi, PropertyType
+from pydrk import Api, HostApi, PropertyType, PropertySubType, Property
 import zmq
 
 api = Api()
@@ -28,8 +28,8 @@ def register_slot(node_path, sig, tag):
     node_id = api.lookup_node_id(node_path)
     api.register_slot(node_id, sig, "", tag)
 
-def get_property(node_path, prop):
-    node_id = api.lookup_node_id(node_path)
+def get_property(node_id, prop):
+    node_id = lookup_node(node_id)
     return api.get_property(node_id, prop)
 
 def set_property(node_id, prop, val):

+ 11 - 11
bin/darkwallet/gui/print_tree.py

@@ -49,19 +49,19 @@ def print_node_info(parent_id, indent):
 
         print_node_info(child_id, indent+1)
 
-    for prop_name, prop_type in api.get_properties(parent_id):
-        if prop_type == PropertyType.STR:
-            prop_val = api.get_property(parent_id, prop_name)
-            prop_val = f" = \"{prop_val}\""
-        elif prop_type != PropertyType.BUFFER:
-            prop_val = api.get_property(parent_id, prop_name)
-            prop_val = f" = {prop_val}"
-        else:
-            prop_val = ""
+    #for prop_name, prop_type in api.get_properties(parent_id):
+    #    if prop_type == PropertyType.STR:
+    #        prop_val = api.get_property(parent_id, prop_name)
+    #        prop_val = f" = \"{prop_val}\""
+    #    elif prop_type != PropertyType.BUFFER:
+    #        prop_val = api.get_property(parent_id, prop_name)
+    #        prop_val = f" = {prop_val}"
+    #    else:
+    #        prop_val = ""
 
-        prop_type = PropertyType.to_str(prop_type)
+    #    prop_type = PropertyType.to_str(prop_type)
 
-        print(f"{ws}{prop_name}: {prop_type}{prop_val}")
+    #    print(f"{ws}{prop_name}: {prop_type}{prop_val}")
 
     for sig in api.get_signals(parent_id):
         print(f"{ws}~{sig}")

+ 3 - 1
bin/darkwallet/pydrk/__init__.py

@@ -1,4 +1,6 @@
-from .api import Api, ErrorCode, SceneNodeType, PropertyType, vertex, face
+from .api import (Api, ErrorCode, SceneNodeType,
+                  PropertyType, PropertySubType, Property,
+                  vertex, face)
 from .host import HostApi
 from . import exc, serial
 

+ 219 - 85
bin/darkwallet/pydrk/api.py

@@ -1,7 +1,22 @@
 import zmq
+from collections import namedtuple
 from . import serial
 from . import exc
 
+Property = namedtuple("Property", [
+    "name",
+    "type",
+    "subtype",
+    "defaults",
+    "ui_name",
+    "desc",
+    "is_null_allowed",
+    "array_len",
+    "min_val",
+    "max_val",
+    "enum_items"
+])
+
 class Command:
     HELLO = 0
     ADD_NODE = 1
@@ -16,8 +31,8 @@ class Command:
     GET_CHILDREN = 4
     GET_PARENTS = 5
     GET_PROPERTIES = 3
-    GET_PROPERTY = 6
-    SET_PROPERTY = 7
+    GET_PROPERTY_VALUE = 6
+    SET_PROPERTY_VALUE = 7
     GET_SIGNALS = 14
     REGISTER_SLOT = 15
     UNREGISTER_SLOT = 16
@@ -45,20 +60,19 @@ class SceneNodeType:
 
 class PropertyType:
     NULL = 0
-    BUFFER = 1
-    BOOL = 2
-    UINT32 = 3
-    FLOAT32 = 4
-    STR = 5
-    SCENE_NODE_ID = 6
+    BOOL = 1
+    UINT32 = 2
+    FLOAT32 = 3
+    STR = 4
+    ENUM = 5
+    BUFFER = 6
+    SCENE_NODE_ID = 7
 
     @staticmethod
     def to_str(prop_type):
         match prop_type:
             case PropertyType.NULL:
                 return "null"
-            case PropertyType.BUFFER:
-                return "buffer"
             case PropertyType.BOOL:
                 return "bool"
             case PropertyType.UINT32:
@@ -67,29 +81,55 @@ class PropertyType:
                 return "float32"
             case PropertyType.STR:
                 return "str"
+            case PropertyType.ENUM:
+                return "enum"
+            case PropertyType.BUFFER:
+                return "buffer"
             case PropertyType.SCENE_NODE_ID:
                 return "scene_node_id"
 
+class PropertySubType:
+    NULL = 0
+    COLOR = 1
+    PIXEL = 2
+
+    @staticmethod
+    def to_str(prop_type):
+        match prop_type:
+            case PropertySubType.NULL:
+                return "null"
+            case PropertySubType.Color:
+                return "color"
+            case PropertySubType.PIXEL:
+                return "pixel"
+
 class ErrorCode:
-    INVALID_SCENE_PATH = 2
-    NODE_NOT_FOUND = 3
-    CHILD_NODE_NOT_FOUND = 4
-    PARENT_NODE_NOT_FOUND = 5
-    PROPERTY_ALREADY_EXISTS = 6
-    PROPERTY_NOT_FOUND = 7
-    PROPERTY_WRONG_TYPE = 8
-    SIGNAL_ALREADY_EXISTS = 9
-    SIGNAL_NOT_FOUND = 10
-    SLOT_NOT_FOUND = 11
-    METHOD_NOT_FOUND = 12
-    NODES_ARE_LINKED = 13
-    NODES_NOT_LINKED = 14
-    NODE_HAS_PARENTS = 15
-    NODE_HAS_CHILDREN = 16
-    NODE_PARENT_NAME_CONFLICT = 17
-    NODE_CHILD_NAME_CONFLICT = 18
-    NODE_SIBLING_NAME_CONFLICT = 19
-    FILE_NOT_FOUND = 20
+    INVALID_SCENE_PATH = 1
+    NODE_NOT_FOUND = 2
+    CHILD_NODE_NOT_FOUND = 3
+    PARENT_NODE_NOT_FOUND = 4
+    PROPERTY_ALREADY_EXISTS = 5
+    PROPERTY_NOT_FOUND = 6
+    PROPERTY_WRONG_TYPE = 7
+    PROPERTY_WRONG_LEN = 8
+    PROPERTY_WRONG_INDEX = 9
+    PROPERTY_OUT_OF_RANGE = 10
+    PROPERTY_NULL_NOT_ALLOWED = 11
+    PROPERTY_IS_BOUNDED = 12
+    PROPERTY_WRONG_ENUM_ITEM = 13
+    SIGNAL_ALREADY_EXISTS = 14
+    SIGNAL_NOT_FOUND = 15
+    SLOT_NOT_FOUND = 16
+    METHOD_ALREADY_EXISTS = 17
+    METHOD_NOT_FOUND = 18
+    NODES_ARE_LINKED = 19
+    NODES_NOT_LINKED = 20
+    NODE_HAS_PARENTS = 21
+    NODE_HAS_CHILDREN = 22
+    NODE_PARENT_NAME_CONFLICT = 23
+    NODE_CHILD_NAME_CONFLICT = 24
+    NODE_SIBLING_NAME_CONFLICT = 25
+    FILE_NOT_FOUND = 26
 
     @staticmethod
     def to_str(errc):
@@ -108,28 +148,42 @@ class ErrorCode:
                 return "property_not_found"
             case ErrorCode.PROPERTY_WRONG_TYPE:
                 return "property_wrong_type"
+            case ErrorCode.PROPERTY_WRONG_LEN:
+                return "property_wrong_len"
+            case ErrorCode.PROPERTY_WRONG_INDEX:
+                return "property_wrong_index"
+            case ErrorCode.PROPERTY_OUT_OF_RANGE:
+                return "property_out_of_range"
+            case ErrorCode.PROPERTY_NULL_NOT_ALLOWED:
+                return "property_null_not_allowed"
+            case ErrorCode.PROPERTY_IS_BOUNDED:
+                return "property_is_bounded"
+            case ErrorCode.PROPERTY_WRONG_ENUM_ITEM:
+                return "property_wrong_enum_item"
             case ErrorCode.SIGNAL_ALREADY_EXISTS:
                 return "signal_already_exists"
             case ErrorCode.SIGNAL_NOT_FOUND:
-                return "signal_not_found "
+                return "signal_not_found"
             case ErrorCode.SLOT_NOT_FOUND:
-                return "slot_not_found "
+                return "slot_not_found"
+            case ErrorCode.METHOD_ALREADY_EXISTS:
+                return "method_already_exists"
             case ErrorCode.METHOD_NOT_FOUND:
-                return "method_not_found "
+                return "method_not_found"
             case ErrorCode.NODES_ARE_LINKED:
-                return "nodes_are_linked "
+                return "nodes_are_linked"
             case ErrorCode.NODES_NOT_LINKED:
-                return "nodes_not_linked "
+                return "nodes_not_linked"
             case ErrorCode.NODE_HAS_PARENTS:
-                return "node_has_parents "
+                return "node_has_parents"
             case ErrorCode.NODE_HAS_CHILDREN:
-                return "node_has_children "
+                return "node_has_children"
             case ErrorCode.NODE_PARENT_NAME_CONFLICT:
-                return "node_parent_name_conflict "
+                return "node_parent_name_conflict"
             case ErrorCode.NODE_CHILD_NAME_CONFLICT:
-                return "node_child_name_conflict "
+                return "node_child_name_conflict"
             case ErrorCode.NODE_SIBLING_NAME_CONFLICT:
-                return "node_sibling_name_conflict "
+                return "node_sibling_name_conflict"
             case ErrorCode.FILE_NOT_FOUND:
                 return "file_not_found"
 
@@ -169,44 +223,58 @@ class Api:
         errc = int.from_bytes(errc, "little")
         cursor = serial.Cursor(reply)
         match errc:
+            case 1:
+                raise exc.InvalidScenePath
             case 2:
-                raise exc.RequestInvalidScenePath
+                raise exc.NodeNotFound
             case 3:
-                raise exc.RequestNodeNotFound
+                raise exc.ChildNodeNotFound
             case 4:
-                raise exc.RequestChildNodeNotFound
+                raise exc.ParentNodeNotFound
             case 5:
-                raise exc.RequestParentNodeNotFound
+                raise exc.PropertyAlreadyExists
             case 6:
-                raise exc.RequestPropertyAlreadyExists
+                raise exc.PropertyNotFound
             case 7:
-                raise exc.RequestPropertyNotFound
+                raise exc.PropertyWrongType
             case 8:
-                raise exc.RequestPropertyWrongType
+                raise exc.PropertyWrongLen
             case 9:
-                raise exc.RequestSignalAlreadyExists
+                raise exc.PropertyWrongIndex
             case 10:
-                raise exc.RequestSignalNotFound
+                raise exc.PropertyOutOfRange
             case 11:
-                raise exc.RequestSlotNotFound
+                raise exc.PropertyNullNotAllowed
             case 12:
-                raise exc.RequestMethodNotFound
+                raise exc.PropertyIsBounded
             case 13:
-                raise exc.RequestNodesAreLinked
+                raise exc.PropertyWrongEnumItem
             case 14:
-                raise exc.RequestNodesNotLinked
+                raise exc.SignalAlreadyExists
             case 15:
-                raise exc.RequestNodeHasParents
+                raise exc.SignalNotFound
             case 16:
-                raise exc.RequestNodeHasChildren
+                raise exc.SlotNotFound
             case 17:
-                raise exc.RequestNodeParentNameConflict
+                raise exc.MethodAlreadyExists
             case 18:
-                raise exc.RequestNodeChildNameConflict
+                raise exc.MethodNotFound
             case 19:
-                raise exc.RequestNodeSiblingNameConflict
+                raise exc.NodesAreLinked
             case 20:
-                raise exc.RequestFileNotFound
+                raise exc.NodesNotLinked
+            case 21:
+                raise exc.NodeHasParents
+            case 22:
+                raise exc.NodeHasChildren
+            case 23:
+                raise exc.NodeParentNameConflict
+            case 24:
+                raise exc.NodeChildNameConflict
+            case 25:
+                raise exc.NodeSiblingNameConflict
+            case 26:
+                raise exc.FileNotFound
         return cursor
 
     def hello(self):
@@ -259,27 +327,43 @@ class Api:
             props.append((prop_name, prop_type))
         return props
 
-    def get_property(self, node_id, prop_name):
+    def get_property_value(self, node_id, prop_name):
         req = bytearray()
         serial.write_u32(req, node_id)
         serial.encode_str(req, prop_name)
-        cur = self._make_request(Command.GET_PROPERTY, req)
+        cur = self._make_request(Command.GET_PROPERTY_VALUE, req)
         prop_type = serial.read_u8(cur)
+        prop_len = serial.decode_varint(cur)
+        vals = []
+
+        def read_array(read_fn):
+            for _ in range(prop_len):
+                is_some = serial.read_u8(cur)
+                if is_some:
+                    val = read_fn()
+                    vals.append(val)
+
         match prop_type:
-            case 0:
+            case PropertyType.NULL:
                 return None
-            case 1:
-                return []
-            case 3:
-                val = serial.read_u8(cur)
-                return bool(val)
-            case 2:
-                return serial.read_u32(cur)
-            case 4:
-                return serial.read_f32(cur)
-            case 5:
-                return serial.decode_str(cur)
-        raise Exception("unknown property type returned")
+            case PropertyType.BOOL:
+                read_array(lambda: bool(serial.read_u8(cur)))
+            case PropertyType.UINT32:
+                read_array(lambda: serial.read_u32(cur))
+            case PropertyType.FLOAT32:
+                read_array(lambda: serial.read_f32(cur))
+            case PropertyType.STR:
+                read_array(lambda: serial.decode_str(cur))
+            case PropertyType.ENUM:
+                read_array(lambda: serial.decode_str(cur))
+            case PropertyType.BUFFER:
+                pass
+            case PropertyType.SCENE_NODE_ID:
+                read_array(lambda: serial.read_u32(cur))
+            case _:
+                raise Exception("unknown property type returned")
+
+        return vals
 
     def add_node(self, node_name, node_type):
         req = bytearray()
@@ -318,11 +402,57 @@ class Api:
             return None
         return serial.read_u32(cur)
 
-    def add_property(self, node_id, prop_name, prop_type):
+    def add_property(self, node_id, prop):
         req = bytearray()
         serial.write_u32(req, node_id)
-        serial.encode_str(req, prop_name)
-        serial.write_u8(req, int(prop_type))
+        serial.encode_str(req, prop.name)
+        serial.write_u8(req, int(prop.type))
+        serial.write_u8(req, int(prop.subtype))
+        serial.write_u32(req, int(prop.array_len))
+        if prop.defaults is None:
+            serial.write_u8(req, 0)
+        else:
+            serial.write_u8(req, 1)
+            defaults_len = len(prop.defaults)
+            serial.encode_varint(req, defaults_len)
+            for default in prop.defaults:
+                match prop.type:
+                    case PropertyType.UINT32:
+                        serial.write_u32(req, default)
+                    case PropertyType.FLOAT32:
+                        serial.write_f32(req, default)
+                    case _:
+                        raise exc.PropertyWrongType
+        serial.encode_str(req, prop.ui_name)
+        serial.encode_str(req, prop.desc)
+        serial.write_u8(req, int(prop.is_null_allowed))
+        if prop.min_val is None:
+            serial.write_u8(req, 0)
+        else:
+            serial.write_u8(req, 1)
+            match prop.type:
+                case PropertyType.UINT32:
+                    serial.write_u32(req, prop.min_val)
+                case PropertyType.FLOAT32:
+                    serial.write_f32(req, prop.min_val)
+                case _:
+                    raise exc.PropertyWrongType
+        if prop.max_val is None:
+            serial.write_u8(req, 0)
+        else:
+            serial.write_u8(req, 1)
+            match prop.type:
+                case PropertyType.UINT32:
+                    serial.write_u32(req, prop.max_val)
+                case PropertyType.FLOAT32:
+                    serial.write_f32(req, prop.max_val)
+                case _:
+                    raise exc.PropertyWrongType
+        serial.encode_varint(req, len(prop.enum_items))
+        for enum_item in prop.enum_items:
+            if prop.type != PropertyType.ENUM:
+                raise exc.PropertyWrongType
+            serial.encode_str(req, enum_item)
         self._make_request(Command.ADD_PROPERTY, req)
 
     def link_node(self, child_id, parent_id):
@@ -342,35 +472,39 @@ class Api:
         serial.write_u32(req, node_id)
         serial.encode_str(req, prop_name)
         serial.write_u8(req, int(val))
-        self._make_request(Command.SET_PROPERTY, req)
+        self._make_request(Command.SET_PROPERTY_VALUE, req)
 
-    def set_property_u32(self, node_id, prop_name, val):
+    def set_property_u32(self, node_id, prop_name, i, val):
         req = bytearray()
         serial.write_u32(req, node_id)
         serial.encode_str(req, prop_name)
+        serial.write_u32(req, i)
         serial.write_u32(req, val)
-        self._make_request(Command.SET_PROPERTY, req)
+        self._make_request(Command.SET_PROPERTY_VALUE, req)
 
-    def set_property_f32(self, node_id, prop_name, val):
+    def set_property_f32(self, node_id, prop_name, i, val):
         req = bytearray()
         serial.write_u32(req, node_id)
         serial.encode_str(req, prop_name)
+        serial.write_u32(req, i)
         serial.write_f32(req, val)
-        self._make_request(Command.SET_PROPERTY, req)
+        self._make_request(Command.SET_PROPERTY_VALUE, req)
 
-    def set_property_buffer(self, node_id, prop_name, buf):
+    def set_property_buffer(self, node_id, prop_name, i, buf):
         req = bytearray()
         serial.write_u32(req, node_id)
         serial.encode_str(req, prop_name)
+        serial.write_u32(req, i)
         serial.encode_buf(req, buf)
-        self._make_request(Command.SET_PROPERTY, req)
+        self._make_request(Command.SET_PROPERTY_VALUE, req)
 
-    def set_property_str(self, node_id, prop_name, val):
+    def set_property_str(self, node_id, prop_name, i, val):
         req = bytearray()
         serial.write_u32(req, node_id)
         serial.encode_str(req, prop_name)
+        serial.write_u32(req, i)
         serial.encode_str(req, val)
-        self._make_request(Command.SET_PROPERTY, req)
+        self._make_request(Command.SET_PROPERTY_VALUE, req)
 
     def get_signals(self, node_id):
         req = bytearray()

+ 33 - 19
bin/darkwallet/pydrk/exc.py

@@ -1,38 +1,52 @@
-class RequestInvalidScenePath(Exception):
+class InvalidScenePath(Exception):
     pass
-class RequestNodeNotFound(Exception):
+class NodeNotFound(Exception):
     pass
-class RequestChildNodeNotFound(Exception):
+class ChildNodeNotFound(Exception):
     pass
-class RequestParentNodeNotFound(Exception):
+class ParentNodeNotFound(Exception):
     pass
-class RequestPropertyAlreadyExists(Exception):
+class PropertyAlreadyExists(Exception):
     pass
-class RequestPropertyNotFound(Exception):
+class PropertyNotFound(Exception):
     pass
-class RequestPropertyWrongType(Exception):
+class PropertyWrongType(Exception):
     pass
-class RequestSignalAlreadyExists(Exception):
+class PropertyWrongLen(Exception):
     pass
-class RequestSignalNotFound(Exception):
+class PropertyWrongIndex(Exception):
     pass
-class RequestSlotNotFound(Exception):
+class PropertyOutOfRange(Exception):
     pass
-class RequestMethodNotFound(Exception):
+class PropertyNullNotAllowed(Exception):
     pass
-class RequestNodesAreLinked(Exception):
+class PropertyIsBounded(Exception):
     pass
-class RequestNodesNotLinked(Exception):
+class PropertyWrongEnumItem(Exception):
     pass
-class RequestNodeHasParents(Exception):
+class SignalAlreadyExists(Exception):
     pass
-class RequestNodeHasChildren(Exception):
+class SignalNotFound(Exception):
     pass
-class RequestNodeParentNameConflict(Exception):
+class SlotNotFound(Exception):
     pass
-class RequestNodeChildNameConflict(Exception):
+class MethodAlreadyExists(Exception):
     pass
-class RequestNodeSiblingNameConflict(Exception):
+class MethodNotFound(Exception):
     pass
-class RequestFileNotFound(Exception):
+class NodesAreLinked(Exception):
+    pass
+class NodesNotLinked(Exception):
+    pass
+class NodeHasParents(Exception):
+    pass
+class NodeHasChildren(Exception):
+    pass
+class NodeParentNameConflict(Exception):
+    pass
+class NodeChildNameConflict(Exception):
+    pass
+class NodeSiblingNameConflict(Exception):
+    pass
+class FileNotFound(Exception):
     pass

+ 40 - 19
bin/darkwallet/src/error.rs

@@ -4,59 +4,80 @@ pub type Result<T> = std::result::Result<T, Error>;
 #[derive(Debug, Copy, Clone, thiserror::Error)]
 pub enum Error {
     #[error("Invalid scene path")]
-    InvalidScenePath = 2,
+    InvalidScenePath = 1,
 
     #[error("Node not found")]
-    NodeNotFound = 3,
+    NodeNotFound = 2,
 
     #[error("Child node not found")]
-    ChildNodeNotFound = 4,
+    ChildNodeNotFound = 3,
 
     #[error("Parent node not found")]
-    ParentNodeNotFound = 5,
+    ParentNodeNotFound = 4,
 
     #[error("Property already exists")]
-    PropertyAlreadyExists = 6,
+    PropertyAlreadyExists = 5,
 
     #[error("Property not found")]
-    PropertyNotFound = 7,
+    PropertyNotFound = 6,
 
     #[error("Property has wrong type")]
-    PropertyWrongType = 8,
+    PropertyWrongType = 7,
+
+    #[error("Property value has the wrong length")]
+    PropertyWrongLen = 8,
+
+    #[error("Property index is wrong")]
+    PropertyWrongIndex = 9,
+
+    #[error("Property out of range")]
+    PropertyOutOfRange = 10,
+
+    #[error("Property null not allowed")]
+    PropertyNullNotAllowed = 11,
+
+    #[error("Property array is bounded length")]
+    PropertyIsBounded = 12,
+
+    #[error("Property enum item is invalid")]
+    PropertyWrongEnumItem = 13,
 
     #[error("Signal already exists")]
-    SignalAlreadyExists = 9,
+    SignalAlreadyExists = 14,
 
     #[error("Signal not found")]
-    SignalNotFound = 10,
+    SignalNotFound = 15,
 
     #[error("Slot not found")]
-    SlotNotFound = 11,
+    SlotNotFound = 16,
+
+    #[error("Signal already exists")]
+    MethodAlreadyExists = 17,
 
     #[error("Method not found")]
-    MethodNotFound = 12,
+    MethodNotFound = 18,
 
     #[error("Nodes are not linked")]
-    NodesAreLinked = 13,
+    NodesAreLinked = 19,
 
     #[error("Nodes are not linked")]
-    NodesNotLinked = 14,
+    NodesNotLinked = 20,
 
     #[error("Node has parents")]
-    NodeHasParents = 15,
+    NodeHasParents = 21,
 
     #[error("Node has children")]
-    NodeHasChildren = 16,
+    NodeHasChildren = 22,
 
     #[error("Node has a parent with this name")]
-    NodeParentNameConflict = 17,
+    NodeParentNameConflict = 23,
 
     #[error("Node has a child with this name")]
-    NodeChildNameConflict = 18,
+    NodeChildNameConflict = 24,
 
     #[error("Node has a sibling with this name")]
-    NodeSiblingNameConflict = 19,
+    NodeSiblingNameConflict = 25,
 
     #[error("File not found")]
-    FileNotFound = 20,
+    FileNotFound = 26,
 }

+ 136 - 71
bin/darkwallet/src/gfx.rs

@@ -8,7 +8,8 @@ use std::{fmt, io::Cursor};
 
 use crate::{
     error::{Error, Result},
-    scene::{PropertyType, SceneGraph, SceneGraphPtr, SceneNode, SceneNodeId, SceneNodeType},
+    prop::{Property, PropertySubType, PropertyType},
+    scene::{SceneGraph, SceneGraphPtr, SceneNode, SceneNodeId, SceneNodeType},
     shader,
 };
 
@@ -157,15 +158,15 @@ impl Stage {
             let font = include_bytes!("../Inter-Regular.ttf") as &[u8];
             let font = Font::from_bytes(font, FontSettings::default()).unwrap();
             let line_metrics = font.horizontal_line_metrics(1.).unwrap();
-            inter_regular.add_property_f32("ascent", line_metrics.ascent).unwrap();
-            inter_regular.add_property_f32("descent", line_metrics.descent).unwrap();
-            inter_regular.add_property_f32("line_gap", line_metrics.line_gap).unwrap();
-            inter_regular.add_property_f32("new_line_size", line_metrics.new_line_size).unwrap();
+            //inter_regular.add_property_f32("ascent", line_metrics.ascent).unwrap();
+            //inter_regular.add_property_f32("descent", line_metrics.descent).unwrap();
+            //inter_regular.add_property_f32("line_gap", line_metrics.line_gap).unwrap();
+            //inter_regular.add_property_f32("new_line_size", line_metrics.new_line_size).unwrap();
 
             inter_regular.add_method(
                 "create_text",
-                vec![("text", PropertyType::Str), ("font_size", PropertyType::Float32)],
-                vec![("node_id", PropertyType::SceneNodeId)],
+                vec![("text", "", PropertyType::Str), ("font_size", "", PropertyType::Float32)],
+                vec![("node_id", "", PropertyType::SceneNodeId)],
             );
 
             let inter_regular_id = inter_regular.id;
@@ -183,14 +184,29 @@ impl Stage {
 
         let window = scene_graph.add_node("window", SceneNodeType::Window);
         let (screen_width, screen_height) = window::screen_size();
-        window.add_property_f32("width", screen_width).unwrap();
-        window.add_property_f32("height", screen_height).unwrap();
-        window.add_signal("resize").unwrap();
-        window.add_method(
-            "load_texture",
-            vec![("node_name", PropertyType::Str), ("path", PropertyType::Str)],
-            vec![("node_id", PropertyType::SceneNodeId)],
-        );
+
+        let mut prop = Property::new("screen_size", PropertyType::Float32, PropertySubType::Pixel);
+        prop.set_array_len(2);
+        prop.set_f32(0, screen_width);
+        prop.set_f32(1, screen_height);
+        window.add_property(prop).unwrap();
+
+        window
+            .add_signal(
+                "resize",
+                vec![
+                    ("screen_width", "", PropertyType::Float32),
+                    ("screen_height", "", PropertyType::Float32),
+                ],
+            )
+            .unwrap();
+        window
+            .add_method(
+                "load_texture",
+                vec![("node_name", "", PropertyType::Str), ("path", "", PropertyType::Str)],
+                vec![("node_id", "", PropertyType::SceneNodeId)],
+            )
+            .unwrap();
         let window_id = window.id;
         scene_graph.link(window_id, SceneGraph::ROOT_ID).unwrap();
 
@@ -199,27 +215,54 @@ impl Stage {
         scene_graph.link(input_id, window_id).unwrap();
 
         let keyb = scene_graph.add_node("keyboard", SceneNodeType::Keyboard);
-        keyb.add_property("shift", PropertyType::Bool).unwrap();
-        keyb.add_property("ctrl", PropertyType::Bool).unwrap();
-        keyb.add_property("alt", PropertyType::Bool).unwrap();
-        keyb.add_property("logo", PropertyType::Bool).unwrap();
-        keyb.add_property("repeat", PropertyType::Bool).unwrap();
-        keyb.add_property("keycode", PropertyType::Str).unwrap();
-        keyb.add_signal("key_down").unwrap();
+        keyb.add_signal(
+            "key_down",
+            vec![
+                ("shift", "", PropertyType::Bool),
+                ("ctrl", "", PropertyType::Bool),
+                ("alt", "", PropertyType::Bool),
+                ("logo", "", PropertyType::Bool),
+                ("repeat", "", PropertyType::Bool),
+                ("keycode", "", PropertyType::Enum),
+            ],
+        )
+        .unwrap();
         let keyb_id = keyb.id;
         scene_graph.link(keyb_id, input_id).unwrap();
 
         let mouse = scene_graph.add_node("mouse", SceneNodeType::Mouse);
-        mouse.add_property("button", PropertyType::Uint32).unwrap();
-        mouse.add_property("motion_x", PropertyType::Float32).unwrap();
-        mouse.add_property("motion_y", PropertyType::Float32).unwrap();
-        mouse.add_property("wheel_y", PropertyType::Float32).unwrap();
-        mouse.add_property("click_x", PropertyType::Float32).unwrap();
-        mouse.add_property("click_y", PropertyType::Float32).unwrap();
-        mouse.add_signal("button_up").unwrap();
-        mouse.add_signal("button_down").unwrap();
-        mouse.add_signal("wheel").unwrap();
-        mouse.add_signal("move").unwrap();
+        mouse
+            .add_signal(
+                "button_up",
+                vec![
+                    ("button", "", PropertyType::Enum),
+                    ("x", "", PropertyType::Float32),
+                    ("y", "", PropertyType::Float32),
+                ],
+            )
+            .unwrap();
+        mouse
+            .add_signal(
+                "button_down",
+                vec![
+                    ("button", "", PropertyType::Enum),
+                    ("x", "", PropertyType::Float32),
+                    ("y", "", PropertyType::Float32),
+                ],
+            )
+            .unwrap();
+        mouse
+            .add_signal(
+                "wheel",
+                vec![("x", "", PropertyType::Float32), ("y", "", PropertyType::Float32)],
+            )
+            .unwrap();
+        mouse
+            .add_signal(
+                "move",
+                vec![("x", "", PropertyType::Float32), ("y", "", PropertyType::Float32)],
+            )
+            .unwrap();
         let mouse_id = mouse.id;
         scene_graph.link(mouse_id, input_id).unwrap();
     }
@@ -312,12 +355,16 @@ impl Stage {
     ) -> Result<SceneNodeId> {
         let mut scene_graph = self.scene_graph.lock().unwrap();
         let text_node = scene_graph.add_node(node_name, SceneNodeType::RenderText);
-        text_node.add_property_str("text", text.clone()).unwrap();
-        text_node.add_property_f32("font_size", font_size).unwrap();
-        text_node.add_property_f32("r", 0.).unwrap();
-        text_node.add_property_f32("g", 0.).unwrap();
-        text_node.add_property_f32("b", 0.).unwrap();
-        text_node.add_property_f32("a", 0.).unwrap();
+
+        let mut prop = Property::new("text", PropertyType::Str, PropertySubType::Null);
+        text_node.add_property(prop)?;
+
+        let mut prop = Property::new("font_size", PropertyType::Float32, PropertySubType::Pixel);
+        text_node.add_property(prop)?;
+
+        let mut prop = Property::new("color", PropertyType::Float32, PropertySubType::Color);
+        prop.set_array_len(4);
+        text_node.add_property(prop)?;
 
         let mut layout = Layout::new(CoordinateSystem::PositiveYDown);
         layout.reset(&LayoutSettings { ..LayoutSettings::default() });
@@ -328,8 +375,6 @@ impl Stage {
         let fonts = [font];
         layout.append(&fonts, &TextStyle::new(&text, font_size, 0));
 
-        text_node.add_property_f32("height", layout.height()).unwrap();
-
         // Calculate the text width
         // std::cmp::max() not impl for f32
         let max_f32 = |x: f32, y: f32| {
@@ -347,28 +392,33 @@ impl Stage {
             total_width = max_f32(total_width, right);
         }
 
-        text_node.add_property_f32("width", total_width).unwrap();
+        let mut prop = Property::new("size", PropertyType::Float32, PropertySubType::Pixel);
+        prop.set_array_len(2);
+        prop.set_f32(0, total_width).unwrap();
+        prop.set_f32(1, layout.height()).unwrap();
 
         let text_node_id = text_node.id;
 
+        /*
         let lines = layout.lines();
         if lines.is_some() {
             for (idx, line) in lines.unwrap().into_iter().enumerate() {
                 let line_node_name = format!("line.{}", idx);
                 let line_node = scene_graph.add_node(line_node_name, SceneNodeType::LinePosition);
-                line_node.add_property_u32("idx", idx as u32).unwrap();
-                line_node.add_property_f32("baseline_y", line.baseline_y).unwrap();
-                line_node.add_property_f32("padding", line.padding).unwrap();
-                line_node.add_property_f32("max_ascent", line.max_ascent).unwrap();
-                line_node.add_property_f32("min_descent", line.min_descent).unwrap();
-                line_node.add_property_f32("max_line_gap", line.max_line_gap).unwrap();
-                line_node.add_property_u32("glyph_start", line.glyph_start as u32).unwrap();
-                line_node.add_property_u32("glyph_end", line.glyph_end as u32).unwrap();
+                //line_node.add_property_u32("idx", idx as u32).unwrap();
+                //line_node.add_property_f32("baseline_y", line.baseline_y).unwrap();
+                //line_node.add_property_f32("padding", line.padding).unwrap();
+                //line_node.add_property_f32("max_ascent", line.max_ascent).unwrap();
+                //line_node.add_property_f32("min_descent", line.min_descent).unwrap();
+                //line_node.add_property_f32("max_line_gap", line.max_line_gap).unwrap();
+                //line_node.add_property_u32("glyph_start", line.glyph_start as u32).unwrap();
+                //line_node.add_property_u32("glyph_end", line.glyph_end as u32).unwrap();
 
                 let line_node_id = line_node.id;
                 scene_graph.link(line_node_id, text_node_id)?;
             }
         }
+        */
 
         scene_graph.link(font_node_id, text_node_id)?;
         Ok(text_node_id)
@@ -387,13 +437,25 @@ impl Stage {
 
         let mut scene_graph = self.scene_graph.lock().unwrap();
         let img_node = scene_graph.add_node(node_name, SceneNodeType::RenderTexture);
-        img_node.add_property_u32("width", width).unwrap();
-        img_node.add_property_u32("height", height).unwrap();
-        img_node.add_property_u32("texture_id", id).unwrap();
+        //img_node.add_property_u32("width", width).unwrap();
+        //img_node.add_property_u32("height", height).unwrap();
+        //img_node.add_property_u32("texture_id", id).unwrap();
+
+        let mut prop = Property::new("size", PropertyType::Uint32, PropertySubType::Pixel);
+        prop.set_array_len(2);
+        prop.set_u32(0, width).unwrap();
+        prop.set_u32(1, height).unwrap();
+        img_node.add_property(prop)?;
+
+        let mut prop = Property::new("texture_id", PropertyType::Uint32, PropertySubType::Null);
+        prop.set_u32(0, id).unwrap();
+        img_node.add_property(prop)?;
+
         Ok(img_node.id)
     }
 }
 
+/*
 fn get_obj_props(obj: &SceneNode) -> Result<(f32, f32, f32, f32, bool)> {
     let x = obj.get_property_f32("x")?;
     let y = obj.get_property_f32("y")?;
@@ -413,6 +475,7 @@ fn get_text_props(render_text: &SceneNode) -> Result<(String, f32, [f32; 4])> {
     let color = [r, g, b, a];
     Ok((text, font_size, color))
 }
+*/
 
 impl EventHandler for Stage {
     fn update(&mut self) {
@@ -501,10 +564,10 @@ impl EventHandler for Stage {
         // This will make the top left (0, 0) and the bottom right (1, 1)
         // Default is (-1, 1) -> (1, -1)
         let proj = glam::Mat4::from_translation(glam::Vec3::new(-1., 1., 0.)) *
-            glam::Mat4::from_scale(glam::Vec3::new(2./screen_width, -2./screen_height, 1.));
+            glam::Mat4::from_scale(glam::Vec3::new(2. / screen_width, -2. / screen_height, 1.));
 
         // Reusable text layout
-        let mut layout = Layout::new(CoordinateSystem::PositiveYDown);
+        //let mut layout = Layout::new(CoordinateSystem::PositiveYDown);
 
         let scene_graph = self.scene_graph.lock().unwrap();
 
@@ -522,6 +585,7 @@ impl EventHandler for Stage {
             self.ctx.begin_default_pass(PassAction::Nothing);
             self.ctx.apply_pipeline(&self.pipeline);
 
+            /*
             let rect_x = layer.get_property_u32("rect_x").unwrap();
             let rect_y = layer.get_property_u32("rect_y").unwrap();
             let rect_w = layer.get_property_u32("rect_w").unwrap();
@@ -666,6 +730,7 @@ impl EventHandler for Stage {
             }
 
             self.ctx.end_render_pass();
+            */
         }
         self.ctx.commit_frame();
     }
@@ -674,14 +739,14 @@ impl EventHandler for Stage {
         let mut scene_graph = self.scene_graph.lock().unwrap();
         let win = scene_graph.lookup_node_mut("/window/input/keyboard").unwrap();
 
-        win.set_property_bool("shift", modifiers.shift).unwrap();
-        win.set_property_bool("ctrl", modifiers.ctrl).unwrap();
-        win.set_property_bool("alt", modifiers.alt).unwrap();
-        win.set_property_bool("logo", modifiers.logo).unwrap();
-        win.set_property_bool("repeat", repeat).unwrap();
+        //win.set_property_bool("shift", modifiers.shift).unwrap();
+        //win.set_property_bool("ctrl", modifiers.ctrl).unwrap();
+        //win.set_property_bool("alt", modifiers.alt).unwrap();
+        //win.set_property_bool("logo", modifiers.logo).unwrap();
+        //win.set_property_bool("repeat", repeat).unwrap();
 
         let send_key_down = |key: &str| {
-            win.set_property_str("keycode", key).unwrap();
+            //win.set_property_str("keycode", key).unwrap();
             win.trigger("key_down").unwrap();
         };
 
@@ -812,39 +877,39 @@ impl EventHandler for Stage {
     fn mouse_motion_event(&mut self, x: f32, y: f32) {
         let mut scene_graph = self.scene_graph.lock().unwrap();
         let mouse = scene_graph.lookup_node_mut("/window/input/mouse").unwrap();
-        mouse.set_property_f32("motion_x", x).unwrap();
-        mouse.set_property_f32("motion_y", y).unwrap();
+        //mouse.set_property_f32("motion_x", x).unwrap();
+        //mouse.set_property_f32("motion_y", y).unwrap();
         mouse.trigger("move").unwrap();
     }
     fn mouse_wheel_event(&mut self, x: f32, y: f32) {
         let mut scene_graph = self.scene_graph.lock().unwrap();
         let mouse = scene_graph.lookup_node_mut("/window/input/mouse").unwrap();
         //mouse.set_property_f32("x", x).unwrap();
-        mouse.set_property_f32("wheel_y", y).unwrap();
+        //mouse.set_property_f32("wheel_y", y).unwrap();
         mouse.trigger("wheel").unwrap();
     }
     fn mouse_button_down_event(&mut self, button: MouseButton, x: f32, y: f32) {
         let mut scene_graph = self.scene_graph.lock().unwrap();
         let mouse = scene_graph.lookup_node_mut("/window/input/mouse").unwrap();
-        mouse.set_property_u32("button", button.to_u8() as u32).unwrap();
-        mouse.set_property_f32("click_x", x).unwrap();
-        mouse.set_property_f32("click_y", y).unwrap();
+        //mouse.set_property_u32("button", button.to_u8() as u32).unwrap();
+        //mouse.set_property_f32("click_x", x).unwrap();
+        //mouse.set_property_f32("click_y", y).unwrap();
         mouse.trigger("button_down").unwrap();
     }
     fn mouse_button_up_event(&mut self, button: MouseButton, x: f32, y: f32) {
         let mut scene_graph = self.scene_graph.lock().unwrap();
         let mouse = scene_graph.lookup_node_mut("/window/input/mouse").unwrap();
-        mouse.set_property_u32("button", button.to_u8() as u32).unwrap();
-        mouse.set_property_f32("click_x", x).unwrap();
-        mouse.set_property_f32("click_y", y).unwrap();
+        //mouse.set_property_u32("button", button.to_u8() as u32).unwrap();
+        //mouse.set_property_f32("click_x", x).unwrap();
+        //mouse.set_property_f32("click_y", y).unwrap();
         mouse.trigger("button_up").unwrap();
     }
 
     fn resize_event(&mut self, width: f32, height: f32) {
         let mut scene_graph = self.scene_graph.lock().unwrap();
         let win = scene_graph.lookup_node_mut("/window").unwrap();
-        win.set_property_f32("width", width).unwrap();
-        win.set_property_f32("height", height).unwrap();
+        //win.set_property_f32("width", width).unwrap();
+        //win.set_property_f32("height", height).unwrap();
         win.trigger("resize").unwrap();
     }
 }

+ 6 - 2
bin/darkwallet/src/main.rs

@@ -14,6 +14,8 @@ use net::ZeroMQAdapter;
 mod scene;
 use scene::{SceneGraph, SceneGraphPtr};
 
+mod prop;
+
 mod shader;
 
 #[macro_use]
@@ -31,6 +33,8 @@ fn init_zmq(scene_graph: SceneGraphPtr) {
 
 fn main() {
     let scene_graph = Arc::new(Mutex::new(SceneGraph::new()));
-    init_zmq(scene_graph.clone());
-    init_gui(scene_graph);
+    //init_zmq(scene_graph.clone());
+    //init_gui(scene_graph);
+    let mut zmq_rpc = ZeroMQAdapter::new(scene_graph);
+    zmq_rpc.poll();
 }

+ 190 - 49
bin/darkwallet/src/net.rs

@@ -1,4 +1,4 @@
-use darkfi_serial::{deserialize, Decodable, Encodable, SerialDecodable};
+use darkfi_serial::{deserialize, Decodable, Encodable, SerialDecodable, VarInt};
 use std::{
     io::Cursor,
     sync::{atomic::Ordering, mpsc},
@@ -7,7 +7,8 @@ use std::{
 
 use crate::{
     error::{Error, Result},
-    scene::{PropertyType, PropertyValue, SceneGraphPtr, SceneNodeId, SceneNodeType, Slot, SlotId},
+    prop::{Property, PropertySubType, PropertyType, PropertyValue},
+    scene::{SceneGraphPtr, SceneNodeId, SceneNodeType, Slot, SlotId},
 };
 
 #[derive(Debug, SerialDecodable)]
@@ -26,8 +27,8 @@ enum Command {
     GetChildren = 4,
     GetParents = 5,
     GetProperties = 3,
-    GetProperty = 6,
-    SetProperty = 7,
+    GetPropertyValue = 6,
+    SetPropertyValue = 7,
     GetSignals = 14,
     RegisterSlot = 15,
     UnregisterSlot = 16,
@@ -38,6 +39,14 @@ enum Command {
     CallMethod = 22,
 }
 
+// Missing calls todo:
+// GetPropLen
+// UnsetProperty
+// SetPropertyNull
+// PropertyPushNull
+// PropertyPush
+// PropertyIsUnset
+
 pub struct ZeroMQAdapter {
     // req-reply commands
     req_socket: zmq::Socket,
@@ -156,52 +165,103 @@ impl ZeroMQAdapter {
                 debug!(target: "req", "{:?}({})", cmd, node_id);
 
                 let node = scene_graph.get_node(node_id).ok_or(Error::NodeNotFound)?;
-                let mut props = vec![];
+                //let mut props = vec![];
                 for prop in &node.props {
-                    props.push((prop.name.clone(), prop.get_type() as u8));
+                    //props.push((prop.name.clone(), prop.get_type() as u8));
                 }
-                props.encode(&mut reply).unwrap();
+                //props.encode(&mut reply).unwrap();
             }
-            Command::GetProperty => {
+            Command::GetPropertyValue => {
                 let node_id = SceneNodeId::decode(&mut cur).unwrap();
                 let prop_name = String::decode(&mut cur).unwrap();
                 debug!(target: "req", "{:?}({}, {})", cmd, node_id, prop_name);
 
                 let node = scene_graph.get_node(node_id).ok_or(Error::NodeNotFound)?;
                 let prop = node.get_property(&prop_name).ok_or(Error::PropertyNotFound)?;
-                match &prop.val {
-                    PropertyValue::Null => {
-                        0u8.encode(&mut reply).unwrap();
-                    }
-                    PropertyValue::Buffer(_) => {
-                        1u8.encode(&mut reply).unwrap();
+                prop.typ.encode(&mut reply).unwrap();
+                VarInt(prop.get_len() as u64).encode(&mut reply).unwrap();
+                match prop.typ {
+                    PropertyType::Null => {}
+                    PropertyType::Bool => {
+                        for i in 0..prop.get_len() {
+                            match prop.get_bool_opt(i).unwrap() {
+                                Some(v) => {
+                                    true.encode(&mut reply).unwrap();
+                                    v.encode(&mut reply).unwrap();
+                                }
+                                None => {
+                                    false.encode(&mut reply).unwrap();
+                                }
+                            }
+                        }
                     }
-                    PropertyValue::Bool(val) => {
-                        3u8.encode(&mut reply).unwrap();
-                        let val = val.load(Ordering::SeqCst);
-                        val.encode(&mut reply).unwrap();
+                    PropertyType::Uint32 => {
+                        for i in 0..prop.get_len() {
+                            match prop.get_u32_opt(i).unwrap() {
+                                Some(v) => {
+                                    true.encode(&mut reply).unwrap();
+                                    v.encode(&mut reply).unwrap();
+                                }
+                                None => {
+                                    false.encode(&mut reply).unwrap();
+                                }
+                            }
+                        }
                     }
-                    PropertyValue::Uint32(val) => {
-                        2u8.encode(&mut reply).unwrap();
-                        let val = val.load(Ordering::SeqCst);
-                        val.encode(&mut reply).unwrap();
+                    PropertyType::Float32 => {
+                        for i in 0..prop.get_len() {
+                            match prop.get_f32_opt(i).unwrap() {
+                                Some(v) => {
+                                    true.encode(&mut reply).unwrap();
+                                    v.encode(&mut reply).unwrap();
+                                }
+                                None => {
+                                    false.encode(&mut reply).unwrap();
+                                }
+                            }
+                        }
                     }
-                    PropertyValue::Float32(val) => {
-                        4u8.encode(&mut reply).unwrap();
-                        let val = val.load(Ordering::SeqCst);
-                        val.encode(&mut reply).unwrap();
+                    PropertyType::Str => {
+                        for i in 0..prop.get_len() {
+                            match prop.get_str_opt(i).unwrap() {
+                                Some(v) => {
+                                    true.encode(&mut reply).unwrap();
+                                    v.encode(&mut reply).unwrap();
+                                }
+                                None => {
+                                    false.encode(&mut reply).unwrap();
+                                }
+                            }
+                        }
                     }
-                    PropertyValue::Str(val) => {
-                        5u8.encode(&mut reply).unwrap();
-                        let val = val.lock().unwrap();
-                        val.encode(&mut reply).unwrap();
+                    PropertyType::Enum => {
+                        for i in 0..prop.get_len() {
+                            match prop.get_enum_opt(i).unwrap() {
+                                Some(v) => {
+                                    true.encode(&mut reply).unwrap();
+                                    v.encode(&mut reply).unwrap();
+                                }
+                                None => {
+                                    false.encode(&mut reply).unwrap();
+                                }
+                            }
+                        }
                     }
-                    PropertyValue::SceneNodeId(val) => {
-                        6u8.encode(&mut reply).unwrap();
-                        let val = val.load(Ordering::SeqCst);
-                        val.encode(&mut reply).unwrap();
+                    PropertyType::Buffer => {}
+                    PropertyType::SceneNodeId => {
+                        for i in 0..prop.get_len() {
+                            match prop.get_node_id_opt(i).unwrap() {
+                                Some(v) => {
+                                    true.encode(&mut reply).unwrap();
+                                    v.encode(&mut reply).unwrap();
+                                }
+                                None => {
+                                    false.encode(&mut reply).unwrap();
+                                }
+                            }
+                        }
                     }
-                };
+                }
             }
             Command::AddNode => {
                 let node_name = String::decode(&mut cur).unwrap();
@@ -236,10 +296,90 @@ impl ZeroMQAdapter {
                 let node_id = SceneNodeId::decode(&mut cur).unwrap();
                 let prop_name = String::decode(&mut cur).unwrap();
                 let prop_type = PropertyType::decode(&mut cur).unwrap();
-                debug!(target: "req", "{:?}({}, {}, {:?})", cmd, node_id, prop_name, prop_type);
+                let prop_subtype = PropertySubType::decode(&mut cur).unwrap();
+
+                debug!(target: "req", "{:?}({}, {}, {:?}, {:?}, ...)", cmd, node_id, prop_name, prop_type, prop_subtype);
+                let mut prop = Property::new(prop_name, prop_type, prop_subtype);
+
+                let prop_array_len = u32::decode(&mut cur).unwrap();
+                prop.set_array_len(prop_array_len as usize);
+
+                let prop_defaults_is_some = bool::decode(&mut cur).unwrap();
+                if prop_defaults_is_some {
+                    let prop_defaults_len = VarInt::decode(&mut cur).unwrap();
+                    match prop_type {
+                        PropertyType::Uint32 => {
+                            let mut prop_defaults = vec![];
+                            for _ in 0..prop_defaults_len.0 {
+                                prop_defaults.push(u32::decode(&mut cur).unwrap());
+                            }
+                            prop.set_defaults_u32(prop_defaults)?;
+                        }
+                        PropertyType::Float32 => {
+                            let mut prop_defaults = vec![];
+                            for _ in 0..prop_defaults_len.0 {
+                                prop_defaults.push(f32::decode(&mut cur).unwrap());
+                            }
+                            prop.set_defaults_f32(prop_defaults)?;
+                        }
+                        _ => return Err(Error::PropertyWrongType),
+                    }
+                }
+
+                let prop_ui_name = String::decode(&mut cur).unwrap();
+                let prop_desc = String::decode(&mut cur).unwrap();
+                let prop_is_null_allowed = bool::decode(&mut cur).unwrap();
+
+                match prop_type {
+                    PropertyType::Uint32 => {
+                        let min_is_some = bool::decode(&mut cur).unwrap();
+                        let min = if min_is_some {
+                            let min = u32::decode(&mut cur).unwrap();
+                            Some(PropertyValue::Uint32(min))
+                        } else {
+                            None
+                        };
+                        let max_is_some = bool::decode(&mut cur).unwrap();
+                        let max = if max_is_some {
+                            let max = u32::decode(&mut cur).unwrap();
+                            Some(PropertyValue::Uint32(max))
+                        } else {
+                            None
+                        };
+                        prop.min_val = min;
+                        prop.max_val = max;
+                    }
+                    PropertyType::Float32 => {
+                        let min_is_some = bool::decode(&mut cur).unwrap();
+                        let min = if min_is_some {
+                            let min = f32::decode(&mut cur).unwrap();
+                            Some(PropertyValue::Float32(min))
+                        } else {
+                            None
+                        };
+                        let max_is_some = bool::decode(&mut cur).unwrap();
+                        let max = if max_is_some {
+                            let max = f32::decode(&mut cur).unwrap();
+                            Some(PropertyValue::Float32(max))
+                        } else {
+                            None
+                        };
+                        prop.min_val = min;
+                        prop.max_val = max;
+                    }
+                    _ => return Err(Error::PropertyWrongType),
+                }
+
+                let prop_enum_items = Vec::<String>::decode(&mut cur).unwrap();
 
                 let node = scene_graph.get_node_mut(node_id).ok_or(Error::NodeNotFound)?;
-                node.add_property(prop_name, prop_type)?;
+
+                prop.set_ui_text(prop_ui_name, prop_desc);
+                prop.is_null_allowed = prop_is_null_allowed;
+                if !prop_enum_items.is_empty() {
+                    prop.set_enum_items(prop_enum_items)?;
+                }
+                node.add_property(prop)?;
             }
             Command::LinkNode => {
                 let child_id = SceneNodeId::decode(&mut cur).unwrap();
@@ -253,39 +393,40 @@ impl ZeroMQAdapter {
                 debug!(target: "req", "{:?}({}, {})", cmd, child_id, parent_id);
                 scene_graph.unlink(child_id, parent_id)?;
             }
-            Command::SetProperty => {
+            Command::SetPropertyValue => {
                 let node_id = SceneNodeId::decode(&mut cur).unwrap();
                 let prop_name = String::decode(&mut cur).unwrap();
+                let prop_i = u32::decode(&mut cur).unwrap() as usize;
                 debug!(target: "req", "{:?}({}, {})", cmd, node_id, prop_name);
 
                 let node = scene_graph.get_node_mut(node_id).ok_or(Error::NodeNotFound)?;
                 let prop = node.get_property(&prop_name).ok_or(Error::PropertyNotFound)?;
 
-                match prop.get_type() {
+                match prop.typ {
                     PropertyType::Null => {}
-                    PropertyType::Buffer => {
-                        let val = Vec::<u8>::decode(&mut cur).unwrap();
-                        prop.set_buf(val)?;
-                    }
                     PropertyType::Bool => {
                         let val = bool::decode(&mut cur).unwrap();
-                        prop.set_bool(val)?;
+                        prop.set_bool(prop_i, val)?;
                     }
                     PropertyType::Uint32 => {
                         let val = u32::decode(&mut cur).unwrap();
-                        prop.set_u32(val)?;
+                        prop.set_u32(prop_i, val)?;
                     }
                     PropertyType::Float32 => {
                         let val = f32::decode(&mut cur).unwrap();
-                        prop.set_f32(val)?;
+                        prop.set_f32(prop_i, val)?;
                     }
-                    PropertyType::Str => {
+                    PropertyType::Str | PropertyType::Enum => {
                         let val = String::decode(&mut cur).unwrap();
-                        prop.set_str(val)?;
+                        prop.set_str(prop_i, val)?;
+                    }
+                    PropertyType::Buffer => {
+                        let val = Vec::<u8>::decode(&mut cur).unwrap();
+                        prop.set_buf(prop_i, val)?;
                     }
                     PropertyType::SceneNodeId => {
                         let val = SceneNodeId::decode(&mut cur).unwrap();
-                        prop.set_node_id(val)?;
+                        prop.set_node_id(prop_i, val)?;
                     }
                 }
             }

+ 574 - 0
bin/darkwallet/src/prop.rs

@@ -0,0 +1,574 @@
+use crate::error::{Error, Result};
+use atomic_float::AtomicF32;
+use darkfi_serial::{SerialDecodable, SerialEncodable};
+use std::{
+    fmt,
+    str::FromStr,
+    sync::{
+        atomic::{AtomicBool, AtomicU32, Ordering},
+        Arc, Mutex, MutexGuard,
+    },
+};
+
+use crate::scene::SceneNodeId;
+
+type BufferGuard<'a> = MutexGuard<'a, Vec<u8>>;
+
+type Buffer = Arc<Vec<u8>>;
+
+#[derive(Debug, Copy, Clone, PartialEq, SerialEncodable, SerialDecodable)]
+#[repr(u8)]
+pub enum PropertyType {
+    Null = 0,
+    Bool = 1,
+    Uint32 = 2,
+    Float32 = 3,
+    Str = 4,
+    Enum = 5,
+    Buffer = 6,
+    SceneNodeId = 7,
+}
+
+impl PropertyType {
+    fn default_value(&self) -> PropertyValue {
+        match self {
+            Self::Null => PropertyValue::Null,
+            Self::Bool => PropertyValue::Bool(false),
+            Self::Uint32 => PropertyValue::Uint32(0),
+            Self::Float32 => PropertyValue::Float32(0.),
+            Self::Str => PropertyValue::Str(String::new()),
+            Self::Enum => PropertyValue::Enum(String::new()),
+            Self::Buffer => PropertyValue::Buffer(Arc::new(vec![])),
+            Self::SceneNodeId => PropertyValue::SceneNodeId(0),
+        }
+    }
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, SerialEncodable, SerialDecodable)]
+#[repr(u8)]
+pub enum PropertySubType {
+    Null = 0,
+    Color = 1,
+    // Size of something in pixels
+    Pixel = 2,
+}
+
+#[derive(Debug, Clone)]
+pub enum PropertyValue {
+    Unset,
+    Null,
+    Bool(bool),
+    Uint32(u32),
+    Float32(f32),
+    Str(String),
+    Enum(String),
+    Buffer(Arc<Vec<u8>>),
+    SceneNodeId(SceneNodeId),
+}
+
+impl PropertyValue {
+    fn as_type(&self) -> PropertyType {
+        match self {
+            Self::Unset => todo!("not sure"),
+            Self::Null => PropertyType::Null,
+            Self::Bool(_) => PropertyType::Bool,
+            Self::Uint32(_) => PropertyType::Uint32,
+            Self::Float32(_) => PropertyType::Float32,
+            Self::Str(_) => PropertyType::Str,
+            Self::Enum(_) => PropertyType::Enum,
+            Self::Buffer(_) => PropertyType::Buffer,
+            Self::SceneNodeId(_) => PropertyType::SceneNodeId,
+        }
+    }
+
+    fn is_unset(&self) -> bool {
+        match self {
+            Self::Unset => true,
+            _ => false,
+        }
+    }
+
+    fn is_null(&self) -> bool {
+        match self {
+            Self::Null => true,
+            _ => false,
+        }
+    }
+
+    fn as_bool(&self) -> Result<bool> {
+        match self {
+            Self::Bool(v) => Ok(*v),
+            _ => Err(Error::PropertyWrongType),
+        }
+    }
+    fn as_u32(&self) -> Result<u32> {
+        match self {
+            Self::Uint32(v) => Ok(*v),
+            _ => Err(Error::PropertyWrongType),
+        }
+    }
+    fn as_f32(&self) -> Result<f32> {
+        match self {
+            Self::Float32(v) => Ok(*v),
+            _ => Err(Error::PropertyWrongType),
+        }
+    }
+    fn as_str(&self) -> Result<String> {
+        match self {
+            Self::Str(v) => Ok(v.clone()),
+            _ => Err(Error::PropertyWrongType),
+        }
+    }
+    fn as_enum(&self) -> Result<String> {
+        match self {
+            Self::Enum(v) => Ok(v.clone()),
+            _ => Err(Error::PropertyWrongType),
+        }
+    }
+    fn as_buf(&self) -> Result<Buffer> {
+        match self {
+            Self::Buffer(v) => Ok(v.clone()),
+            _ => Err(Error::PropertyWrongType),
+        }
+    }
+    fn as_node_id(&self) -> Result<SceneNodeId> {
+        match self {
+            Self::SceneNodeId(v) => Ok(*v),
+            _ => Err(Error::PropertyWrongType),
+        }
+    }
+}
+
+pub struct Property {
+    pub name: String,
+    pub typ: PropertyType,
+    pub subtype: PropertySubType,
+    pub defaults: Vec<PropertyValue>,
+    pub vals: Mutex<Vec<PropertyValue>>,
+
+    pub ui_name: String,
+    pub desc: String,
+
+    pub is_null_allowed: bool,
+    // Use 0 for unbounded length
+    pub array_len: usize,
+    pub min_val: Option<PropertyValue>,
+    pub max_val: Option<PropertyValue>,
+
+    // PropertyType must be Enum
+    pub enum_items: Option<Vec<String>>,
+}
+
+impl Property {
+    pub fn new<S: Into<String>>(name: S, typ: PropertyType, subtype: PropertySubType) -> Self {
+        Self {
+            name: name.into(),
+            typ,
+            subtype,
+
+            defaults: vec![typ.default_value()],
+            vals: Mutex::new(vec![PropertyValue::Unset]),
+
+            ui_name: String::new(),
+            desc: String::new(),
+
+            is_null_allowed: false,
+            array_len: 1,
+            min_val: None,
+            max_val: None,
+            enum_items: None,
+        }
+    }
+
+    pub fn set_ui_text<S: Into<String>>(&mut self, ui_name: S, desc: S) {
+        self.ui_name = ui_name.into();
+        self.desc = desc.into();
+    }
+
+    pub fn set_array_len(&mut self, len: usize) {
+        self.array_len = len;
+        self.defaults.resize(len, self.typ.default_value());
+        self.vals.lock().unwrap().resize(len, PropertyValue::Unset);
+    }
+    pub fn set_unbounded(&mut self) {
+        self.set_array_len(0);
+    }
+
+    pub fn set_range_u32(&mut self, min: u32, max: u32) {
+        self.min_val = Some(PropertyValue::Uint32(min));
+        self.max_val = Some(PropertyValue::Uint32(max));
+    }
+    pub fn set_range_f32(&mut self, min: f32, max: f32) {
+        self.min_val = Some(PropertyValue::Float32(min));
+        self.max_val = Some(PropertyValue::Float32(max));
+    }
+
+    pub fn set_enum_items<S: Into<String>>(&mut self, enum_items: Vec<S>) -> Result<()> {
+        if self.typ != PropertyType::Enum {
+            return Err(Error::PropertyWrongType)
+        }
+        self.enum_items = Some(enum_items.into_iter().map(|item| item.into()).collect());
+        Ok(())
+    }
+
+    pub fn allow_null_values(&mut self) {
+        self.is_null_allowed = true;
+    }
+
+    fn check_defaults_len(&self, defaults_len: usize) -> Result<()> {
+        if !self.is_bounded() || defaults_len != self.array_len {
+            return Err(Error::PropertyWrongLen)
+        }
+        Ok(())
+    }
+    pub fn set_defaults_u32(&mut self, defaults: Vec<u32>) -> Result<()> {
+        self.check_defaults_len(defaults.len())?;
+        self.defaults = defaults.into_iter().map(|v| PropertyValue::Uint32(v)).collect();
+        Ok(())
+    }
+    pub fn set_defaults_f32(&mut self, defaults: Vec<f32>) -> Result<()> {
+        self.check_defaults_len(defaults.len())?;
+        self.defaults = defaults.into_iter().map(|v| PropertyValue::Float32(v)).collect();
+        Ok(())
+    }
+
+    pub fn get_len(&self) -> usize {
+        // Avoid locking unless we need to
+        // If array len is nonzero, then vals len should be the same.
+        if !self.is_bounded() {
+            return self.vals.lock().unwrap().len()
+        }
+        self.array_len
+    }
+
+    fn is_bounded(&self) -> bool {
+        self.array_len != 0
+    }
+
+    /// This will clear all values, resetting them to the default
+    pub fn clear_values(&self) {
+        let vals = &mut self.vals.lock().unwrap();
+        vals.clear();
+        vals.resize(self.array_len, PropertyValue::Unset);
+    }
+
+    // Set
+
+    fn set_raw_value(&self, i: usize, val: PropertyValue) -> Result<()> {
+        if self.typ != val.as_type() {
+            return Err(Error::PropertyWrongType)
+        }
+
+        let vals = &mut self.vals.lock().unwrap();
+        if i >= vals.len() {
+            return Err(Error::PropertyWrongIndex)
+        }
+        vals[i] = val;
+        Ok(())
+    }
+
+    pub fn unset(&self, i: usize) -> Result<()> {
+        let vals = &mut self.vals.lock().unwrap();
+        if i >= vals.len() {
+            return Err(Error::PropertyWrongIndex)
+        }
+        vals[i] = PropertyValue::Unset;
+        Ok(())
+    }
+
+    pub fn set_null(&self, i: usize) -> Result<()> {
+        if !self.is_null_allowed {
+            return Err(Error::PropertyNullNotAllowed)
+        }
+        let vals = &mut self.vals.lock().unwrap();
+        if i >= vals.len() {
+            return Err(Error::PropertyWrongIndex)
+        }
+        vals[i] = PropertyValue::Null;
+        Ok(())
+    }
+
+    pub fn set_bool(&self, i: usize, val: bool) -> Result<()> {
+        self.set_raw_value(i, PropertyValue::Bool(val))
+    }
+    pub fn set_u32(&self, i: usize, val: u32) -> Result<()> {
+        if self.min_val.is_some() {
+            let min = self.min_val.as_ref().unwrap().as_u32()?;
+            if val < min {
+                return Err(Error::PropertyOutOfRange);
+            }
+        }
+        if self.max_val.is_some() {
+            let max = self.max_val.as_ref().unwrap().as_u32()?;
+            if val > max {
+                return Err(Error::PropertyOutOfRange);
+            }
+        }
+        self.set_raw_value(i, PropertyValue::Uint32(val))
+    }
+    pub fn set_f32(&self, i: usize, val: f32) -> Result<()> {
+        if self.min_val.is_some() {
+            let min = self.min_val.as_ref().unwrap().as_f32()?;
+            if val < min {
+                return Err(Error::PropertyOutOfRange);
+            }
+        }
+        if self.max_val.is_some() {
+            let max = self.max_val.as_ref().unwrap().as_f32()?;
+            if val > max {
+                return Err(Error::PropertyOutOfRange);
+            }
+        }
+        self.set_raw_value(i, PropertyValue::Float32(val))
+    }
+    pub fn set_str<S: Into<String>>(&self, i: usize, val: S) -> Result<()> {
+        self.set_raw_value(i, PropertyValue::Str(val.into()))
+    }
+    pub fn set_enum<S: Into<String>>(&self, i: usize, val: S) -> Result<()> {
+        if self.typ != PropertyType::Enum {
+            return Err(Error::PropertyWrongType)
+        }
+        let val = val.into();
+        if !self.enum_items.as_ref().unwrap().contains(&val) {
+            return Err(Error::PropertyWrongEnumItem)
+        }
+        self.set_raw_value(i, PropertyValue::Enum(val.into()))
+    }
+    pub fn set_buf(&self, i: usize, val: Vec<u8>) -> Result<()> {
+        self.set_raw_value(i, PropertyValue::Buffer(Arc::new(val)))
+    }
+    pub fn set_node_id(&self, i: usize, val: SceneNodeId) -> Result<()> {
+        self.set_raw_value(i, PropertyValue::SceneNodeId(val))
+    }
+
+    // Push
+
+    pub fn push_null(&self) -> Result<usize> {
+        if self.is_bounded() {
+            return Err(Error::PropertyIsBounded)
+        }
+        let vals = &mut self.vals.lock().unwrap();
+        let i = vals.len();
+        vals.push(PropertyValue::Null);
+        Ok(i)
+    }
+
+    pub fn push_bool(&self, val: bool) -> Result<usize> {
+        let i = self.push_null()?;
+        self.set_bool(i, val)?;
+        Ok(i)
+    }
+    pub fn push_u32(&self, val: u32) -> Result<usize> {
+        let i = self.push_null()?;
+        self.set_u32(i, val)?;
+        Ok(i)
+    }
+    pub fn push_f32(&self, val: f32) -> Result<usize> {
+        let i = self.push_null()?;
+        self.set_f32(i, val)?;
+        Ok(i)
+    }
+    pub fn push_str<S: Into<String>>(&self, val: S) -> Result<usize> {
+        let i = self.push_null()?;
+        self.set_str(i, val)?;
+        Ok(i)
+    }
+    pub fn push_enum<S: Into<String>>(&self, val: S) -> Result<usize> {
+        let i = self.push_null()?;
+        self.set_enum(i, val)?;
+        Ok(i)
+    }
+    pub fn push_buf(&self, val: Vec<u8>) -> Result<usize> {
+        let i = self.push_null()?;
+        self.set_buf(i, val)?;
+        Ok(i)
+    }
+    pub fn push_node_id(&self, val: SceneNodeId) -> Result<usize> {
+        let i = self.push_null()?;
+        self.set_node_id(i, val)?;
+        Ok(i)
+    }
+
+    // Get
+
+    pub fn is_unset(&self, i: usize) -> Result<bool> {
+        let val = self.get_raw_value(i)?;
+        Ok(val.is_unset())
+    }
+
+    pub fn get_raw_value(&self, i: usize) -> Result<PropertyValue> {
+        let vals = &self.vals.lock().unwrap();
+        if self.is_bounded() {
+            assert_eq!(vals.len(), self.array_len);
+        }
+        if i >= vals.len() {
+            return Err(Error::PropertyWrongIndex)
+        }
+        Ok(vals[i].clone())
+    }
+
+    pub fn get_value(&self, i: usize) -> Result<PropertyValue> {
+        let val = self.get_raw_value(i)?;
+        if val.is_unset() {
+            return Ok(self.defaults[i].clone())
+        }
+        Ok(val)
+    }
+
+    pub fn get_bool(&self, i: usize) -> Result<bool> {
+        self.get_value(i)?.as_bool()
+    }
+    pub fn get_bool_opt(&self, i: usize) -> Result<Option<bool>> {
+        let val = self.get_value(i)?;
+        if val.is_null() {
+            return Ok(None)
+        }
+        Ok(Some(val.as_bool()?))
+    }
+    pub fn get_u32(&self, i: usize) -> Result<u32> {
+        self.get_value(i)?.as_u32()
+    }
+    pub fn get_u32_opt(&self, i: usize) -> Result<Option<u32>> {
+        let val = self.get_value(i)?;
+        if val.is_null() {
+            return Ok(None)
+        }
+        Ok(Some(val.as_u32()?))
+    }
+    pub fn get_f32(&self, i: usize) -> Result<f32> {
+        self.get_value(i)?.as_f32()
+    }
+    pub fn get_f32_opt(&self, i: usize) -> Result<Option<f32>> {
+        let val = self.get_value(i)?;
+        if val.is_null() {
+            return Ok(None)
+        }
+        Ok(Some(val.as_f32()?))
+    }
+    pub fn get_str(&self, i: usize) -> Result<String> {
+        self.get_value(i)?.as_str()
+    }
+    pub fn get_str_opt(&self, i: usize) -> Result<Option<String>> {
+        let val = self.get_value(i)?;
+        if val.is_null() {
+            return Ok(None)
+        }
+        Ok(Some(val.as_str()?))
+    }
+    pub fn get_enum(&self, i: usize) -> Result<String> {
+        self.get_value(i)?.as_enum()
+    }
+    pub fn get_enum_opt(&self, i: usize) -> Result<Option<String>> {
+        let val = self.get_value(i)?;
+        if val.is_null() {
+            return Ok(None)
+        }
+        Ok(Some(val.as_enum()?))
+    }
+    pub fn get_buf(&self, i: usize) -> Result<Buffer> {
+        self.get_value(i)?.as_buf()
+    }
+    pub fn get_buf_opt(&self, i: usize) -> Result<Option<Buffer>> {
+        let val = self.get_value(i)?;
+        if val.is_null() {
+            return Ok(None)
+        }
+        Ok(Some(val.as_buf()?))
+    }
+    pub fn get_node_id(&self, i: usize) -> Result<SceneNodeId> {
+        self.get_value(i)?.as_node_id()
+    }
+    pub fn get_node_id_opt(&self, i: usize) -> Result<Option<SceneNodeId>> {
+        let val = self.get_value(i)?;
+        if val.is_null() {
+            return Ok(None)
+        }
+        Ok(Some(val.as_node_id()?))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_getset() {
+        let mut prop = Property::new("foo", PropertyType::Float32, PropertySubType::Null);
+        assert!(prop.set_f32(1, 4.).is_err());
+        assert!(prop.is_unset(0).unwrap());
+        assert!(prop.set_f32(0, 4.).is_ok());
+        assert_eq!(prop.get_f32(0).unwrap(), 4.);
+        assert!(!prop.is_unset(0).unwrap());
+        prop.unset(0).unwrap();
+        assert!(prop.is_unset(0).unwrap());
+        assert_eq!(prop.get_f32(0).unwrap(), 0.);
+    }
+
+    #[test]
+    fn test_nullable() {
+        // default len is 1
+        let mut prop = Property::new("foo", PropertyType::Float32, PropertySubType::Null);
+        assert!(prop.set_defaults_f32(vec![1.0, 0.0]).is_err());
+        assert!(prop.set_defaults_f32(vec![2.0]).is_ok());
+        prop.allow_null_values();
+        prop.set_null(0).unwrap();
+
+        assert!(prop.get_f32_opt(1).is_err());
+        assert!(prop.get_f32_opt(0).is_ok());
+        assert!(prop.get_f32_opt(0).unwrap().is_none());
+
+        prop.clear_values();
+        assert!(prop.get_f32(0).is_ok());
+        assert!(prop.get_f32_opt(0).unwrap().is_some());
+        assert_eq!(prop.get_f32(0).unwrap(), 2.0);
+    }
+
+    #[test]
+    fn test_nonnullable() {
+        let mut prop = Property::new("foo", PropertyType::Float32, PropertySubType::Null);
+        assert!(prop.set_null(0).is_err());
+        assert!(prop.is_unset(0).unwrap());
+    }
+
+    #[test]
+    fn test_unbounded() {
+        let mut prop = Property::new("foo", PropertyType::Float32, PropertySubType::Null);
+        prop.set_unbounded();
+        assert_eq!(prop.get_len(), 0);
+        prop.push_f32(2.0).unwrap();
+        prop.push_f32(3.0).unwrap();
+        assert_eq!(prop.get_len(), 2);
+
+        prop.clear_values();
+        assert_eq!(prop.get_len(), 0);
+        prop.allow_null_values();
+        prop.push_null().unwrap();
+        prop.push_f32(4.0).unwrap();
+        prop.push_f32(5.0).unwrap();
+        assert_eq!(prop.get_len(), 3);
+        assert!(prop.get_f32_opt(0).unwrap().is_none());
+        assert!(prop.get_f32_opt(1).unwrap().is_some());
+        assert!(prop.get_f32_opt(2).unwrap().is_some());
+        assert!(prop.get_f32_opt(3).is_err());
+
+        let mut prop2 = Property::new("foo", PropertyType::Float32, PropertySubType::Null);
+        assert!(prop2.push_f32(4.0).is_err());
+    }
+
+    #[test]
+    fn test_range() {
+        let mut prop = Property::new("foo", PropertyType::Float32, PropertySubType::Null);
+        let half_pi = 3.1415926535 / 2.;
+        prop.set_range_f32(-half_pi, half_pi);
+        assert!(prop.set_f32(0, 6.).is_err());
+        assert!(prop.set_f32(0, 1.).is_ok());
+    }
+
+    #[test]
+    fn test_enum() {
+        let mut prop = Property::new("foo", PropertyType::Enum, PropertySubType::Null);
+        prop.set_enum_items(vec!["ABC", "XYZ", "FOO"]).unwrap();
+        assert!(prop.set_enum(0, "ABC").is_ok());
+        assert!(prop.set_enum(0, "BAR").is_err());
+    }
+}

+ 65 - 227
bin/darkwallet/src/scene.rs

@@ -1,4 +1,3 @@
-use crate::error::{Error, Result};
 use atomic_float::AtomicF32;
 use darkfi_serial::{SerialDecodable, SerialEncodable};
 use std::{
@@ -10,6 +9,11 @@ use std::{
     },
 };
 
+use crate::{
+    error::{Error, Result},
+    prop::{Property, PropertyType},
+};
+
 pub struct ScenePath(Vec<String>);
 
 impl<S: Into<String>> From<S> for ScenePath {
@@ -397,43 +401,11 @@ impl SceneNode {
             .map(|child_inf| scene_graph.get_node(child_inf.id).unwrap())
     }
 
-    pub fn add_property<S: Into<String>>(
-        &mut self,
-        name: S,
-        typ: PropertyType,
-    ) -> Result<Arc<Property>> {
-        let name = name.into();
-        if self.has_property(&name) {
+    pub fn add_property(&mut self, prop: Property) -> Result<()> {
+        if self.has_property(&prop.name) {
             return Err(Error::PropertyAlreadyExists);
         }
-        let prop = Property::new(name, typ);
-        self.props.push(prop.clone());
-        Ok(prop)
-    }
-
-    // Convenience methods
-    pub fn add_property_buf(&mut self, name: &str, val: Vec<u8>) -> Result<()> {
-        self.add_property(name, PropertyType::Buffer)?.set_buf(val)?;
-        Ok(())
-    }
-    pub fn add_property_bool(&mut self, name: &str, val: bool) -> Result<()> {
-        self.add_property(name, PropertyType::Bool)?.set_bool(val)?;
-        Ok(())
-    }
-    pub fn add_property_u32(&mut self, name: &str, val: u32) -> Result<()> {
-        self.add_property(name, PropertyType::Uint32)?.set_u32(val)?;
-        Ok(())
-    }
-    pub fn add_property_f32(&mut self, name: &str, val: f32) -> Result<()> {
-        self.add_property(name, PropertyType::Float32)?.set_f32(val)?;
-        Ok(())
-    }
-    pub fn add_property_str<S: Into<String>>(&mut self, name: &str, val: S) -> Result<()> {
-        self.add_property(name, PropertyType::Str)?.set_str(val)?;
-        Ok(())
-    }
-    pub fn add_property_node_id(&mut self, name: &str, val: SceneNodeId) -> Result<()> {
-        self.add_property(name, PropertyType::SceneNodeId)?.set_node_id(val)?;
+        self.props.push(Arc::new(prop));
         Ok(())
     }
 
@@ -447,43 +419,51 @@ impl SceneNode {
 
     // Convenience methods
     pub fn get_property_bool(&self, name: &str) -> Result<bool> {
-        self.get_property(name).ok_or(Error::PropertyNotFound)?.get_bool()
+        self.get_property(name).ok_or(Error::PropertyNotFound)?.get_bool(0)
     }
     pub fn get_property_u32(&self, name: &str) -> Result<u32> {
-        self.get_property(name).ok_or(Error::PropertyNotFound)?.get_u32()
+        self.get_property(name).ok_or(Error::PropertyNotFound)?.get_u32(0)
     }
     pub fn get_property_f32(&self, name: &str) -> Result<f32> {
-        self.get_property(name).ok_or(Error::PropertyNotFound)?.get_f32()
+        self.get_property(name).ok_or(Error::PropertyNotFound)?.get_f32(0)
     }
     pub fn get_property_str(&self, name: &str) -> Result<String> {
-        self.get_property(name).ok_or(Error::PropertyNotFound)?.get_str()
+        self.get_property(name).ok_or(Error::PropertyNotFound)?.get_str(0)
     }
     pub fn get_property_node_id(&self, name: &str) -> Result<SceneNodeId> {
-        self.get_property(name).ok_or(Error::PropertyNotFound)?.get_node_id()
-    }
-    // Setters
-    pub fn set_property_bool(&self, name: &str, val: bool) -> Result<()> {
-        self.get_property(name).ok_or(Error::PropertyNotFound)?.set_bool(val)
-    }
-    pub fn set_property_u32(&self, name: &str, val: u32) -> Result<()> {
-        self.get_property(name).ok_or(Error::PropertyNotFound)?.set_u32(val)
-    }
-    pub fn set_property_f32(&self, name: &str, val: f32) -> Result<()> {
-        self.get_property(name).ok_or(Error::PropertyNotFound)?.set_f32(val)
-    }
-    pub fn set_property_str<S: Into<String>>(&self, name: &str, val: S) -> Result<()> {
-        self.get_property(name).ok_or(Error::PropertyNotFound)?.set_str(val)
-    }
-    pub fn set_property_node_id(&self, name: &str, val: SceneNodeId) -> Result<()> {
-        self.get_property(name).ok_or(Error::PropertyNotFound)?.set_node_id(val)
-    }
-
-    pub fn add_signal<S: Into<String>>(&mut self, name: S) -> Result<()> {
+        self.get_property(name).ok_or(Error::PropertyNotFound)?.get_node_id(0)
+    }
+    //// Setters
+    //pub fn set_property_bool(&self, name: &str, val: bool) -> Result<()> {
+    //    self.get_property(name).ok_or(Error::PropertyNotFound)?.set_bool(val)
+    //}
+    //pub fn set_property_u32(&self, name: &str, val: u32) -> Result<()> {
+    //    self.get_property(name).ok_or(Error::PropertyNotFound)?.set_u32(val)
+    //}
+    //pub fn set_property_f32(&self, name: &str, val: f32) -> Result<()> {
+    //    self.get_property(name).ok_or(Error::PropertyNotFound)?.set_f32(val)
+    //}
+    //pub fn set_property_str<S: Into<String>>(&self, name: &str, val: S) -> Result<()> {
+    //    self.get_property(name).ok_or(Error::PropertyNotFound)?.set_str(val)
+    //}
+    //pub fn set_property_node_id(&self, name: &str, val: SceneNodeId) -> Result<()> {
+    //    self.get_property(name).ok_or(Error::PropertyNotFound)?.set_node_id(val)
+    //}
+
+    pub fn add_signal<S: Into<String>>(
+        &mut self,
+        name: S,
+        fmt: Vec<(S, S, PropertyType)>,
+    ) -> Result<()> {
         let name = name.into();
         if self.has_signal(&name) {
             return Err(Error::SignalAlreadyExists);
         }
-        self.sigs.push(Signal { name: name.into(), slots: vec![], freed: vec![] });
+        let fmt = fmt
+            .into_iter()
+            .map(|(n, d, t)| CallArg { name: n.into(), desc: d.into(), typ: t })
+            .collect();
+        self.sigs.push(Signal { name: name.into(), fmt, slots: vec![], freed: vec![] });
         Ok(())
     }
 
@@ -530,12 +510,23 @@ impl SceneNode {
     pub fn add_method<S: Into<String>>(
         &mut self,
         name: S,
-        args: Vec<(S, PropertyType)>,
-        result: Vec<(S, PropertyType)>,
-    ) {
-        let args = args.into_iter().map(|(s, p)| (s.into(), p)).collect();
-        let result = result.into_iter().map(|(s, p)| (s.into(), p)).collect();
-        self.methods.push(Method { name: name.into(), args, result, queue: vec![] })
+        args: Vec<(S, S, PropertyType)>,
+        result: Vec<(S, S, PropertyType)>,
+    ) -> Result<()> {
+        let name = name.into();
+        if self.has_signal(&name) {
+            return Err(Error::MethodAlreadyExists);
+        }
+        let args = args
+            .into_iter()
+            .map(|(n, d, t)| CallArg { name: n.into(), desc: d.into(), typ: t })
+            .collect();
+        let result = result
+            .into_iter()
+            .map(|(n, d, t)| CallArg { name: n.into(), desc: d.into(), typ: t })
+            .collect();
+        self.methods.push(Method { name: name.into(), args, result, queue: vec![] });
+        Ok(())
     }
 
     pub fn get_method(&self, name: &str) -> Option<&Method> {
@@ -557,165 +548,11 @@ impl SceneNode {
     }
 }
 
-type BufferGuard<'a> = MutexGuard<'a, Vec<u8>>;
-
-#[derive(Debug, Copy, Clone, PartialEq, SerialEncodable, SerialDecodable)]
-#[repr(u8)]
-pub enum PropertyType {
-    Null = 0,
-    Buffer = 1,
-    Bool = 2,
-    Uint32 = 3,
-    Float32 = 4,
-    Str = 5,
-    SceneNodeId = 6,
-}
-
-pub enum PropertyValue {
-    Null,
-    Buffer(Mutex<Vec<u8>>),
-    Bool(AtomicBool),
-    Uint32(AtomicU32),
-    Float32(AtomicF32),
-    Str(Mutex<String>),
-    SceneNodeId(AtomicU32),
-}
-
-pub struct Property {
+#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
+pub struct CallArg {
     pub name: String,
-    //typ: PropertyType,
-    pub val: PropertyValue,
-}
-
-impl Property {
-    fn new(name: String, typ: PropertyType) -> Arc<Self> {
-        let val = match typ {
-            PropertyType::Null => PropertyValue::Null,
-            PropertyType::Buffer => PropertyValue::Buffer(Mutex::new(Vec::new())),
-            PropertyType::Bool => PropertyValue::Bool(AtomicBool::new(false)),
-            PropertyType::Uint32 => PropertyValue::Uint32(AtomicU32::new(0)),
-            PropertyType::Float32 => PropertyValue::Float32(AtomicF32::new(0.)),
-            PropertyType::Str => PropertyValue::Str(Mutex::new(String::new())),
-            PropertyType::SceneNodeId => PropertyValue::SceneNodeId(AtomicU32::new(0)),
-        };
-        Arc::new(Self { name, val })
-    }
-
-    pub fn get_type(&self) -> PropertyType {
-        match self.val {
-            PropertyValue::Null => PropertyType::Null,
-            PropertyValue::Buffer(_) => PropertyType::Buffer,
-            PropertyValue::Bool(_) => PropertyType::Bool,
-            PropertyValue::Uint32(_) => PropertyType::Uint32,
-            PropertyValue::Float32(_) => PropertyType::Float32,
-            PropertyValue::Str(_) => PropertyType::Str,
-            PropertyValue::SceneNodeId(_) => PropertyType::SceneNodeId,
-        }
-    }
-
-    pub fn get_buf<'a>(&'a self) -> Result<BufferGuard<'a>> {
-        match &self.val {
-            PropertyValue::Buffer(propval) => Ok(propval.lock().unwrap()),
-            _ => Err(Error::PropertyWrongType),
-        }
-    }
-
-    pub fn get_bool(&self) -> Result<bool> {
-        match &self.val {
-            PropertyValue::Bool(propval) => Ok(propval.load(Ordering::SeqCst)),
-            _ => Err(Error::PropertyWrongType),
-        }
-    }
-
-    pub fn get_u32(&self) -> Result<u32> {
-        match &self.val {
-            PropertyValue::Uint32(propval) => Ok(propval.load(Ordering::SeqCst)),
-            _ => Err(Error::PropertyWrongType),
-        }
-    }
-
-    pub fn get_f32(&self) -> Result<f32> {
-        match &self.val {
-            PropertyValue::Float32(propval) => Ok(propval.load(Ordering::SeqCst)),
-            _ => Err(Error::PropertyWrongType),
-        }
-    }
-
-    pub fn get_str(&self) -> Result<String> {
-        match &self.val {
-            PropertyValue::Str(propval) => Ok(propval.lock().unwrap().clone()),
-            _ => Err(Error::PropertyWrongType),
-        }
-    }
-
-    pub fn get_node_id(&self) -> Result<SceneNodeId> {
-        match &self.val {
-            PropertyValue::SceneNodeId(propval) => Ok(propval.load(Ordering::SeqCst)),
-            _ => Err(Error::PropertyWrongType),
-        }
-    }
-
-    pub fn set_buf(&self, val: Vec<u8>) -> Result<()> {
-        match &self.val {
-            PropertyValue::Buffer(propval) => {
-                let mut buf = propval.lock().unwrap();
-                let _ = std::mem::replace(&mut *buf, val);
-                Ok(())
-            }
-            _ => Err(Error::PropertyWrongType),
-        }
-    }
-
-    pub fn set_bool(&self, val: bool) -> Result<()> {
-        match &self.val {
-            PropertyValue::Bool(propval) => {
-                propval.store(val, Ordering::SeqCst);
-                Ok(())
-            }
-            _ => Err(Error::PropertyWrongType),
-        }
-    }
-
-    pub fn set_u32(&self, val: u32) -> Result<()> {
-        match &self.val {
-            PropertyValue::Uint32(propval) => {
-                propval.store(val, Ordering::SeqCst);
-                Ok(())
-            }
-            _ => Err(Error::PropertyWrongType),
-        }
-    }
-
-    pub fn set_f32(&self, val: f32) -> Result<()> {
-        match &self.val {
-            PropertyValue::Float32(propval) => {
-                propval.store(val, Ordering::SeqCst);
-                Ok(())
-            }
-            _ => Err(Error::PropertyWrongType),
-        }
-    }
-
-    pub fn set_str<S: Into<String>>(&self, val: S) -> Result<()> {
-        match &self.val {
-            PropertyValue::Str(propval) => {
-                let mut pv = propval.lock().unwrap();
-                *pv = val.into();
-                Ok(())
-            }
-            _ => Err(Error::PropertyWrongType),
-        }
-    }
-
-    pub fn set_node_id(&self, val: SceneNodeId) -> Result<()> {
-        match &self.val {
-            PropertyValue::SceneNodeId(propval) => {
-                propval.store(val, Ordering::SeqCst);
-                Ok(())
-            }
-            _ => Err(Error::PropertyWrongType),
-        }
-    }
+    pub desc: String,
+    pub typ: PropertyType,
 }
 
 type SlotFn = Box<dyn Fn() + Send>;
@@ -734,6 +571,7 @@ impl Slot {
 
 pub struct Signal {
     pub name: String,
+    pub fmt: Vec<CallArg>,
     slots: Vec<Slot>,
     freed: Vec<SlotId>,
 }
@@ -768,7 +606,7 @@ type MethodResponseFn = Box<dyn Fn(Result<Vec<u8>>) + Send>;
 
 pub struct Method {
     pub name: String,
-    pub args: Vec<(String, PropertyType)>,
-    pub result: Vec<(String, PropertyType)>,
+    pub args: Vec<CallArg>,
+    pub result: Vec<CallArg>,
     pub queue: Vec<(Vec<u8>, MethodResponseFn)>,
 }