Просмотр исходного кода

wallet: add Property wrapper types, and begin prelim selection code

darkfi 2 лет назад
Родитель
Сommit
d0303017a0

+ 37 - 6
bin/darkwallet/gui/__init__.py

@@ -507,6 +507,15 @@ def draw():
 
 
     node_id = api.add_node("editz", SceneNodeType.EDIT_BOX)
     node_id = api.add_node("editz", SceneNodeType.EDIT_BOX)
 
 
+    prop = Property(
+        "is_active", PropertyType.BOOL, PropertySubType.NULL,
+        None,
+        "Is Active", "Whether the editbox is active",
+        False, True, 1, None, None, []
+    )
+    api.add_property(node_id, prop)
+    api.set_property_bool(node_id, "is_active", 0, True)
+
     prop = Property(
     prop = Property(
         "rect", PropertyType.FLOAT32, PropertySubType.PIXEL,
         "rect", PropertyType.FLOAT32, PropertySubType.PIXEL,
         None,
         None,
@@ -563,16 +572,38 @@ def draw():
     api.set_property_str(node_id, "text", 0, "hello king!😁🍆jelly 🍆1234")
     api.set_property_str(node_id, "text", 0, "hello king!😁🍆jelly 🍆1234")
 
 
     prop = Property(
     prop = Property(
-        "color", PropertyType.FLOAT32, PropertySubType.COLOR,
+        "text_color", PropertyType.FLOAT32, PropertySubType.COLOR,
         None,
         None,
-        "Color", "Color of the text",
+        "Text Color", "Color of the text",
         False, False, 4, 0, 1, []
         False, False, 4, 0, 1, []
     )
     )
     api.add_property(node_id, prop)
     api.add_property(node_id, prop)
-    api.set_property_f32(node_id, "color", 0, 1)
-    api.set_property_f32(node_id, "color", 1, 1)
-    api.set_property_f32(node_id, "color", 2, 1)
-    api.set_property_f32(node_id, "color", 3, 1)
+    api.set_property_f32(node_id, "text_color", 0, 1)
+    api.set_property_f32(node_id, "text_color", 1, 1)
+    api.set_property_f32(node_id, "text_color", 2, 1)
+    api.set_property_f32(node_id, "text_color", 3, 1)
+
+    prop = Property(
+        "hi_bg_color", PropertyType.FLOAT32, PropertySubType.COLOR,
+        None,
+        "Highlight Bg Color", "Background color for highlighted text",
+        False, False, 4, 0, 1, []
+    )
+    api.add_property(node_id, prop)
+    api.set_property_f32(node_id, "hi_bg_color", 0, 1)
+    api.set_property_f32(node_id, "hi_bg_color", 1, 1)
+    api.set_property_f32(node_id, "hi_bg_color", 2, 1)
+    api.set_property_f32(node_id, "hi_bg_color", 3, 0.5)
+
+    prop = Property(
+        "selected", PropertyType.UINT32, PropertySubType.NULL,
+        None,
+        "Selected", "Selected range",
+        True, False, 2, 0, None, []
+    )
+    api.add_property(node_id, prop)
+    api.set_property_u32(node_id, "selected", 0, 1)
+    api.set_property_u32(node_id, "selected", 1, 4)
 
 
     prop = Property(
     prop = Property(
         "z_index", PropertyType.UINT32, PropertySubType.NULL,
         "z_index", PropertyType.UINT32, PropertySubType.NULL,

+ 216 - 55
bin/darkwallet/src/editbox.rs

@@ -1,20 +1,29 @@
-use miniquad::{KeyMods, UniformType};
+use miniquad::{KeyMods, UniformType, MouseButton};
 use std::{io::Cursor, sync::{Arc, Mutex}};
 use std::{io::Cursor, sync::{Arc, Mutex}};
 use darkfi_serial::Decodable;
 use darkfi_serial::Decodable;
 use freetype as ft;
 use freetype as ft;
 
 
-use crate::{error::{Error, Result}, prop::Property, scene::{SceneGraph, SceneNodeId, Pimpl, Slot}, gfx::{Rectangle, RenderContext, COLOR_WHITE, COLOR_BLUE, COLOR_RED, COLOR_GREEN, FreetypeFace}, text::{Glyph, TextShaper}};
+use crate::{error::{Error, Result}, prop::{
+    PropertyBool, PropertyFloat32, PropertyUint32, PropertyStr, PropertyColor,
+    Property}, scene::{SceneGraph, SceneNode, SceneNodeId, Pimpl, Slot}, gfx::{Rectangle, RenderContext, COLOR_WHITE, COLOR_BLUE, COLOR_RED, COLOR_GREEN, FreetypeFace, Point}, text::{Glyph, TextShaper}, keysym::{MouseButtonAsU8, KeyCodeAsU16}};
 
 
 const CURSOR_WIDTH: f32 = 4.;
 const CURSOR_WIDTH: f32 = 4.;
 
 
 pub type EditBoxPtr = Arc<EditBox>;
 pub type EditBoxPtr = Arc<EditBox>;
 
 
 pub struct EditBox {
 pub struct EditBox {
-    scroll: Arc<Property>,
-    cursor_pos: Arc<Property>,
-    text: Arc<Property>,
-    font_size: Arc<Property>,
-    color: Arc<Property>,
+    is_active: PropertyBool,
+    debug: PropertyBool,
+    baseline: PropertyFloat32,
+    scroll: PropertyFloat32,
+    cursor_pos: PropertyUint32,
+    selected: Arc<Property>,
+    text: PropertyStr,
+    font_size: PropertyFloat32,
+    text_color: PropertyColor,
+    hi_bg_color: PropertyColor,
+    // Used for mouse clicks
+    world_rect: Mutex<Rectangle<f32>>,
     glyphs: Mutex<Vec<Glyph>>,
     glyphs: Mutex<Vec<Glyph>>,
     text_shaper: TextShaper,
     text_shaper: TextShaper,
 }
 }
@@ -22,32 +31,40 @@ pub struct EditBox {
 impl EditBox {
 impl EditBox {
     pub fn new(scene_graph: &mut SceneGraph, node_id: SceneNodeId, font_faces: Vec<FreetypeFace>) -> Result<Pimpl> {
     pub fn new(scene_graph: &mut SceneGraph, node_id: SceneNodeId, font_faces: Vec<FreetypeFace>) -> Result<Pimpl> {
         let node = scene_graph.get_node(node_id).unwrap();
         let node = scene_graph.get_node(node_id).unwrap();
-        let scroll = node.get_property("scroll").ok_or(Error::PropertyNotFound)?;
-        let cursor_pos = node.get_property("cursor_pos").ok_or(Error::PropertyNotFound)?;
-        let text = node.get_property("text").ok_or(Error::PropertyNotFound)?;
-        let font_size = node.get_property("font_size").ok_or(Error::PropertyNotFound)?;
-        let color = node.get_property("color").ok_or(Error::PropertyNotFound)?;
+        let is_active = PropertyBool::wrap(node, "is_active", 0)?;
+        let debug = PropertyBool::wrap(node, "debug", 0)?;
+        let baseline = PropertyFloat32::wrap(node, "baseline", 0)?;
+        let scroll = PropertyFloat32::wrap(node, "scroll", 0)?;
+        let cursor_pos = PropertyUint32::wrap(node, "cursor_pos", 0)?;
+        let selected = node.get_property("selected").ok_or(Error::PropertyNotFound)?;
+        let text = PropertyStr::wrap(node, "text", 0)?;
+        let font_size = PropertyFloat32::wrap(node, "font_size", 0)?;
+        let text_color = PropertyColor::wrap(node, "text_color")?;
+        let hi_bg_color = PropertyColor::wrap(node, "hi_bg_color")?;
 
 
         let text_shaper = TextShaper {
         let text_shaper = TextShaper {
             font_faces
             font_faces
         };
         };
 
 
-        let glyphs = text_shaper.shape(text.get_str(0)?, font_size.get_f32(0)?, 
-                [color.get_f32(0)?, color.get_f32(1)?,
-                 color.get_f32(2)?, color.get_f32(3)?]);
-
         println!("EditBox::new()");
         println!("EditBox::new()");
         let self_ = Arc::new(Self{
         let self_ = Arc::new(Self{
+            is_active,
+            debug,
+            baseline,
             scroll,
             scroll,
             cursor_pos,
             cursor_pos,
+            selected,
             text,
             text,
             font_size,
             font_size,
-            color,
-            glyphs: Mutex::new(glyphs),
+            text_color,
+            hi_bg_color,
+            world_rect: Mutex::new(Rectangle { x: 0., y: 0., w: 0., h: 0. }),
+            glyphs: Mutex::new(vec![]),
             text_shaper,
             text_shaper,
         });
         });
-        let weak_self = Arc::downgrade(&self_);
+        self_.regen_glyphs().unwrap();
 
 
+        let weak_self = Arc::downgrade(&self_);
         let slot = Slot {
         let slot = Slot {
             name: "editbox::key_down".to_string(),
             name: "editbox::key_down".to_string(),
             func: Box::new(move |data| {
             func: Box::new(move |data| {
@@ -74,15 +91,76 @@ impl EditBox {
             .expect("no keyboard attached!");
             .expect("no keyboard attached!");
         keyb_node.register("key_down", slot);
         keyb_node.register("key_down", slot);
 
 
+        let weak_self = Arc::downgrade(&self_);
+        let slot_btn_down = Slot {
+            name: "editbox::mouse_button_down".to_string(),
+            func: Box::new(move |data| {
+                let mut cur = Cursor::new(&data);
+                let button = MouseButton::from_u8(u8::decode(&mut cur).unwrap());
+                let x = f32::decode(&mut cur).unwrap();
+                let y = f32::decode(&mut cur).unwrap();
+
+                let self_ = weak_self.upgrade();
+                if let Some(self_) = self_ {
+                    self_.mouse_button_down(button, x, y);
+                }
+            }),
+        };
+
+        let weak_self = Arc::downgrade(&self_);
+        let slot_btn_up = Slot {
+            name: "editbox::mouse_button_up".to_string(),
+            func: Box::new(move |data| {
+                let mut cur = Cursor::new(&data);
+                let button = MouseButton::from_u8(u8::decode(&mut cur).unwrap());
+                let x = f32::decode(&mut cur).unwrap();
+                let y = f32::decode(&mut cur).unwrap();
+
+                let self_ = weak_self.upgrade();
+                if let Some(self_) = self_ {
+                    self_.mouse_button_up(button, x, y);
+                }
+            }),
+        };
+
+        let weak_self = Arc::downgrade(&self_);
+        let slot_move = Slot {
+            name: "editbox::mouse_move".to_string(),
+            func: Box::new(move |data| {
+                let mut cur = Cursor::new(&data);
+                let x = f32::decode(&mut cur).unwrap();
+                let y = f32::decode(&mut cur).unwrap();
+
+                let self_ = weak_self.upgrade();
+                if let Some(self_) = self_ {
+                    self_.mouse_move(x, y);
+                }
+            }),
+        };
+
+        let mouse_node = 
+            scene_graph
+            .lookup_node_mut("/window/input/mouse")
+            .expect("no mouse attached!");
+        mouse_node.register("button_down", slot_btn_down);
+        mouse_node.register("button_up", slot_btn_up);
+        mouse_node.register("move", slot_move);
+
         // Save any properties we use
         // Save any properties we use
         Ok(Pimpl::EditBox(self_))
         Ok(Pimpl::EditBox(self_))
     }
     }
 
 
-    pub fn render<'a>(&self, render: &mut RenderContext<'a>, node_id: SceneNodeId, layer_rect: &Rectangle<i32>) -> Result<()> {
+    pub fn render<'a>(&self, render: &mut RenderContext<'a>, node_id: SceneNodeId, layer_rect: &Rectangle<f32>) -> Result<()> {
         let node = render.scene_graph.get_node(node_id).unwrap();
         let node = render.scene_graph.get_node(node_id).unwrap();
 
 
         let rect = RenderContext::get_dim(node, layer_rect)?;
         let rect = RenderContext::get_dim(node, layer_rect)?;
 
 
+        // Used for detecting mouse clicks
+        let mut world_rect = rect.clone();
+        world_rect.x += layer_rect.x as f32;
+        world_rect.y += layer_rect.y as f32;
+        *self.world_rect.lock().unwrap() = world_rect;
+
         let layer_w = layer_rect.w as f32;
         let layer_w = layer_rect.w as f32;
         let layer_h = layer_rect.h as f32;
         let layer_h = layer_rect.h as f32;
         let off_x = rect.x / layer_w;
         let off_x = rect.x / layer_w;
@@ -106,24 +184,20 @@ impl EditBox {
         self.apply_cursor_scrolling(&rect);
         self.apply_cursor_scrolling(&rect);
 
 
         let node = render.scene_graph.get_node(node_id).unwrap();
         let node = render.scene_graph.get_node(node_id).unwrap();
-        let text = node.get_property_str("text")?;
-        let font_size = node.get_property_f32("font_size")?;
-        let debug = node.get_property_bool("debug")?;
-        let baseline = node.get_property_f32("baseline")?;
-        let scroll = node.get_property_f32("scroll")?;
-        let cursor_pos = node.get_property_u32("cursor_pos")?;
-
-        let color_prop = node.get_property("color").ok_or(Error::PropertyNotFound)?;
-        let color_r = color_prop.get_f32(0)?;
-        let color_g = color_prop.get_f32(1)?;
-        let color_b = color_prop.get_f32(2)?;
-        let color_a = color_prop.get_f32(3)?;
-        let text_color = [color_r, color_g, color_b, color_a];
-
-        let mut glyph_idx = 0;
-        let mut rhs = 0.;
+        let debug = self.debug.get();
+        let baseline = self.baseline.get();
+        let scroll = self.scroll.get();
+        let cursor_pos = self.cursor_pos.get() as usize;
+
+        let color = node.get_property("text_color").ok_or(Error::PropertyNotFound)?;
+        let text_color = self.text_color.get();
+
+        if !self.selected.is_null(0)? && !self.selected.is_null(1)? {
+            self.render_selected(render, &rect)?;
+        }
 
 
-        for glyph in &*self.glyphs.lock().unwrap() {
+        let mut rhs = 0.;
+        for (glyph_idx, glyph) in self.glyphs.lock().unwrap().iter().enumerate() {
             let texture = render.ctx.new_texture_from_rgba8(glyph.bmp_width, glyph.bmp_height, &glyph.bmp);
             let texture = render.ctx.new_texture_from_rgba8(glyph.bmp_width, glyph.bmp_height, &glyph.bmp);
 
 
             let x1 = glyph.pos.x + scroll;
             let x1 = glyph.pos.x + scroll;
@@ -150,8 +224,6 @@ impl EditBox {
                 render.render_box(x2, 0., x2 + CURSOR_WIDTH, rect.h, cursor_color);
                 render.render_box(x2, 0., x2 + CURSOR_WIDTH, rect.h, cursor_color);
             }
             }
 
 
-            glyph_idx += 1;
-
             rhs = x2;
             rhs = x2;
         }
         }
 
 
@@ -168,9 +240,35 @@ impl EditBox {
         Ok(())
         Ok(())
     }
     }
 
 
-    fn apply_cursor_scrolling(&self, rect: &Rectangle<f32>) -> Result<()> {
-        let cursor_pos = self.cursor_pos.get_u32(0)? as usize;
-        let mut scroll = self.scroll.get_f32(0)?;
+    pub fn render_selected<'a>(&self, render: &mut RenderContext<'a>, rect: &Rectangle<f32>) -> Result<()> {
+        let sel_start = self.selected.get_u32(0)? as usize;
+        let sel_end = self.selected.get_u32(1)? as usize;
+        assert!(sel_start <= sel_end);
+        let scroll = self.scroll.get();
+        let hi_bg_color = self.hi_bg_color.get();
+
+        let mut start_x = 0.;
+        let mut end_x = 0.;
+
+        for (glyph_idx, glyph) in self.glyphs.lock().unwrap().iter().enumerate() {
+            let x1 = glyph.pos.x + scroll;
+            let x2 = x1 + glyph.pos.w;
+
+            if glyph_idx == sel_start {
+                start_x = x1;
+            }
+            if glyph_idx == sel_end {
+                end_x = x2;
+            }
+        }
+
+        render.render_box(start_x, 0., end_x, rect.h, hi_bg_color);
+        Ok(())
+    }
+
+    fn apply_cursor_scrolling(&self, rect: &Rectangle<f32>) {
+        let cursor_pos = self.cursor_pos.get() as usize;
+        let mut scroll = self.scroll.get();
 
 
         let cursor_x = {
         let cursor_x = {
             let glyphs = &*self.glyphs.lock().unwrap();
             let glyphs = &*self.glyphs.lock().unwrap();
@@ -189,13 +287,12 @@ impl EditBox {
             scroll = -cursor_x;
             scroll = -cursor_x;
         }
         }
 
 
-        self.scroll.set_f32(0, scroll)
+        self.scroll.set(scroll);
     }
     }
 
 
     fn regen_glyphs(&self) -> Result<()> {
     fn regen_glyphs(&self) -> Result<()> {
-        let glyphs = self.text_shaper.shape(self.text.get_str(0)?, self.font_size.get_f32(0)?, 
-                [self.color.get_f32(0)?, self.color.get_f32(1)?,
-                 self.color.get_f32(2)?, self.color.get_f32(3)?]);
+        let glyphs = self.text_shaper.shape(self.text.get(), self.font_size.get(), 
+                self.text_color.get());
         *self.glyphs.lock().unwrap() = glyphs;
         *self.glyphs.lock().unwrap() = glyphs;
         Ok(())
         Ok(())
     }
     }
@@ -204,6 +301,9 @@ impl EditBox {
         if repeat {
         if repeat {
             return;
             return;
         }
         }
+        if !self.is_active.get() {
+            return
+        }
         match key.as_str() {
         match key.as_str() {
             "PageUp" => {
             "PageUp" => {
                 println!("pageup!");
                 println!("pageup!");
@@ -212,20 +312,44 @@ impl EditBox {
                 println!("pagedown!");
                 println!("pagedown!");
             }
             }
             "Left" => {
             "Left" => {
-                let cursor_pos = self.cursor_pos.get_u32(0).unwrap();
+                let mut cursor_pos = self.cursor_pos.get();
                 if cursor_pos > 0 {
                 if cursor_pos > 0 {
-                    self.cursor_pos.set_u32(0, cursor_pos - 1).unwrap();
+                    cursor_pos -= 1;
+                    self.cursor_pos.set(cursor_pos);
+                }
+
+                if !mods.shift {
+                    self.selected.set_null(0).unwrap();
+                    self.selected.set_null(1).unwrap();
+                } else {
+                    if self.selected.is_null(0).unwrap() {
+                        assert!(self.selected.is_null(1).unwrap());
+                        self.selected.set_u32(0, cursor_pos).unwrap();
+                    }
+                    self.selected.set_u32(1, cursor_pos).unwrap();
                 }
                 }
             }
             }
             "Right" => {
             "Right" => {
-                let cursor_pos = self.cursor_pos.get_u32(0).unwrap();
+                let mut cursor_pos = self.cursor_pos.get();
                 let glyphs_len = self.glyphs.lock().unwrap().len() as u32;
                 let glyphs_len = self.glyphs.lock().unwrap().len() as u32;
                 if cursor_pos < glyphs_len {
                 if cursor_pos < glyphs_len {
-                    self.cursor_pos.set_u32(0, cursor_pos + 1).unwrap();
+                    cursor_pos += 1;
+                    self.cursor_pos.set(cursor_pos);
+                }
+
+                if !mods.shift {
+                    self.selected.set_null(0).unwrap();
+                    self.selected.set_null(1).unwrap();
+                } else {
+                    if self.selected.is_null(0).unwrap() {
+                        assert!(self.selected.is_null(1).unwrap());
+                        self.selected.set_u32(1, cursor_pos).unwrap();
+                    }
+                    self.selected.set_u32(1, cursor_pos).unwrap();
                 }
                 }
             }
             }
             "Delete" => {
             "Delete" => {
-                let cursor_pos = self.cursor_pos.get_u32(0).unwrap();
+                let cursor_pos = self.cursor_pos.get();
                 if cursor_pos == 0 {
                 if cursor_pos == 0 {
                     return;
                     return;
                 }
                 }
@@ -240,11 +364,11 @@ impl EditBox {
                     }
                     }
                     text.push_str(&substr);
                     text.push_str(&substr);
                 }
                 }
-                self.text.set_str(0, text).unwrap();
+                self.text.set(text);
                 self.regen_glyphs().unwrap();
                 self.regen_glyphs().unwrap();
             }
             }
             "Backspace" => {
             "Backspace" => {
-                let cursor_pos = self.cursor_pos.get_u32(0).unwrap();
+                let cursor_pos = self.cursor_pos.get();
                 if cursor_pos == 0 {
                 if cursor_pos == 0 {
                     return;
                     return;
                 }
                 }
@@ -256,12 +380,49 @@ impl EditBox {
                     }
                     }
                     text.push_str(&substr);
                     text.push_str(&substr);
                 }
                 }
-                self.cursor_pos.set_u32(0, cursor_pos - 1).unwrap();
-                self.text.set_str(0, text).unwrap();
+                self.cursor_pos.set(cursor_pos - 1);
+                self.text.set(text);
                 self.regen_glyphs().unwrap();
                 self.regen_glyphs().unwrap();
             }
             }
             _ => {}
             _ => {}
         }
         }
     }
     }
+
+    fn mouse_button_down(self: Arc<Self>, button: MouseButton, x: f32, y: f32) {
+        let mouse_pos = Point { x, y };
+        let rect = self.world_rect.lock().unwrap();
+
+        // clicking inside box will:
+        // 1. make it active
+        // 2. begin selection
+        if rect.contains(&mouse_pos) {
+            if !self.is_active.get() {
+                self.is_active.set(true);
+                println!("inside!");
+                // Send signal
+            }
+
+            // set cursor pos
+            // begin selection
+        }
+        // click outside the box will:
+        // 1. make it inactive
+        else {
+            if self.is_active.get() {
+                self.is_active.set(false);
+                // Send signal
+            }
+        }
+    }
+    fn mouse_button_up(self: Arc<Self>, button: MouseButton, x: f32, y: f32) {
+        // releasing mouse button will:
+        // 1. end selection
+    }
+    fn mouse_move(self: Arc<Self>, x: f32, y: f32) {
+        // if active and selection_active, then use x to modify the selection.
+        // also implement scrolling when cursor is to the left or right
+        // just scroll to the end
+        // also set cursor_pos too
+    }
 }
 }
 
 

+ 28 - 24
bin/darkwallet/src/gfx.rs

@@ -18,6 +18,7 @@ use crate::{
     error::{Error, Result},
     error::{Error, Result},
     editbox,
     editbox,
     expr::{SExprMachine, SExprVal},
     expr::{SExprMachine, SExprVal},
+    keysym::MouseButtonAsU8,
     prop::{Property, PropertySubType, PropertyType},
     prop::{Property, PropertySubType, PropertyType},
     res::{ResourceId, ResourceManager},
     res::{ResourceId, ResourceManager},
     scene::{
     scene::{
@@ -27,21 +28,6 @@ use crate::{
     shader,
     shader,
 };
 };
 
 
-trait MouseButtonAsU8 {
-    fn to_u8(&self) -> u8;
-}
-
-impl MouseButtonAsU8 for MouseButton {
-    fn to_u8(&self) -> u8 {
-        match self {
-            MouseButton::Left => 0,
-            MouseButton::Middle => 1,
-            MouseButton::Right => 2,
-            MouseButton::Unknown => 3,
-        }
-    }
-}
-
 type Color = [f32; 4];
 type Color = [f32; 4];
 
 
 pub const COLOR_RED: Color = [1., 0., 0., 1.];
 pub const COLOR_RED: Color = [1., 0., 0., 1.];
@@ -76,6 +62,11 @@ struct Mesh {
     pub index_buffer: BufferId,
     pub index_buffer: BufferId,
 }
 }
 
 
+pub struct Point<T> {
+    pub x: T,
+    pub y: T,
+}
+
 #[derive(Debug, Clone)]
 #[derive(Debug, Clone)]
 pub struct Rectangle<T: Copy + std::ops::Add<Output=T> + std::ops::Sub<Output=T> + std::cmp::PartialOrd> {
 pub struct Rectangle<T: Copy + std::ops::Add<Output=T> + std::ops::Sub<Output=T> + std::cmp::PartialOrd> {
     pub x: T,
     pub x: T,
@@ -84,7 +75,7 @@ pub struct Rectangle<T: Copy + std::ops::Add<Output=T> + std::ops::Sub<Output=T>
     pub h: T,
     pub h: T,
 }
 }
 
 
-impl<T: Copy + std::ops::Add<Output=T> + std::ops::Sub<Output=T> + std::cmp::PartialOrd> Rectangle<T> {
+impl<T: Copy + std::ops::Add<Output=T> + std::ops::Sub<Output=T> + std::ops::AddAssign + std::cmp::PartialOrd> Rectangle<T> {
     fn from_array(arr: [T; 4]) -> Self {
     fn from_array(arr: [T; 4]) -> Self {
         let mut iter = IntoIter::new(arr);
         let mut iter = IntoIter::new(arr);
         Self {
         Self {
@@ -95,7 +86,7 @@ impl<T: Copy + std::ops::Add<Output=T> + std::ops::Sub<Output=T> + std::cmp::Par
         }
         }
     }
     }
 
 
-    fn clip(&self, other: &Rectangle<T>) -> Option<Rectangle<T>> {
+    pub fn clip(&self, other: &Self) -> Option<Self> {
         if other.x + other.w < self.x ||
         if other.x + other.w < self.x ||
             other.x > self.x + self.w ||
             other.x > self.x + self.w ||
             other.y + other.h < self.y ||
             other.y + other.h < self.y ||
@@ -119,6 +110,11 @@ impl<T: Copy + std::ops::Add<Output=T> + std::ops::Sub<Output=T> + std::cmp::Par
         }
         }
         Some(clipped)
         Some(clipped)
     }
     }
+
+    pub fn contains(&self, point: &Point<T>) -> bool {
+        self.x < point.x && point.x < self.x + self.w &&
+            self.y < point.y && point.y < self.y + self.h
+    }
 }
 }
 
 
 pub type FreetypeFace = ft::Face<&'static [u8]>;
 pub type FreetypeFace = ft::Face<&'static [u8]>;
@@ -472,11 +468,19 @@ impl<'a> RenderContext<'a> {
 
 
         let (_, screen_height) = window::screen_size();
         let (_, screen_height) = window::screen_size();
 
 
-        let mut rect = Self::get_rect(&layer)?;
-        rect.y = screen_height as i32 - (rect.y + rect.h);
+        let rect = Self::get_rect(&layer)?;
+
+        let mut view = rect.clone();
+        view.y = screen_height as i32 - (rect.y + rect.h);
+        self.ctx.apply_viewport(view.x, view.y, view.w, view.h);
+        self.ctx.apply_scissor_rect(view.x, view.y, view.w, view.h);
 
 
-        self.ctx.apply_viewport(rect.x, rect.y, rect.w, rect.h);
-        self.ctx.apply_scissor_rect(rect.x, rect.y, rect.w, rect.h);
+        let rect = Rectangle {
+            x: rect.x as f32,
+            y: rect.y as f32,
+            w: rect.w as f32,
+            h: rect.h as f32,
+        };
 
 
         let layer_children =
         let layer_children =
             layer.get_children(&[SceneNodeType::RenderMesh, SceneNodeType::RenderText, SceneNodeType::EditBox]);
             layer.get_children(&[SceneNodeType::RenderMesh, SceneNodeType::RenderText, SceneNodeType::EditBox]);
@@ -536,7 +540,7 @@ impl<'a> RenderContext<'a> {
         nodes.into_iter().map(|(z_index, node_inf)| node_inf).collect()
         nodes.into_iter().map(|(z_index, node_inf)| node_inf).collect()
     }
     }
 
 
-    pub fn get_dim(mesh: &SceneNode, layer_rect: &Rectangle<i32>) -> Result<Rectangle<f32>> {
+    pub fn get_dim(mesh: &SceneNode, layer_rect: &Rectangle<f32>) -> Result<Rectangle<f32>> {
         let prop = mesh.get_property("rect").ok_or(Error::PropertyNotFound)?;
         let prop = mesh.get_property("rect").ok_or(Error::PropertyNotFound)?;
         if prop.array_len != 4 {
         if prop.array_len != 4 {
             return Err(Error::PropertyWrongLen)
             return Err(Error::PropertyWrongLen)
@@ -563,7 +567,7 @@ impl<'a> RenderContext<'a> {
         Ok(Rectangle::from_array(rect))
         Ok(Rectangle::from_array(rect))
     }
     }
 
 
-    fn render_mesh(&mut self, node_id: SceneNodeId, layer_rect: &Rectangle<i32>) -> Result<()> {
+    fn render_mesh(&mut self, node_id: SceneNodeId, layer_rect: &Rectangle<f32>) -> Result<()> {
         let mesh = self.scene_graph.get_node(node_id).unwrap();
         let mesh = self.scene_graph.get_node(node_id).unwrap();
 
 
         let z_index = mesh.get_property_u32("z_index")?;
         let z_index = mesh.get_property_u32("z_index")?;
@@ -754,7 +758,7 @@ impl<'a> RenderContext<'a> {
         self.render_box(x1, y2 - w, x2, y2, color);
         self.render_box(x1, y2 - w, x2, y2, color);
     }
     }
 
 
-    fn render_text(&mut self, node_id: SceneNodeId, layer_rect: &Rectangle<i32>) -> Result<()> {
+    fn render_text(&mut self, node_id: SceneNodeId, layer_rect: &Rectangle<f32>) -> Result<()> {
         let node = self.scene_graph.get_node(node_id).unwrap();
         let node = self.scene_graph.get_node(node_id).unwrap();
 
 
         let text = node.get_property_str("text")?;
         let text = node.get_property_str("text")?;

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

@@ -14,6 +14,8 @@ mod expr;
 mod gfx;
 mod gfx;
 use gfx::run_gui;
 use gfx::run_gui;
 
 
+mod keysym;
+
 mod net;
 mod net;
 use net::ZeroMQAdapter;
 use net::ZeroMQAdapter;
 
 

+ 10 - 1
bin/darkwallet/src/prop.rs → bin/darkwallet/src/prop/mod.rs

@@ -13,6 +13,11 @@ use std::{
 
 
 use crate::{expr::SExprCode, scene::SceneNodeId};
 use crate::{expr::SExprCode, scene::SceneNodeId};
 
 
+mod wrap;
+pub use wrap::{
+    PropertyBool, PropertyFloat32, PropertyUint32, PropertyStr, PropertyColor,
+};
+
 type Buffer = Arc<Vec<u8>>;
 type Buffer = Arc<Vec<u8>>;
 
 
 #[derive(Debug, Copy, Clone, PartialEq, SerialEncodable, SerialDecodable)]
 #[derive(Debug, Copy, Clone, PartialEq, SerialEncodable, SerialDecodable)]
@@ -437,7 +442,7 @@ impl Property {
 
 
     // Get
     // Get
 
 
-    fn is_bounded(&self) -> bool {
+    pub fn is_bounded(&self) -> bool {
         self.array_len != 0
         self.array_len != 0
     }
     }
 
 
@@ -454,6 +459,10 @@ impl Property {
         let val = self.get_raw_value(i)?;
         let val = self.get_raw_value(i)?;
         Ok(val.is_unset())
         Ok(val.is_unset())
     }
     }
+    pub fn is_null(&self, i: usize) -> Result<bool> {
+        let val = self.get_raw_value(i)?;
+        Ok(val.is_null())
+    }
 
 
     pub fn is_expr(&self, i: usize) -> Result<bool> {
     pub fn is_expr(&self, i: usize) -> Result<bool> {
         if !self.is_expr_allowed {
         if !self.is_expr_allowed {

+ 134 - 0
bin/darkwallet/src/prop/wrap.rs

@@ -0,0 +1,134 @@
+use std::sync::Arc;
+
+use crate::{scene::SceneNode, error::{Error, Result}};
+use super::Property;
+
+pub struct PropertyBool {
+    prop: Arc<Property>,
+    idx: usize,
+}
+
+impl PropertyBool {
+    pub fn wrap(node: &SceneNode, prop_name: &str, idx: usize) -> Result<Self> {
+        let prop = node.get_property(prop_name).ok_or(Error::PropertyNotFound)?;
+
+        // Test if it works
+        let _ = prop.get_bool(idx)?;
+
+        Ok(Self { prop, idx })
+    }
+
+    pub fn get(&self) -> bool {
+        self.prop.get_bool(self.idx).unwrap()
+    }
+
+    pub fn set(&self, val: bool) {
+        self.prop.set_bool(self.idx, val).unwrap()
+    }
+}
+
+pub struct PropertyUint32 {
+    prop: Arc<Property>,
+    idx: usize,
+}
+
+impl PropertyUint32 {
+    pub fn wrap(node: &SceneNode, prop_name: &str, idx: usize) -> Result<Self> {
+        let prop = node.get_property(prop_name).ok_or(Error::PropertyNotFound)?;
+
+        // Test if it works
+        let _ = prop.get_u32(idx)?;
+
+        Ok(Self { prop, idx })
+    }
+
+    pub fn get(&self) -> u32 {
+        self.prop.get_u32(self.idx).unwrap()
+    }
+
+    pub fn set(&self, val: u32) {
+        self.prop.set_u32(self.idx, val).unwrap()
+    }
+}
+
+pub struct PropertyFloat32 {
+    prop: Arc<Property>,
+    idx: usize,
+}
+
+impl PropertyFloat32 {
+    pub fn wrap(node: &SceneNode, prop_name: &str, idx: usize) -> Result<Self> {
+        let prop = node.get_property(prop_name).ok_or(Error::PropertyNotFound)?;
+
+        // Test if it works
+        let _ = prop.get_f32(idx)?;
+
+        Ok(Self { prop, idx })
+    }
+
+    pub fn get(&self) -> f32 {
+        self.prop.get_f32(self.idx).unwrap()
+    }
+
+    pub fn set(&self, val: f32) {
+        self.prop.set_f32(self.idx, val).unwrap()
+    }
+}
+
+pub struct PropertyStr {
+    prop: Arc<Property>,
+    idx: usize,
+}
+
+impl PropertyStr {
+    pub fn wrap(node: &SceneNode, prop_name: &str, idx: usize) -> Result<Self> {
+        let prop = node.get_property(prop_name).ok_or(Error::PropertyNotFound)?;
+
+        // Test if it works
+        let _ = prop.get_str(idx)?;
+
+        Ok(Self { prop, idx })
+    }
+
+    pub fn get(&self) -> String {
+        self.prop.get_str(self.idx).unwrap()
+    }
+
+    pub fn set<S: Into<String>>(&self, val: S) {
+        self.prop.set_str(self.idx, val.into()).unwrap()
+    }
+}
+
+pub struct PropertyColor {
+    prop: Arc<Property>,
+}
+
+impl PropertyColor {
+    pub fn wrap(node: &SceneNode, 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 })
+    }
+
+    pub fn get(&self) -> [f32; 4] {
+        [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 set(&self, val: [f32; 4]) {
+        self.prop.set_f32(0, val[0]).unwrap();
+        self.prop.set_f32(1, val[1]).unwrap();
+        self.prop.set_f32(2, val[2]).unwrap();
+        self.prop.set_f32(3, val[3]).unwrap();
+    }
+}
+