diff --git a/bin/darkwallet/gui/__init__.py b/bin/darkwallet/gui/__init__.py index 3e72d2e12..dad414007 100644 --- a/bin/darkwallet/gui/__init__.py +++ b/bin/darkwallet/gui/__init__.py @@ -507,6 +507,15 @@ def draw(): 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( "rect", PropertyType.FLOAT32, PropertySubType.PIXEL, None, @@ -563,16 +572,38 @@ def draw(): api.set_property_str(node_id, "text", 0, "hello king!๐Ÿ˜๐Ÿ†jelly ๐Ÿ†1234") prop = Property( - "color", PropertyType.FLOAT32, PropertySubType.COLOR, + "text_color", PropertyType.FLOAT32, PropertySubType.COLOR, None, - "Color", "Color of the text", + "Text Color", "Color of the text", False, False, 4, 0, 1, [] ) 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( "z_index", PropertyType.UINT32, PropertySubType.NULL, diff --git a/bin/darkwallet/src/editbox.rs b/bin/darkwallet/src/editbox.rs index 146b1ce56..d9a333cbc 100644 --- a/bin/darkwallet/src/editbox.rs +++ b/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 darkfi_serial::Decodable; 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.; pub type EditBoxPtr = Arc; pub struct EditBox { - scroll: Arc, - cursor_pos: Arc, - text: Arc, - font_size: Arc, - color: Arc, + is_active: PropertyBool, + debug: PropertyBool, + baseline: PropertyFloat32, + scroll: PropertyFloat32, + cursor_pos: PropertyUint32, + selected: Arc, + text: PropertyStr, + font_size: PropertyFloat32, + text_color: PropertyColor, + hi_bg_color: PropertyColor, + // Used for mouse clicks + world_rect: Mutex>, glyphs: Mutex>, text_shaper: TextShaper, } @@ -22,32 +31,40 @@ pub struct EditBox { impl EditBox { pub fn new(scene_graph: &mut SceneGraph, node_id: SceneNodeId, font_faces: Vec) -> Result { 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 { 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()"); let self_ = Arc::new(Self{ + is_active, + debug, + baseline, scroll, cursor_pos, + selected, text, 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, }); - let weak_self = Arc::downgrade(&self_); + self_.regen_glyphs().unwrap(); + let weak_self = Arc::downgrade(&self_); let slot = Slot { name: "editbox::key_down".to_string(), func: Box::new(move |data| { @@ -74,15 +91,76 @@ impl EditBox { .expect("no keyboard attached!"); 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 Ok(Pimpl::EditBox(self_)) } - pub fn render<'a>(&self, render: &mut RenderContext<'a>, node_id: SceneNodeId, layer_rect: &Rectangle) -> Result<()> { + pub fn render<'a>(&self, render: &mut RenderContext<'a>, node_id: SceneNodeId, layer_rect: &Rectangle) -> Result<()> { let node = render.scene_graph.get_node(node_id).unwrap(); 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_h = layer_rect.h as f32; let off_x = rect.x / layer_w; @@ -106,24 +184,20 @@ impl EditBox { self.apply_cursor_scrolling(&rect); 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 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_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 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)?; + } - let mut glyph_idx = 0; let mut rhs = 0.; - - for glyph in &*self.glyphs.lock().unwrap() { + 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 x1 = glyph.pos.x + scroll; @@ -150,8 +224,6 @@ impl EditBox { render.render_box(x2, 0., x2 + CURSOR_WIDTH, rect.h, cursor_color); } - glyph_idx += 1; - rhs = x2; } @@ -168,9 +240,35 @@ impl EditBox { Ok(()) } - fn apply_cursor_scrolling(&self, rect: &Rectangle) -> 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) -> 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) { + let cursor_pos = self.cursor_pos.get() as usize; + let mut scroll = self.scroll.get(); let cursor_x = { let glyphs = &*self.glyphs.lock().unwrap(); @@ -189,13 +287,12 @@ impl EditBox { scroll = -cursor_x; } - self.scroll.set_f32(0, scroll) + self.scroll.set(scroll); } 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; Ok(()) } @@ -204,6 +301,9 @@ impl EditBox { if repeat { return; } + if !self.is_active.get() { + return + } match key.as_str() { "PageUp" => { println!("pageup!"); @@ -212,20 +312,44 @@ impl EditBox { println!("pagedown!"); } "Left" => { - let cursor_pos = self.cursor_pos.get_u32(0).unwrap(); + let mut cursor_pos = self.cursor_pos.get(); 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" => { - 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; 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" => { - let cursor_pos = self.cursor_pos.get_u32(0).unwrap(); + let cursor_pos = self.cursor_pos.get(); if cursor_pos == 0 { return; } @@ -240,11 +364,11 @@ impl EditBox { } text.push_str(&substr); } - self.text.set_str(0, text).unwrap(); + self.text.set(text); self.regen_glyphs().unwrap(); } "Backspace" => { - let cursor_pos = self.cursor_pos.get_u32(0).unwrap(); + let cursor_pos = self.cursor_pos.get(); if cursor_pos == 0 { return; } @@ -256,12 +380,49 @@ impl EditBox { } 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(); } _ => {} } } + + fn mouse_button_down(self: Arc, 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, button: MouseButton, x: f32, y: f32) { + // releasing mouse button will: + // 1. end selection + } + fn mouse_move(self: Arc, 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 + } } diff --git a/bin/darkwallet/src/gfx.rs b/bin/darkwallet/src/gfx.rs index c07636042..a2eff4fa3 100644 --- a/bin/darkwallet/src/gfx.rs +++ b/bin/darkwallet/src/gfx.rs @@ -18,6 +18,7 @@ use crate::{ error::{Error, Result}, editbox, expr::{SExprMachine, SExprVal}, + keysym::MouseButtonAsU8, prop::{Property, PropertySubType, PropertyType}, res::{ResourceId, ResourceManager}, scene::{ @@ -27,21 +28,6 @@ use crate::{ 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]; pub const COLOR_RED: Color = [1., 0., 0., 1.]; @@ -76,6 +62,11 @@ struct Mesh { pub index_buffer: BufferId, } +pub struct Point { + pub x: T, + pub y: T, +} + #[derive(Debug, Clone)] pub struct Rectangle + std::ops::Sub + std::cmp::PartialOrd> { pub x: T, @@ -84,7 +75,7 @@ pub struct Rectangle + std::ops::Sub pub h: T, } -impl + std::ops::Sub + std::cmp::PartialOrd> Rectangle { +impl + std::ops::Sub + std::ops::AddAssign + std::cmp::PartialOrd> Rectangle { fn from_array(arr: [T; 4]) -> Self { let mut iter = IntoIter::new(arr); Self { @@ -95,7 +86,7 @@ impl + std::ops::Sub + std::cmp::Par } } - fn clip(&self, other: &Rectangle) -> Option> { + pub fn clip(&self, other: &Self) -> Option { if other.x + other.w < self.x || other.x > self.x + self.w || other.y + other.h < self.y || @@ -119,6 +110,11 @@ impl + std::ops::Sub + std::cmp::Par } Some(clipped) } + + pub fn contains(&self, point: &Point) -> 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]>; @@ -472,11 +468,19 @@ impl<'a> RenderContext<'a> { 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)?; - 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 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); + + 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 = 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() } - pub fn get_dim(mesh: &SceneNode, layer_rect: &Rectangle) -> Result> { + pub fn get_dim(mesh: &SceneNode, layer_rect: &Rectangle) -> Result> { let prop = mesh.get_property("rect").ok_or(Error::PropertyNotFound)?; if prop.array_len != 4 { return Err(Error::PropertyWrongLen) @@ -563,7 +567,7 @@ impl<'a> RenderContext<'a> { Ok(Rectangle::from_array(rect)) } - fn render_mesh(&mut self, node_id: SceneNodeId, layer_rect: &Rectangle) -> Result<()> { + fn render_mesh(&mut self, node_id: SceneNodeId, layer_rect: &Rectangle) -> Result<()> { let mesh = self.scene_graph.get_node(node_id).unwrap(); 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); } - fn render_text(&mut self, node_id: SceneNodeId, layer_rect: &Rectangle) -> Result<()> { + fn render_text(&mut self, node_id: SceneNodeId, layer_rect: &Rectangle) -> Result<()> { let node = self.scene_graph.get_node(node_id).unwrap(); let text = node.get_property_str("text")?; diff --git a/bin/darkwallet/src/main.rs b/bin/darkwallet/src/main.rs index b1018e17d..8e0fbef33 100644 --- a/bin/darkwallet/src/main.rs +++ b/bin/darkwallet/src/main.rs @@ -14,6 +14,8 @@ mod expr; mod gfx; use gfx::run_gui; +mod keysym; + mod net; use net::ZeroMQAdapter; diff --git a/bin/darkwallet/src/prop.rs b/bin/darkwallet/src/prop/mod.rs similarity index 98% rename from bin/darkwallet/src/prop.rs rename to bin/darkwallet/src/prop/mod.rs index 624e6d367..6eaf8ce5c 100644 --- a/bin/darkwallet/src/prop.rs +++ b/bin/darkwallet/src/prop/mod.rs @@ -13,6 +13,11 @@ use std::{ use crate::{expr::SExprCode, scene::SceneNodeId}; +mod wrap; +pub use wrap::{ + PropertyBool, PropertyFloat32, PropertyUint32, PropertyStr, PropertyColor, +}; + type Buffer = Arc>; #[derive(Debug, Copy, Clone, PartialEq, SerialEncodable, SerialDecodable)] @@ -437,7 +442,7 @@ impl Property { // Get - fn is_bounded(&self) -> bool { + pub fn is_bounded(&self) -> bool { self.array_len != 0 } @@ -454,6 +459,10 @@ impl Property { let val = self.get_raw_value(i)?; Ok(val.is_unset()) } + pub fn is_null(&self, i: usize) -> Result { + let val = self.get_raw_value(i)?; + Ok(val.is_null()) + } pub fn is_expr(&self, i: usize) -> Result { if !self.is_expr_allowed { diff --git a/bin/darkwallet/src/prop/wrap.rs b/bin/darkwallet/src/prop/wrap.rs new file mode 100644 index 000000000..9849a9712 --- /dev/null +++ b/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, + idx: usize, +} + +impl PropertyBool { + pub fn wrap(node: &SceneNode, prop_name: &str, idx: usize) -> Result { + 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, + idx: usize, +} + +impl PropertyUint32 { + pub fn wrap(node: &SceneNode, prop_name: &str, idx: usize) -> Result { + 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, + idx: usize, +} + +impl PropertyFloat32 { + pub fn wrap(node: &SceneNode, prop_name: &str, idx: usize) -> Result { + 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, + idx: usize, +} + +impl PropertyStr { + pub fn wrap(node: &SceneNode, prop_name: &str, idx: usize) -> Result { + 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>(&self, val: S) { + self.prop.set_str(self.idx, val.into()).unwrap() + } +} + +pub struct PropertyColor { + prop: Arc, +} + +impl PropertyColor { + pub fn wrap(node: &SceneNode, prop_name: &str) -> Result { + 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(); + } +} +