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

wallet: upgrade ui::Mesh to ui::VectorArt which provides scalable vector shapes

darkfi 1 год назад
Родитель
Сommit
e8f9333580

+ 86 - 250
bin/darkwallet/src/app/schema.rs

@@ -31,7 +31,8 @@ use crate::{
     },
     text::TextShaperPtr,
     ui::{
-        chatview, Button, ChatView, EditBox, Image, RenderLayer, Stoppable, Text, VectorArt, Window,
+        chatview, vector_art::shape, Button, ChatView, EditBox, Image, RenderLayer, ShapeVertex,
+        Stoppable, Text, VectorArt, VectorShape, Window,
     },
     ExecutorPtr,
 };
@@ -115,29 +116,18 @@ pub(super) async fn make_old(app: &App) {
     let c = if LIGHTMODE { 1. } else { 0. };
     // Setup the pimpl
     let node_id = node.id;
-    let (x1, y1) = (0., 0.);
-    let (x2, y2) = (1., 1.);
-    let verts = vec![
-        // top left
-        Vertex { pos: [x1, y1], color: [c, c, c, 1.], uv: [0., 0.] },
-        // top right
-        Vertex { pos: [x2, y1], color: [c, c, c, 1.], uv: [1., 0.] },
-        // bottom left
-        Vertex { pos: [x1, y2], color: [c, c, c, 1.], uv: [0., 1.] },
-        // bottom right
-        Vertex { pos: [x2, y2], color: [c, c, c, 1.], uv: [1., 1.] },
-    ];
-    let indices = vec![0, 2, 1, 1, 2, 3];
+    let mut shape = VectorShape::new();
+    shape.add_filled_box(
+        shape::const_f32(0.),
+        shape::const_f32(0.),
+        shape::load_var("w"),
+        shape::load_var("h"),
+        [c, c, c, 1.],
+    );
     drop(sg);
-    let pimpl = VectorArt::new(
-        app.ex.clone(),
-        app.sg.clone(),
-        node_id,
-        app.render_api.clone(),
-        verts,
-        indices,
-    )
-    .await;
+    let pimpl =
+        VectorArt::new(app.ex.clone(), app.sg.clone(), node_id, app.render_api.clone(), shape)
+            .await;
     let mut sg = app.sg.lock().await;
     let node = sg.get_node_mut(node_id).unwrap();
     node.pimpl = pimpl;
@@ -157,42 +147,27 @@ pub(super) async fn make_old(app: &App) {
     prop.set_f32(Role::App, 3, 60.).unwrap();
 
     // Setup the pimpl
-    let (x1, y1) = (0., 0.);
-    let (x2, y2) = (1., 1.);
     let verts = if LIGHTMODE {
         vec![
-            // top left
-            Vertex { pos: [x1, y1], color: [1., 0., 0., 1.], uv: [0., 0.] },
-            // top right
-            Vertex { pos: [x2, y1], color: [1., 0., 0., 1.], uv: [1., 0.] },
-            // bottom left
-            Vertex { pos: [x1, y2], color: [1., 0., 0., 1.], uv: [0., 1.] },
-            // bottom right
-            Vertex { pos: [x2, y2], color: [1., 0., 0., 1.], uv: [1., 1.] },
+            ShapeVertex::from_xy(0., 0., [1., 0., 0., 1.]),
+            ShapeVertex::from_xy(200., 0., [1., 0., 0., 1.]),
+            ShapeVertex::from_xy(0., 60., [1., 0., 0., 1.]),
+            ShapeVertex::from_xy(200., 60., [1., 0., 0., 1.]),
         ]
     } else {
         vec![
-            // top left
-            Vertex { pos: [x1, y1], color: [1., 0., 0., 1.], uv: [0., 0.] },
-            // top right
-            Vertex { pos: [x2, y1], color: [1., 0., 1., 1.], uv: [1., 0.] },
-            // bottom left
-            Vertex { pos: [x1, y2], color: [0., 0., 1., 1.], uv: [0., 1.] },
-            // bottom right
-            Vertex { pos: [x2, y2], color: [1., 1., 0., 1.], uv: [1., 1.] },
+            ShapeVertex::from_xy(0., 0., [1., 0., 0., 1.]),
+            ShapeVertex::from_xy(200., 0., [1., 0., 1., 1.]),
+            ShapeVertex::from_xy(0., 60., [0., 0., 1., 1.]),
+            ShapeVertex::from_xy(200., 60., [1., 1., 0., 1.]),
         ]
     };
     let indices = vec![0, 2, 1, 1, 2, 3];
+    let shape = VectorShape { verts, indices };
     drop(sg);
-    let pimpl = VectorArt::new(
-        app.ex.clone(),
-        app.sg.clone(),
-        node_id,
-        app.render_api.clone(),
-        verts,
-        indices,
-    )
-    .await;
+    let pimpl =
+        VectorArt::new(app.ex.clone(), app.sg.clone(), node_id, app.render_api.clone(), shape)
+            .await;
     let mut sg = app.sg.lock().await;
     let node = sg.get_node_mut(node_id).unwrap();
     node.pimpl = pimpl;
@@ -235,42 +210,27 @@ pub(super) async fn make_old(app: &App) {
     prop.set_f32(Role::App, 3, 60.).unwrap();
 
     // Setup the pimpl
-    let (x1, y1) = (0., 0.);
-    let (x2, y2) = (1., 1.);
     let verts = if LIGHTMODE {
         vec![
-            // top left
-            Vertex { pos: [x1, y1], color: [1., 0., 0., 1.], uv: [0., 0.] },
-            // top right
-            Vertex { pos: [x2, y1], color: [1., 0., 0., 1.], uv: [1., 0.] },
-            // bottom left
-            Vertex { pos: [x1, y2], color: [1., 0., 0., 1.], uv: [0., 1.] },
-            // bottom right
-            Vertex { pos: [x2, y2], color: [1., 0., 0., 1.], uv: [1., 1.] },
+            ShapeVertex::from_xy(0., 0., [1., 0., 0., 1.]),
+            ShapeVertex::from_xy(60., 0., [1., 0., 0., 1.]),
+            ShapeVertex::from_xy(0., 60., [0., 0., 0., 1.]),
+            ShapeVertex::from_xy(60., 60., [1., 0., 0., 1.]),
         ]
     } else {
         vec![
-            // top left
-            Vertex { pos: [x1, y1], color: [1., 0., 0., 1.], uv: [0., 0.] },
-            // top right
-            Vertex { pos: [x2, y1], color: [1., 0., 1., 1.], uv: [1., 0.] },
-            // bottom left
-            Vertex { pos: [x1, y2], color: [0., 0., 1., 1.], uv: [0., 1.] },
-            // bottom right
-            Vertex { pos: [x2, y2], color: [1., 1., 0., 1.], uv: [1., 1.] },
+            ShapeVertex::from_xy(0., 0., [1., 0., 0., 1.]),
+            ShapeVertex::from_xy(60., 0., [1., 0., 1., 1.]),
+            ShapeVertex::from_xy(0., 60., [0., 0., 1., 1.]),
+            ShapeVertex::from_xy(60., 60., [1., 1., 0., 1.]),
         ]
     };
     let indices = vec![0, 2, 1, 1, 2, 3];
+    let shape = VectorShape { verts, indices };
     drop(sg);
-    let pimpl = VectorArt::new(
-        app.ex.clone(),
-        app.sg.clone(),
-        node_id,
-        app.render_api.clone(),
-        verts,
-        indices,
-    )
-    .await;
+    let pimpl =
+        VectorArt::new(app.ex.clone(), app.sg.clone(), node_id, app.render_api.clone(), shape)
+            .await;
     let mut sg = app.sg.lock().await;
     let node = sg.get_node_mut(node_id).unwrap();
     node.pimpl = pimpl;
@@ -288,79 +248,34 @@ pub(super) async fn make_old(app: &App) {
     prop.set_expr(Role::App, 1, code).unwrap();
     let code = vec![Op::LoadVar("w".to_string())];
     prop.set_expr(Role::App, 2, code).unwrap();
-    prop.set_f32(Role::App, 3, 5.).unwrap();
-
-    node.set_property_u32(Role::App, "z_index", 2).unwrap();
-
-    // Setup the pimpl
-    let (x1, y1) = (0., 0.);
-    let (x2, y2) = (1., 1.);
-    let verts = vec![
-        // top left
-        Vertex { pos: [x1, y1], color: [0., 1., 0., 1.], uv: [0., 0.] },
-        // top right
-        Vertex { pos: [x2, y1], color: [0., 1., 0., 1.], uv: [1., 0.] },
-        // bottom left
-        Vertex { pos: [x1, y2], color: [0., 1., 0., 1.], uv: [0., 1.] },
-        // bottom right
-        Vertex { pos: [x2, y2], color: [0., 1., 0., 1.], uv: [1., 1.] },
-    ];
-    let indices = vec![0, 2, 1, 1, 2, 3];
-    drop(sg);
-    let pimpl = VectorArt::new(
-        app.ex.clone(),
-        app.sg.clone(),
-        node_id,
-        app.render_api.clone(),
-        verts,
-        indices,
-    )
-    .await;
-    let mut sg = app.sg.lock().await;
-    let node = sg.get_node_mut(node_id).unwrap();
-    node.pimpl = pimpl;
-
-    sg.link(node_id, layer_node_id).unwrap();
-
-    // Debugging tool
-    let node_id = create_vector_art(&mut sg, "debugtool2");
-
-    let node = sg.get_node_mut(node_id).unwrap();
-    let prop = node.get_property("rect").unwrap();
-    prop.set_f32(Role::App, 0, 0.).unwrap();
-    let code =
-        vec![Op::Sub((Box::new(Op::LoadVar("h".to_string())), Box::new(Op::ConstFloat32(200.))))];
-    prop.set_expr(Role::App, 1, code).unwrap();
-    let code = vec![Op::LoadVar("w".to_string())];
-    prop.set_expr(Role::App, 2, code).unwrap();
-    prop.set_f32(Role::App, 3, 5.).unwrap();
+    let code = vec![Op::Sub((
+        Box::new(Op::Div((Box::new(Op::LoadVar("h".to_string())), Box::new(Op::ConstFloat32(2.))))),
+        Box::new(Op::ConstFloat32(200.)),
+    ))];
+    prop.set_expr(Role::App, 3, code).unwrap();
 
     node.set_property_u32(Role::App, "z_index", 2).unwrap();
 
     // Setup the pimpl
-    let (x1, y1) = (0., 0.);
-    let (x2, y2) = (1., 1.);
-    let verts = vec![
-        // top left
-        Vertex { pos: [x1, y1], color: [0., 1., 0., 1.], uv: [0., 0.] },
-        // top right
-        Vertex { pos: [x2, y1], color: [0., 1., 0., 1.], uv: [1., 0.] },
-        // bottom left
-        Vertex { pos: [x1, y2], color: [0., 1., 0., 1.], uv: [0., 1.] },
-        // bottom right
-        Vertex { pos: [x2, y2], color: [0., 1., 0., 1.], uv: [1., 1.] },
-    ];
-    let indices = vec![0, 2, 1, 1, 2, 3];
+    let mut shape = VectorShape::new();
+    shape.add_filled_box(
+        shape::const_f32(0.),
+        shape::const_f32(0.),
+        shape::load_var("w"),
+        shape::const_f32(5.),
+        [0., 1., 0., 1.],
+    );
+    shape.add_filled_box(
+        shape::const_f32(0.),
+        vec![Op::Sub((Box::new(Op::LoadVar("h".to_string())), Box::new(Op::ConstFloat32(5.))))],
+        shape::load_var("w"),
+        shape::load_var("h"),
+        [0., 1., 0., 1.],
+    );
     drop(sg);
-    let pimpl = VectorArt::new(
-        app.ex.clone(),
-        app.sg.clone(),
-        node_id,
-        app.render_api.clone(),
-        verts,
-        indices,
-    )
-    .await;
+    let pimpl =
+        VectorArt::new(app.ex.clone(), app.sg.clone(), node_id, app.render_api.clone(), shape)
+            .await;
     let mut sg = app.sg.lock().await;
     let node = sg.get_node_mut(node_id).unwrap();
     node.pimpl = pimpl;
@@ -660,29 +575,18 @@ pub(super) async fn make(app: &App) {
     let c = if LIGHTMODE { 1. } else { 0.05 };
     // Setup the pimpl
     let node_id = node.id;
-    let (x1, y1) = (0., 0.);
-    let (x2, y2) = (1., 1.);
-    let verts = vec![
-        // top left
-        Vertex { pos: [x1, y1], color: [c, c, c, 1.], uv: [0., 0.] },
-        // top right
-        Vertex { pos: [x2, y1], color: [c, c, c, 1.], uv: [1., 0.] },
-        // bottom left
-        Vertex { pos: [x1, y2], color: [c, c, c, 1.], uv: [0., 1.] },
-        // bottom right
-        Vertex { pos: [x2, y2], color: [c, c, c, 1.], uv: [1., 1.] },
-    ];
-    let indices = vec![0, 2, 1, 1, 2, 3];
+    let mut shape = VectorShape::new();
+    shape.add_filled_box(
+        shape::const_f32(0.),
+        shape::const_f32(0.),
+        shape::load_var("w"),
+        shape::load_var("h"),
+        [c, c, c, 1.],
+    );
     drop(sg);
-    let pimpl = VectorArt::new(
-        app.ex.clone(),
-        app.sg.clone(),
-        node_id,
-        app.render_api.clone(),
-        verts,
-        indices,
-    )
-    .await;
+    let pimpl =
+        VectorArt::new(app.ex.clone(), app.sg.clone(), node_id, app.render_api.clone(), shape)
+            .await;
     let mut sg = app.sg.lock().await;
     let node = sg.get_node_mut(node_id).unwrap();
     node.pimpl = pimpl;
@@ -807,94 +711,26 @@ pub(super) async fn make(app: &App) {
     prop.set_f32(Role::App, 3, EDITCHAT_HEIGHT).unwrap();
     node.set_property_u32(Role::App, "z_index", 0).unwrap();
     drop(sg);
-    let mut mesh = MeshBuilder::new();
-    mesh.draw_box(
-        &Rectangle { x: 0., y: 0., w: 1., h: 1. },
+    let mut shape = VectorShape::new();
+    shape.add_filled_box(
+        shape::const_f32(0.),
+        shape::const_f32(0.),
+        shape::load_var("w"),
+        shape::load_var("h"),
         [0., 0.13, 0.08, 1.],
-        &Rectangle { x: 0., y: 0., w: 1., h: 1. },
     );
-    let pimpl = VectorArt::new(
-        app.ex.clone(),
-        app.sg.clone(),
-        node_id,
-        app.render_api.clone(),
-        mesh.verts,
-        mesh.indices,
-    )
-    .await;
-    let mut sg = app.sg.lock().await;
-    let node = sg.get_node_mut(node_id).unwrap();
-    node.pimpl = pimpl;
-
-    sg.link(node_id, layer_node_id).unwrap();
-
-    // Create the nick - editbox sep
-    let node_id = create_vector_art(&mut sg, "editbox_lhs_sep");
-
-    let node = sg.get_node_mut(node_id).unwrap();
-    let prop = node.get_property("rect").unwrap();
-    prop.set_f32(Role::App, 0, NICKLABEL_WIDTH).unwrap();
-    let code = vec![Op::Sub((
-        Box::new(Op::LoadVar("h".to_string())),
-        Box::new(Op::ConstFloat32(EDITCHAT_HEIGHT)),
-    ))];
-    prop.set_expr(Role::App, 1, code).unwrap();
-    prop.set_f32(Role::App, 2, 1.).unwrap();
-    prop.set_f32(Role::App, 3, EDITCHAT_HEIGHT).unwrap();
-    node.set_property_u32(Role::App, "z_index", 1).unwrap();
-    drop(sg);
-    let mut mesh = MeshBuilder::new();
-    mesh.draw_box(
-        &Rectangle { x: 0., y: 0., w: 1., h: 1. },
+    shape.add_outline(
+        shape::const_f32(0.),
+        shape::const_f32(0.),
+        shape::load_var("w"),
+        shape::load_var("h"),
+        1.,
         [0.4, 0.4, 0.4, 1.],
-        &Rectangle { x: 0., y: 0., w: 1., h: 1. },
     );
-    let pimpl = VectorArt::new(
-        app.ex.clone(),
-        app.sg.clone(),
-        node_id,
-        app.render_api.clone(),
-        mesh.verts,
-        mesh.indices,
-    )
-    .await;
-    let mut sg = app.sg.lock().await;
-    let node = sg.get_node_mut(node_id).unwrap();
-    node.pimpl = pimpl;
 
-    sg.link(node_id, layer_node_id).unwrap();
-
-    // Create the chatview - editbox sep
-    let node_id = create_vector_art(&mut sg, "chatview_bhs_sep");
-
-    let node = sg.get_node_mut(node_id).unwrap();
-    let prop = node.get_property("rect").unwrap();
-    prop.set_f32(Role::App, 0, 0.).unwrap();
-    let code = vec![Op::Sub((
-        Box::new(Op::LoadVar("h".to_string())),
-        Box::new(Op::ConstFloat32(EDITCHAT_HEIGHT)),
-    ))];
-    prop.set_expr(Role::App, 1, code).unwrap();
-    let code = vec![Op::LoadVar("w".to_string())];
-    prop.set_expr(Role::App, 2, code).unwrap();
-    prop.set_f32(Role::App, 3, 1.).unwrap();
-    node.set_property_u32(Role::App, "z_index", 1).unwrap();
-    drop(sg);
-    let mut mesh = MeshBuilder::new();
-    mesh.draw_box(
-        &Rectangle { x: 0., y: 0., w: 1., h: 1. },
-        [0.4, 0.4, 0.4, 1.],
-        &Rectangle { x: 0., y: 0., w: 1., h: 1. },
-    );
-    let pimpl = VectorArt::new(
-        app.ex.clone(),
-        app.sg.clone(),
-        node_id,
-        app.render_api.clone(),
-        mesh.verts,
-        mesh.indices,
-    )
-    .await;
+    let pimpl =
+        VectorArt::new(app.ex.clone(), app.sg.clone(), node_id, app.render_api.clone(), shape)
+            .await;
     let mut sg = app.sg.lock().await;
     let node = sg.get_node_mut(node_id).unwrap();
     node.pimpl = pimpl;

+ 1 - 1
bin/darkwallet/src/expr.rs

@@ -122,7 +122,7 @@ pub struct SExprMachine<'a> {
 // Each item is a statement
 pub type SExprCode = Vec<Op>;
 
-#[derive(Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
 pub enum Op {
     Null,
     Add((Box<Op>, Box<Op>)),

+ 5 - 2
bin/darkwallet/src/ui/mod.rs

@@ -35,8 +35,11 @@ mod editbox;
 pub use editbox::{EditBox, EditBoxPtr};
 mod image;
 pub use image::{Image, ImagePtr};
-mod vector_art;
-pub use vector_art::{VectorArt, VectorArtPtr};
+pub mod vector_art;
+pub use vector_art::{
+    shape::{ShapeVertex, VectorShape},
+    VectorArt, VectorArtPtr,
+};
 mod layer;
 pub use layer::{RenderLayer, RenderLayerPtr};
 mod text;

+ 42 - 29
bin/darkwallet/src/ui/vector_art.rs → bin/darkwallet/src/ui/vector_art/mod.rs

@@ -17,19 +17,26 @@
  */
 
 use rand::{rngs::OsRng, Rng};
-use std::sync::{Arc, Weak};
+use std::sync::{Arc, Mutex as SyncMutex, Weak};
 
 use crate::{
+    error::{Error, Result},
+    expr::{Op, SExprCode, SExprMachine, SExprVal},
     gfx::{
         GfxBufferId, GfxDrawCall, GfxDrawInstruction, GfxDrawMesh, Rectangle, RenderApiPtr, Vertex,
     },
+    mesh::Color,
     prop::{PropertyPtr, PropertyUint32, Role},
     scene::{Pimpl, SceneGraph, SceneGraphPtr2, SceneNodeId},
+    util::enumerate,
     ExecutorPtr,
 };
 
 use super::{eval_rect, get_parent_rect, read_rect, DrawUpdate, OnModify, Stoppable};
 
+pub mod shape;
+use shape::VectorShape;
+
 pub type VectorArtPtr = Arc<VectorArt>;
 
 pub struct VectorArt {
@@ -37,10 +44,8 @@ pub struct VectorArt {
     render_api: RenderApiPtr,
     _tasks: Vec<smol::Task<()>>,
 
-    vertex_buffer: GfxBufferId,
-    index_buffer: GfxBufferId,
-    // Texture
-    num_elements: i32,
+    shape: VectorShape,
+    buffers: SyncMutex<Option<GfxDrawMesh>>,
 
     dc_key: u64,
 
@@ -55,13 +60,8 @@ impl VectorArt {
         sg: SceneGraphPtr2,
         node_id: SceneNodeId,
         render_api: RenderApiPtr,
-        verts: Vec<Vertex>,
-        indices: Vec<u16>,
+        shape: VectorShape,
     ) -> Pimpl {
-        let num_elements = indices.len() as i32;
-        let vertex_buffer = render_api.new_vertex_buffer(verts);
-        let index_buffer = render_api.new_index_buffer(indices);
-
         let scene_graph = sg.lock().await;
         let node = scene_graph.get_node(node_id).unwrap();
         let node_name = node.name.clone();
@@ -78,9 +78,8 @@ impl VectorArt {
                 sg,
                 render_api,
                 _tasks: on_modify.tasks,
-                vertex_buffer,
-                index_buffer,
-                num_elements,
+                shape,
+                buffers: SyncMutex::new(None),
                 dc_key: OsRng.gen(),
                 node_id,
                 rect,
@@ -100,25 +99,18 @@ impl VectorArt {
         };
 
         let Some(draw_update) = self.draw(&sg, &parent_rect) else {
-            error!(target: "ui::mesh", "Mesh {:?} failed to draw", node);
+            error!(target: "ui::vector_art", "Mesh {:?} failed to draw", node);
             return;
         };
         self.render_api.replace_draw_calls(draw_update.draw_calls);
-        debug!(target: "ui::mesh", "replace draw calls done");
+        debug!(target: "ui::vector_art", "replace draw calls done");
     }
 
     pub fn draw(&self, sg: &SceneGraph, parent_rect: &Rectangle) -> Option<DrawUpdate> {
-        debug!(target: "ui::mesh", "Mesh::draw()");
+        debug!(target: "ui::vector_art", "VectorArt::draw()");
         // Only used for debug messages
         let node = sg.get_node(self.node_id).unwrap();
 
-        let mesh = GfxDrawMesh {
-            vertex_buffer: self.vertex_buffer,
-            index_buffer: self.index_buffer,
-            texture: None,
-            num_elements: self.num_elements,
-        };
-
         if let Err(err) = eval_rect(self.rect.clone(), parent_rect) {
             panic!("Node {:?} bad rect property: {}", node, err);
         }
@@ -130,10 +122,29 @@ impl VectorArt {
         rect.x += parent_rect.x;
         rect.y += parent_rect.x;
 
+        let verts = self.shape.eval(rect.w, rect.h).expect("bad shape");
+
+        let vertex_buffer = self.render_api.new_vertex_buffer(verts);
+        // You are one lazy motherfucker
+        let index_buffer = self.render_api.new_index_buffer(self.shape.indices.clone());
+        let mesh = GfxDrawMesh {
+            vertex_buffer,
+            index_buffer,
+            texture: None,
+            num_elements: self.shape.indices.len() as i32,
+        };
+
+        let old_mesh = std::mem::replace(&mut *self.buffers.lock().unwrap(), Some(mesh.clone()));
+        let mut freed_buffers = vec![];
+        if let Some(old_mesh) = old_mesh {
+            freed_buffers.push(old_mesh.vertex_buffer);
+            freed_buffers.push(old_mesh.index_buffer);
+        }
+
         let off_x = rect.x / parent_rect.w;
         let off_y = rect.y / parent_rect.h;
-        let scale_x = rect.w / parent_rect.w;
-        let scale_y = rect.h / parent_rect.h;
+        let scale_x = 1. / parent_rect.w;
+        let scale_y = 1. / parent_rect.h;
         let model = glam::Mat4::from_translation(glam::Vec3::new(off_x, off_y, 0.)) *
             glam::Mat4::from_scale(glam::Vec3::new(scale_x, scale_y, 1.));
 
@@ -151,7 +162,7 @@ impl VectorArt {
                 },
             )],
             freed_textures: vec![],
-            freed_buffers: vec![],
+            freed_buffers,
         })
     }
 }
@@ -162,7 +173,9 @@ impl Stoppable for VectorArt {
 
         // Free buffers
         // Should this be in drop?
-        self.render_api.delete_buffer(self.vertex_buffer);
-        self.render_api.delete_buffer(self.index_buffer);
+        if let Some(mesh) = &*self.buffers.lock().unwrap() {
+            self.render_api.delete_buffer(mesh.vertex_buffer);
+            self.render_api.delete_buffer(mesh.index_buffer);
+        }
     }
 }

+ 157 - 0
bin/darkwallet/src/ui/vector_art/shape.rs

@@ -0,0 +1,157 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2024 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use crate::{
+    error::{Error, Result},
+    expr::{Op, SExprCode, SExprMachine, SExprVal},
+    gfx::{
+        GfxBufferId, GfxDrawCall, GfxDrawInstruction, GfxDrawMesh, Rectangle, RenderApiPtr, Vertex,
+    },
+    mesh::Color,
+    prop::{PropertyPtr, PropertyUint32, Role},
+    scene::{Pimpl, SceneGraph, SceneGraphPtr2, SceneNodeId},
+    util::enumerate,
+    ExecutorPtr,
+};
+
+pub fn const_f32(x: f32) -> SExprCode {
+    vec![Op::ConstFloat32(x)]
+}
+pub fn load_var<S: Into<String>>(var: S) -> SExprCode {
+    vec![Op::LoadVar(var.into())]
+}
+
+#[derive(Debug)]
+pub struct ShapeVertex {
+    x: SExprCode,
+    y: SExprCode,
+    color: Color,
+}
+
+impl ShapeVertex {
+    pub fn new(x: SExprCode, y: SExprCode, color: Color) -> Self {
+        Self { x, y, color }
+    }
+
+    pub fn from_xy(x: f32, y: f32, color: Color) -> Self {
+        Self { x: vec![Op::ConstFloat32(x)], y: vec![Op::ConstFloat32(y)], color }
+    }
+}
+
+#[derive(Debug)]
+pub struct VectorShape {
+    pub verts: Vec<ShapeVertex>,
+    pub indices: Vec<u16>,
+}
+
+impl VectorShape {
+    pub fn new() -> Self {
+        Self { verts: vec![], indices: vec![] }
+    }
+
+    pub fn eval(&self, w: f32, h: f32) -> Result<Vec<Vertex>> {
+        let mut verts = vec![];
+        for shape_vert in &self.verts {
+            let mut pos = [0.; 2];
+            for (i, shape_X) in [(0, &shape_vert.x), (1, &shape_vert.y)] {
+                let machine = SExprMachine {
+                    globals: vec![
+                        ("w".to_string(), SExprVal::Float32(w)),
+                        ("h".to_string(), SExprVal::Float32(h)),
+                    ],
+                    stmts: shape_X,
+                };
+                pos[i] = machine.call()?.as_f32()?;
+            }
+
+            let vert = Vertex { pos, color: shape_vert.color.clone(), uv: [0., 0.] };
+            verts.push(vert);
+        }
+        Ok(verts)
+    }
+
+    pub fn add_filled_box(
+        &mut self,
+        x1: SExprCode,
+        y1: SExprCode,
+        x2: SExprCode,
+        y2: SExprCode,
+        color: Color,
+    ) {
+        let mut verts = vec![
+            ShapeVertex::new(x1.clone(), y1.clone(), color.clone()),
+            ShapeVertex::new(x2.clone(), y1.clone(), color.clone()),
+            ShapeVertex::new(x1.clone(), y2.clone(), color.clone()),
+            ShapeVertex::new(x2, y2, color),
+        ];
+        let i = self.verts.len() as u16;
+        let mut indices = vec![i + 0, i + 2, i + 1, i + 1, i + 2, i + 3];
+        self.verts.append(&mut verts);
+        self.indices.append(&mut indices);
+    }
+
+    // s-expr surgery
+    fn sexpr_add(mut x: SExprCode, border_px: f32) -> Option<SExprCode> {
+        let eqn = x.pop()?;
+        x.push(Op::Add((Box::new(eqn), Box::new(Op::ConstFloat32(border_px)))));
+        Some(x)
+    }
+
+    pub fn add_outline(
+        &mut self,
+        x1: SExprCode,
+        y1: SExprCode,
+        x2: SExprCode,
+        y2: SExprCode,
+        border_px: f32,
+        color: Color,
+    ) {
+        // LHS
+        self.add_filled_box(
+            x1.clone(),
+            y1.clone(),
+            Self::sexpr_add(x1.clone(), border_px).unwrap(),
+            y2.clone(),
+            color.clone(),
+        );
+        // THS
+        self.add_filled_box(
+            x1.clone(),
+            y1.clone(),
+            x2.clone(),
+            Self::sexpr_add(y1.clone(), border_px).unwrap(),
+            color.clone(),
+        );
+        // RHS
+        self.add_filled_box(
+            Self::sexpr_add(x2.clone(), -border_px).unwrap(),
+            y1.clone(),
+            x2.clone(),
+            y2.clone(),
+            color.clone(),
+        );
+        // BHS
+        self.add_filled_box(
+            x1.clone(),
+            Self::sexpr_add(y2.clone(), -border_px).unwrap(),
+            x2.clone(),
+            y2.clone(),
+            color.clone(),
+        );
+    }
+}