Browse Source

app: make a bunch of style changes and dont show back button for wallet on main screen

darkfi 1 day ago
parent
commit
f151b6a3f8

+ 53 - 15
bin/app/src/app/schema/chat.rs

@@ -101,6 +101,23 @@ mod android_ui_consts {
     pub const ACTION_PADDING: f32 = 32.;
     pub const ACTION_PADDING: f32 = 32.;
     pub const ACTION_SPACING: f32 = 8.;
     pub const ACTION_SPACING: f32 = 8.;
     pub const BACK_SEP_W: f32 = 1.;
     pub const BACK_SEP_W: f32 = 1.;
+
+    pub const COPY_BTN_SCALE: f32 = 40.;
+    pub const COPY_BTN_X_OFF: f32 = 60.;
+    pub const COPY_BTN_Y: f32 = 70.;
+
+    pub const SELECT_CLOSE_X: f32 = 70.;
+    pub const SELECT_CLOSE_Y: f32 = 70.;
+    pub const SELECT_CLOSE_SCALE: f32 = 20.;
+
+    // Down arrow (scroll to bottom) overlay
+    pub const DOWNARROW_NEG_X: f32 = 240.;
+    pub const DOWNARROW_NEG_Y: f32 = 450.;
+    pub const DOWNARROW_W: f32 = 200.;
+    pub const DOWNARROW_H: f32 = 200.;
+    pub const DOWNBG_X: f32 = 100.;
+    pub const DOWNBG_Y: f32 = 100.;
+    pub const DOWNARROW_SCALE: f32 = 100.;
 }
 }
 
 
 #[cfg(target_os = "android")]
 #[cfg(target_os = "android")]
@@ -170,6 +187,20 @@ mod ui_consts {
 
 
     pub const COPY_BTN_SCALE: f32 = 20.;
     pub const COPY_BTN_SCALE: f32 = 20.;
     pub const COPY_BTN_X_OFF: f32 = 30.;
     pub const COPY_BTN_X_OFF: f32 = 30.;
+    pub const COPY_BTN_Y: f32 = 27.;
+
+    pub const SELECT_CLOSE_X: f32 = 40.;
+    pub const SELECT_CLOSE_Y: f32 = 30.;
+    pub const SELECT_CLOSE_SCALE: f32 = 10.;
+
+    // Down arrow (scroll to bottom) overlay
+    pub const DOWNARROW_NEG_X: f32 = 120.;
+    pub const DOWNARROW_NEG_Y: f32 = 250.;
+    pub const DOWNARROW_W: f32 = 100.;
+    pub const DOWNARROW_H: f32 = 100.;
+    pub const DOWNBG_X: f32 = 50.;
+    pub const DOWNBG_Y: f32 = 50.;
+    pub const DOWNARROW_SCALE: f32 = 50.;
 }
 }
 
 
 use super::{EMOJI_PICKER_ICON_MARGIN_X, EMOJI_PICKER_ICON_MARGIN_Y, EMOJI_PICKER_ICON_SIZE};
 use super::{EMOJI_PICKER_ICON_MARGIN_X, EMOJI_PICKER_ICON_MARGIN_Y, EMOJI_PICKER_ICON_SIZE};
@@ -227,6 +258,8 @@ pub async fn make(
     cc.add_const_f32("SENDBTN_BOX_1", SENDBTN_BOX[1]);
     cc.add_const_f32("SENDBTN_BOX_1", SENDBTN_BOX[1]);
     cc.add_const_f32("CMD_HELP_HEIGHT", CMD_HELP_HEIGHT);
     cc.add_const_f32("CMD_HELP_HEIGHT", CMD_HELP_HEIGHT);
     cc.add_const_f32("CMD_HELP_GAP", CMD_HELP_GAP);
     cc.add_const_f32("CMD_HELP_GAP", CMD_HELP_GAP);
+    cc.add_const_f32("DOWNARROW_NEG_X", DOWNARROW_NEG_X);
+    cc.add_const_f32("DOWNARROW_NEG_Y", DOWNARROW_NEG_Y);
     cc.add_const_f32("NETSTATUS_ICON_SIZE", super::NETSTATUS_ICON_SIZE);
     cc.add_const_f32("NETSTATUS_ICON_SIZE", super::NETSTATUS_ICON_SIZE);
 
 
     // Main view
     // Main view
@@ -673,15 +706,14 @@ pub async fn make(
     });
     });
     layer_node.push_task(listen_file_download);
     layer_node.push_task(listen_file_download);
 
 
-    // Root content layer
     let down_layer = create_layer("chat_down_arrow");
     let down_layer = create_layer("chat_down_arrow");
     let prop = down_layer.get_property("rect").unwrap();
     let prop = down_layer.get_property("rect").unwrap();
-    let code = cc.compile("w - 120").unwrap();
+    let code = cc.compile("w - DOWNARROW_NEG_X").unwrap();
     prop.set_expr(atom, Role::App, 0, code).unwrap();
     prop.set_expr(atom, Role::App, 0, code).unwrap();
-    let code = cc.compile("h / 2").unwrap();
+    let code = cc.compile("h - DOWNARROW_NEG_Y").unwrap();
     prop.set_expr(atom, Role::App, 1, code).unwrap();
     prop.set_expr(atom, Role::App, 1, code).unwrap();
-    prop.set_f32(atom, Role::App, 2, 100.).unwrap();
-    prop.set_f32(atom, Role::App, 3, 100.).unwrap();
+    prop.set_f32(atom, Role::App, 2, DOWNARROW_W).unwrap();
+    prop.set_f32(atom, Role::App, 3, DOWNARROW_H).unwrap();
     down_layer.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
     down_layer.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
     down_layer.set_property_u32(atom, Role::App, "z_index", 3).unwrap();
     down_layer.set_property_u32(atom, Role::App, "z_index", 3).unwrap();
     let down_layer = down_layer.setup(|me| Layer::new(me, renderer.clone(), redraw.clone())).await;
     let down_layer = down_layer.setup(|me| Layer::new(me, renderer.clone(), redraw.clone())).await;
@@ -709,13 +741,14 @@ pub async fn make(
     // Placeholder single-color background filling the whole overlay
     // Placeholder single-color background filling the whole overlay
     let node = create_vector_art("downbg");
     let node = create_vector_art("downbg");
     let prop = node.get_property("rect").unwrap();
     let prop = node.get_property("rect").unwrap();
-    prop.set_f32(atom, Role::App, 0, 50.).unwrap();
-    prop.set_f32(atom, Role::App, 1, 50.).unwrap();
-    prop.set_f32(atom, Role::App, 2, 100.).unwrap();
-    prop.set_f32(atom, Role::App, 3, 100.).unwrap();
+    prop.set_f32(atom, Role::App, 0, DOWNBG_X).unwrap();
+    prop.set_f32(atom, Role::App, 1, DOWNBG_Y).unwrap();
+    prop.set_f32(atom, Role::App, 2, DOWNARROW_W).unwrap();
+    prop.set_f32(atom, Role::App, 3, DOWNARROW_H).unwrap();
     node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
     node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
     node.set_property_u32(atom, Role::App, "z_index", 0).unwrap();
     node.set_property_u32(atom, Role::App, "z_index", 0).unwrap();
-    let mut shape = shape::create_down_bgtab(rgba!(0x003232ff), rgba!(0x294f60ff), 0.1).scaled(50.);
+    let mut shape =
+        shape::create_down_bgtab(rgba!(0x003232ff), rgba!(0x294f60ff), 0.1).scaled(DOWNARROW_SCALE);
     shape.join(shape::create_down_arrow(rgba!(0x00f0ffff), 1.));
     shape.join(shape::create_down_arrow(rgba!(0x00f0ffff), 1.));
     let node = node.setup(|me| VectorArt::new(me, shape, renderer.clone(), redraw.clone())).await;
     let node = node.setup(|me| VectorArt::new(me, shape, renderer.clone(), redraw.clone())).await;
     down_layer.link(node);
     down_layer.link(node);
@@ -726,8 +759,8 @@ pub async fn make(
     let prop = node.get_property("rect").unwrap();
     let prop = node.get_property("rect").unwrap();
     prop.set_f32(atom, Role::App, 0, 0.).unwrap();
     prop.set_f32(atom, Role::App, 0, 0.).unwrap();
     prop.set_f32(atom, Role::App, 1, 0.).unwrap();
     prop.set_f32(atom, Role::App, 1, 0.).unwrap();
-    prop.set_f32(atom, Role::App, 2, 100.).unwrap();
-    prop.set_f32(atom, Role::App, 3, 100.).unwrap();
+    prop.set_f32(atom, Role::App, 2, DOWNARROW_W).unwrap();
+    prop.set_f32(atom, Role::App, 3, DOWNARROW_H).unwrap();
     let (slot, recvr) = Slot::new("scroll_bottom");
     let (slot, recvr) = Slot::new("scroll_bottom");
     node.register("click", slot).unwrap();
     node.register("click", slot).unwrap();
     let redraw2 = redraw.clone();
     let redraw2 = redraw.clone();
@@ -771,11 +804,16 @@ pub async fn make(
     shape.add_filled_box(
     shape.add_filled_box(
         expr::const_f32(0.),
         expr::const_f32(0.),
         expr::const_f32(0.),
         expr::const_f32(0.),
-        expr::const_f32(EMOJI_BG_W),
+        expr::const_f32(BACKARROW_BG_W),
         expr::load_var("h"),
         expr::load_var("h"),
         rgba!(0x1e0000ff),
         rgba!(0x1e0000ff),
     );
     );
-    shape.join(shape::create_x(Point::new(40., 30.), 10., 1., COLOR_RED));
+    shape.join(shape::create_x(
+        Point::new(SELECT_CLOSE_X, SELECT_CLOSE_Y),
+        SELECT_CLOSE_SCALE,
+        1.,
+        COLOR_RED,
+    ));
     let node = node.setup(|me| VectorArt::new(me, shape, renderer.clone(), redraw.clone())).await;
     let node = node.setup(|me| VectorArt::new(me, shape, renderer.clone(), redraw.clone())).await;
     select_layer.link(node);
     select_layer.link(node);
 
 
@@ -783,7 +821,7 @@ pub async fn make(
     let prop = node.get_property("rect").unwrap();
     let prop = node.get_property("rect").unwrap();
     let code = cc.compile("w - COPY_BTN_X_OFF").unwrap();
     let code = cc.compile("w - COPY_BTN_X_OFF").unwrap();
     prop.set_expr(atom, Role::App, 0, code).unwrap();
     prop.set_expr(atom, Role::App, 0, code).unwrap();
-    prop.set_f32(atom, Role::App, 1, 27.).unwrap();
+    prop.set_f32(atom, Role::App, 1, COPY_BTN_Y).unwrap();
     prop.set_f32(atom, Role::App, 2, 400.).unwrap();
     prop.set_f32(atom, Role::App, 2, 400.).unwrap();
     prop.set_f32(atom, Role::App, 3, CHATEDIT_HEIGHT).unwrap();
     prop.set_f32(atom, Role::App, 3, CHATEDIT_HEIGHT).unwrap();
     node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
     node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();

+ 0 - 2
bin/app/src/app/schema/wallet/data.rs

@@ -46,7 +46,6 @@ mod android_ui_consts {
     pub const HEADER_HEIGHT: f32 = 140.;
     pub const HEADER_HEIGHT: f32 = 140.;
     pub const BACK_SEP_W: f32 = 1.;
     pub const BACK_SEP_W: f32 = 1.;
     pub const ROW_HEIGHT: f32 = 80.;
     pub const ROW_HEIGHT: f32 = 80.;
-    pub const WALLET_BTN_SIZE: f32 = 50.;
     pub const COPY_WIDTH: f32 = 200.;
     pub const COPY_WIDTH: f32 = 200.;
     pub const COPY_SCALE: f32 = 30.;
     pub const COPY_SCALE: f32 = 30.;
 
 
@@ -103,7 +102,6 @@ mod ui_consts {
     pub const HEADER_HEIGHT: f32 = 60.;
     pub const HEADER_HEIGHT: f32 = 60.;
     pub const BACK_SEP_W: f32 = 0.5;
     pub const BACK_SEP_W: f32 = 0.5;
     pub const ROW_HEIGHT: f32 = 80.;
     pub const ROW_HEIGHT: f32 = 80.;
-    pub const WALLET_BTN_SIZE: f32 = 50.;
     pub const COPY_WIDTH: f32 = 100.;
     pub const COPY_WIDTH: f32 = 100.;
     pub const COPY_SCALE: f32 = 15.;
     pub const COPY_SCALE: f32 = 15.;
 
 

+ 145 - 12
bin/app/src/app/schema/wallet/main.rs

@@ -25,7 +25,7 @@ use crate::{
         App,
         App,
     },
     },
     expr,
     expr,
-    gfx::gfxtag,
+    gfx::{gfxtag, Point},
     mesh::{COLOR_CYAN, COLOR_TEAL},
     mesh::{COLOR_CYAN, COLOR_TEAL},
     prop::{PropertyAtomicGuard, PropertyBool, PropertyFloat32, Role},
     prop::{PropertyAtomicGuard, PropertyBool, PropertyFloat32, Role},
     scene::{SceneNodePtr, Slot},
     scene::{SceneNodePtr, Slot},
@@ -78,22 +78,45 @@ pub async fn make(
     // Back arrow
     // Back arrow
     let node = create_vector_art("wallet_back_btn_bg");
     let node = create_vector_art("wallet_back_btn_bg");
     let prop = node.get_property("rect").unwrap();
     let prop = node.get_property("rect").unwrap();
-    prop.set_f32(atom, Role::App, 0, BACKARROW_X).unwrap();
-    prop.set_f32(atom, Role::App, 1, BACKARROW_Y).unwrap();
-    prop.set_f32(atom, Role::App, 2, BACKARROW_SCALE).unwrap();
-    prop.set_f32(atom, Role::App, 3, BACKARROW_SCALE).unwrap();
+    prop.set_f32(atom, Role::App, 0, 0.).unwrap();
+    prop.set_f32(atom, Role::App, 1, 0.).unwrap();
+    prop.set_f32(atom, Role::App, 2, BACKARROW_BG_W + BACK_SEP_W).unwrap();
+    prop.set_f32(atom, Role::App, 3, HEADER_HEIGHT).unwrap();
     node.set_property_u32(atom, Role::App, "z_index", 3).unwrap();
     node.set_property_u32(atom, Role::App, "z_index", 3).unwrap();
-    let arrow_shape = shape::create_back_arrow().scaled(BACKARROW_SCALE);
+    node.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
+    let mut shape = VectorShape::new();
+    let (_bg_color, sep_color) = match COLOR_SCHEME {
+        ColorScheme::DarkMode => ([0., 0., 0., 1.], [0.41, 0.6, 0.65, 1.]),
+        ColorScheme::PaperLight => ([1., 1., 1., 1.], [0., 0.6, 0.65, 1.]),
+    };
+    shape.add_filled_box(
+        expr::const_f32(0.),
+        expr::const_f32(0.),
+        expr::const_f32(BACKARROW_BG_W),
+        expr::load_var("h"),
+        [0.0, 0.106, 0.114, 1.0],
+    );
+    shape.add_filled_box(
+        expr::const_f32(BACKARROW_BG_W),
+        expr::const_f32(0.),
+        expr::const_f32(BACKARROW_BG_W + BACK_SEP_W),
+        expr::load_var("h"),
+        sep_color,
+    );
+    shape.join(
+        shape::create_back_arrow()
+            .scaled(BACKARROW_SCALE)
+            .offset(Point::new(BACKARROW_X, BACKARROW_Y)),
+    );
     let node = node
     let node = node
-        .setup(|me| {
-            VectorArt::new(me, arrow_shape, app.renderer.clone(), app.redraw_trigger.clone())
-        })
+        .setup(|me| VectorArt::new(me, shape, app.renderer.clone(), app.redraw_trigger.clone()))
         .await;
         .await;
-    main_layer.link(node);
+    wallet_layer.link(node.clone());
+    let back_bg_is_visible = PropertyBool::wrap(&node, Role::App, "is_visible", 0).unwrap();
 
 
     // Back button
     // Back button
     let node = create_button("wallet_back_btn");
     let node = create_button("wallet_back_btn");
-    node.set_property_bool(atom, Role::App, "is_active", true).unwrap();
+    node.set_property_bool(atom, Role::App, "is_active", false).unwrap();
     node.set_property_u32(atom, Role::App, "z_index", 10).unwrap();
     node.set_property_u32(atom, Role::App, "z_index", 10).unwrap();
     node.set_property_u32(atom, Role::App, "priority", 10).unwrap();
     node.set_property_u32(atom, Role::App, "priority", 10).unwrap();
     let prop = node.get_property("rect").unwrap();
     let prop = node.get_property("rect").unwrap();
@@ -101,9 +124,119 @@ pub async fn make(
     prop.set_f32(atom, Role::App, 1, 0.).unwrap();
     prop.set_f32(atom, Role::App, 1, 0.).unwrap();
     prop.set_f32(atom, Role::App, 2, BACKARROW_BG_W).unwrap();
     prop.set_f32(atom, Role::App, 2, BACKARROW_BG_W).unwrap();
     prop.set_f32(atom, Role::App, 3, HEADER_HEIGHT).unwrap();
     prop.set_f32(atom, Role::App, 3, HEADER_HEIGHT).unwrap();
+
+    let redraw = app.redraw_trigger.clone();
+    let sg_root = app.sg_root.clone();
+    let main_is_visible1 = main_is_visible.clone();
+    let (slot, recvr) = Slot::new("wallet_back_clicked");
+    node.register("click", slot).unwrap();
+    let listen_click = app.ex.spawn(async move {
+        while recvr.recv().await.is_ok() {
+            let atom = &mut redraw.make_guard(gfxtag!("wallet back button"));
+            let tx_status_is_visible = PropertyBool::wrap(
+                &sg_root.lookup_node("/window/content/wallet/tx_status_layer").unwrap(),
+                Role::App,
+                "is_visible",
+                0,
+            )
+            .unwrap();
+            let step4_is_visible = PropertyBool::wrap(
+                &sg_root.lookup_node("/window/content/wallet/send_step4_layer").unwrap(),
+                Role::App,
+                "is_visible",
+                0,
+            )
+            .unwrap();
+            let step3_is_visible = PropertyBool::wrap(
+                &sg_root.lookup_node("/window/content/wallet/send_step3_layer").unwrap(),
+                Role::App,
+                "is_visible",
+                0,
+            )
+            .unwrap();
+            let step2_is_visible = PropertyBool::wrap(
+                &sg_root.lookup_node("/window/content/wallet/send_step2_layer").unwrap(),
+                Role::App,
+                "is_visible",
+                0,
+            )
+            .unwrap();
+            let step1_is_visible = PropertyBool::wrap(
+                &sg_root.lookup_node("/window/content/wallet/send_step1_layer").unwrap(),
+                Role::App,
+                "is_visible",
+                0,
+            )
+            .unwrap();
+            let receive_is_visible = PropertyBool::wrap(
+                &sg_root.lookup_node("/window/content/wallet/receive_layer").unwrap(),
+                Role::App,
+                "is_visible",
+                0,
+            )
+            .unwrap();
+
+            if tx_status_is_visible.get() {
+                tx_status_is_visible.set(atom, false);
+                main_is_visible1.set(atom, true);
+                continue
+            }
+
+            if step4_is_visible.get() {
+                step4_is_visible.set(atom, false);
+                step3_is_visible.set(atom, true);
+                continue
+            }
+
+            if step3_is_visible.get() {
+                if let Some(error_node) =
+                    sg_root.lookup_node("/window/content/wallet/send_step3_layer/error")
+                {
+                    error_node.set_property_str(atom, Role::App, "text", "").unwrap();
+                }
+                step3_is_visible.set(atom, false);
+                step2_is_visible.set(atom, true);
+                continue
+            }
+
+            if step2_is_visible.get() {
+                step2_is_visible.set(atom, false);
+                step1_is_visible.set(atom, true);
+                continue
+            }
+
+            if step1_is_visible.get() {
+                step1_is_visible.set(atom, false);
+                main_is_visible1.set(atom, true);
+                continue
+            }
+
+            if receive_is_visible.get() {
+                receive_is_visible.set(atom, false);
+                main_is_visible1.set(atom, true);
+            }
+        }
+    });
+    app.tasks.lock().unwrap().push(listen_click);
+
     let node =
     let node =
         node.setup(|me| Button::new(me, app.renderer.clone(), app.redraw_trigger.clone())).await;
         node.setup(|me| Button::new(me, app.renderer.clone(), app.redraw_trigger.clone())).await;
-    main_layer.link(node);
+    wallet_layer.link(node.clone());
+    let back_btn_is_active = PropertyBool::wrap(&node, Role::App, "is_active", 0).unwrap();
+
+    // Show the back button whenever the main wallet screen is hidden
+    let redraw = app.redraw_trigger.clone();
+    let main_is_visible2 = main_is_visible.clone();
+    let main_is_visible_sub = main_is_visible.prop().subscribe_modify();
+    let listen_main_visible = app.ex.spawn(async move {
+        while let Ok(_) = main_is_visible_sub.receive().await {
+            let atom = &mut redraw.make_guard(gfxtag!("wallet back button visibility"));
+            let visible = !main_is_visible2.get();
+            back_bg_is_visible.set(atom, visible);
+            back_btn_is_active.set(atom, visible);
+        }
+    });
+    app.tasks.lock().unwrap().push(listen_main_visible);
 
 
     create_chat_btn(app, atom, &cc, &main_layer).await;
     create_chat_btn(app, atom, &cc, &main_layer).await;
 
 

+ 1 - 45
bin/app/src/app/schema/wallet/receive.rs

@@ -23,9 +23,8 @@ use crate::{
         App,
         App,
     },
     },
     expr,
     expr,
-    gfx::gfxtag,
     mesh::COLOR_CYAN,
     mesh::COLOR_CYAN,
-    prop::{PropertyAtomicGuard, PropertyBool, PropertyFloat32, PropertyStr, Role},
+    prop::{PropertyAtomicGuard, PropertyFloat32, PropertyStr, Role},
     scene::{SceneNodePtr, Slot},
     scene::{SceneNodePtr, Slot},
     shape,
     shape,
     ui::{Button, Layer, Text, VectorArt},
     ui::{Button, Layer, Text, VectorArt},
@@ -46,8 +45,6 @@ pub async fn make(
     cc.add_const_f32("PADDING_Y", PADDING_Y);
     cc.add_const_f32("PADDING_Y", PADDING_Y);
     cc.add_const_f32("COPY_WIDTH", COPY_WIDTH);
     cc.add_const_f32("COPY_WIDTH", COPY_WIDTH);
 
 
-    let main_layer = wallet_layer.lookup_node("/main_layer").unwrap();
-
     // Receive layer
     // Receive layer
     let receive_layer = create_layer("receive_layer");
     let receive_layer = create_layer("receive_layer");
     let prop = receive_layer.get_property("rect").unwrap();
     let prop = receive_layer.get_property("rect").unwrap();
@@ -65,49 +62,8 @@ pub async fn make(
     create_bg_mesh(app, atom, &receive_layer, "receive_bg").await;
     create_bg_mesh(app, atom, &receive_layer, "receive_bg").await;
     create_header_bg(app, atom, &receive_layer, "receive_header_bg").await;
     create_header_bg(app, atom, &receive_layer, "receive_header_bg").await;
 
 
-    // Back button
-    let node = create_vector_art("receive_back_btn_bg");
-    let prop = node.get_property("rect").unwrap();
-    prop.set_f32(atom, Role::App, 0, BACKARROW_X).unwrap();
-    prop.set_f32(atom, Role::App, 1, BACKARROW_Y).unwrap();
-    prop.set_f32(atom, Role::App, 2, 500.).unwrap();
-    prop.set_f32(atom, Role::App, 3, 500.).unwrap();
-    node.set_property_u32(atom, Role::App, "z_index", 2).unwrap();
-    let shape = shape::create_back_arrow().scaled(BACKARROW_SCALE);
-    let node = node
-        .setup(|me| VectorArt::new(me, shape, app.renderer.clone(), app.redraw_trigger.clone()))
-        .await;
-    receive_layer.link(node);
-
     let mut y = 0.;
     let mut y = 0.;
 
 
-    let node = create_button("receive_back_btn");
-    node.set_property_bool(atom, Role::App, "is_active", true).unwrap();
-    let prop = node.get_property("rect").unwrap();
-    prop.set_f32(atom, Role::App, 0, 0.).unwrap();
-    prop.set_f32(atom, Role::App, 1, y).unwrap();
-    prop.set_f32(atom, Role::App, 2, WALLET_BTN_SIZE * 2.).unwrap();
-    prop.set_f32(atom, Role::App, 3, HEADER_HEIGHT).unwrap();
-
-    let main_is_visible = PropertyBool::wrap(&main_layer, Role::App, "is_visible", 0).unwrap();
-    let receive_is_visible =
-        PropertyBool::wrap(&receive_layer, Role::App, "is_visible", 0).unwrap();
-    let redraw = app.redraw_trigger.clone();
-    let (slot, recvr) = Slot::new("receive_back_clicked");
-    node.register("click", slot).unwrap();
-    let listen_click = app.ex.spawn(async move {
-        while let Ok(_) = recvr.recv().await {
-            let atom = &mut redraw.make_guard(gfxtag!("receive back button"));
-            receive_is_visible.set(atom, false);
-            main_is_visible.set(atom, true);
-        }
-    });
-    app.tasks.lock().unwrap().push(listen_click);
-
-    let node =
-        node.setup(|me| Button::new(me, app.renderer.clone(), app.redraw_trigger.clone())).await;
-    receive_layer.link(node);
-
     y += HEADER_HEIGHT;
     y += HEADER_HEIGHT;
 
 
     create_title(app, atom, &receive_layer, &window_scale, i18n_fish, "RECEIVE", &mut y).await;
     create_title(app, atom, &receive_layer, &window_scale, i18n_fish, "RECEIVE", &mut y).await;

+ 1 - 10
bin/app/src/app/schema/wallet/send.rs

@@ -50,42 +50,33 @@ pub async fn make(
     )
     )
     .await;
     .await;
 
 
-    let step1_is_visible =
-        PropertyBool::wrap(&send_step1_layer, Role::App, "is_visible", 0).unwrap();
-
     let send_step2_layer = send_step2::make(
     let send_step2_layer = send_step2::make(
         app,
         app,
         wallet_layer.clone(),
         wallet_layer.clone(),
         i18n_fish,
         i18n_fish,
         window_scale.clone(),
         window_scale.clone(),
         send_tx_data.clone(),
         send_tx_data.clone(),
-        step1_is_visible.clone(),
     )
     )
     .await;
     .await;
 
 
     let step2_is_visible =
     let step2_is_visible =
         PropertyBool::wrap(&send_step2_layer, Role::App, "is_visible", 0).unwrap();
         PropertyBool::wrap(&send_step2_layer, Role::App, "is_visible", 0).unwrap();
 
 
-    let send_step3_layer = send_step3::make(
+    let _send_step3_layer = send_step3::make(
         app,
         app,
         wallet_layer.clone(),
         wallet_layer.clone(),
         i18n_fish,
         i18n_fish,
         window_scale.clone(),
         window_scale.clone(),
         send_tx_data.clone(),
         send_tx_data.clone(),
-        step2_is_visible.clone(),
     )
     )
     .await;
     .await;
 
 
-    let step3_is_visible =
-        PropertyBool::wrap(&send_step3_layer, Role::App, "is_visible", 0).unwrap();
-
     let _send_step4_layer = send_step4::make(
     let _send_step4_layer = send_step4::make(
         app,
         app,
         wallet_layer.clone(),
         wallet_layer.clone(),
         i18n_fish,
         i18n_fish,
         window_scale.clone(),
         window_scale.clone(),
         send_tx_data.clone(),
         send_tx_data.clone(),
-        step3_is_visible.clone(),
     )
     )
     .await;
     .await;
 
 

+ 2 - 46
bin/app/src/app/schema/wallet/send_step1.rs

@@ -22,7 +22,7 @@ use darkfi_serial::Decodable;
 
 
 use crate::{
 use crate::{
     app::{
     app::{
-        node::{create_button, create_layer, create_tokentable, create_vector_art},
+        node::{create_layer, create_tokentable},
         schema::COLOR_SCHEME,
         schema::COLOR_SCHEME,
         App,
         App,
     },
     },
@@ -30,8 +30,7 @@ use crate::{
     gfx::gfxtag,
     gfx::gfxtag,
     prop::{PropertyAtomicGuard, PropertyBool, PropertyFloat32, Role},
     prop::{PropertyAtomicGuard, PropertyBool, PropertyFloat32, Role},
     scene::{SceneNodePtr, Slot},
     scene::{SceneNodePtr, Slot},
-    shape,
-    ui::{Button, Layer, TokenRow, TokenTable, VectorArt},
+    ui::{Layer, TokenRow, TokenTable},
     util::i18n::I18nBabelFish,
     util::i18n::I18nBabelFish,
 };
 };
 
 
@@ -58,8 +57,6 @@ pub async fn make(
     cc.add_const_f32("RECIPIENT_INPUT_MARGIN", RECIPIENT_INPUT_MARGIN);
     cc.add_const_f32("RECIPIENT_INPUT_MARGIN", RECIPIENT_INPUT_MARGIN);
     cc.add_const_f32("RECIPIENT_INPUT_PADDING_X", RECIPIENT_INPUT_PADDING_X);
     cc.add_const_f32("RECIPIENT_INPUT_PADDING_X", RECIPIENT_INPUT_PADDING_X);
 
 
-    let main_layer = wallet_layer.lookup_node("/main_layer").unwrap();
-
     // ============================================
     // ============================================
     // Step 1: Pick token layer
     // Step 1: Pick token layer
     // ============================================
     // ============================================
@@ -81,49 +78,8 @@ pub async fn make(
     create_bg_mesh(app, atom, &send_step1_layer, "send_bg").await;
     create_bg_mesh(app, atom, &send_step1_layer, "send_bg").await;
     create_header_bg(app, atom, &send_step1_layer, "send_header_bg").await;
     create_header_bg(app, atom, &send_step1_layer, "send_header_bg").await;
 
 
-    // Back button
-    let node = create_vector_art("send_back_btn_bg");
-    let prop = node.get_property("rect").unwrap();
-    prop.set_f32(atom, Role::App, 0, BACKARROW_X).unwrap();
-    prop.set_f32(atom, Role::App, 1, BACKARROW_Y).unwrap();
-    prop.set_f32(atom, Role::App, 2, 500.).unwrap();
-    prop.set_f32(atom, Role::App, 3, 500.).unwrap();
-    node.set_property_u32(atom, Role::App, "z_index", 2).unwrap();
-    let shape = shape::create_back_arrow().scaled(BACKARROW_SCALE);
-    let node = node
-        .setup(|me| VectorArt::new(me, shape, app.renderer.clone(), app.redraw_trigger.clone()))
-        .await;
-    send_step1_layer.link(node);
-
     let mut y = 0.;
     let mut y = 0.;
 
 
-    let node = create_button("send_back_btn");
-    node.set_property_bool(atom, Role::App, "is_active", true).unwrap();
-    let prop = node.get_property("rect").unwrap();
-    prop.set_f32(atom, Role::App, 0, 0.).unwrap();
-    prop.set_f32(atom, Role::App, 1, y).unwrap();
-    prop.set_f32(atom, Role::App, 2, WALLET_BTN_SIZE * 2.).unwrap();
-    prop.set_f32(atom, Role::App, 3, HEADER_HEIGHT).unwrap();
-
-    let main_is_visible = PropertyBool::wrap(&main_layer, Role::App, "is_visible", 0).unwrap();
-    let redraw = app.redraw_trigger.clone();
-    let main_is_visible1 = main_is_visible.clone();
-    let step1_is_visible1 = step1_is_visible.clone();
-    let (slot, recvr) = Slot::new("send_back_clicked");
-    node.register("click", slot).unwrap();
-    let listen_click = app.ex.spawn(async move {
-        while let Ok(_) = recvr.recv().await {
-            let atom = &mut redraw.make_guard(gfxtag!("send back button"));
-            main_is_visible1.set(atom, true);
-            step1_is_visible1.set(atom, false);
-        }
-    });
-    app.tasks.lock().unwrap().push(listen_click);
-
-    let node =
-        node.setup(|me| Button::new(me, app.renderer.clone(), app.redraw_trigger.clone())).await;
-    send_step1_layer.link(node);
-
     y += HEADER_HEIGHT;
     y += HEADER_HEIGHT;
 
 
     create_title(app, atom, &send_step1_layer, &window_scale, i18n_fish, "SEND", &mut y).await;
     create_title(app, atom, &send_step1_layer, &window_scale, i18n_fish, "SEND", &mut y).await;

+ 0 - 41
bin/app/src/app/schema/wallet/send_step2.rs

@@ -72,7 +72,6 @@ pub async fn make(
     i18n_fish: &I18nBabelFish,
     i18n_fish: &I18nBabelFish,
     window_scale: PropertyFloat32,
     window_scale: PropertyFloat32,
     send_tx_data: Arc<std::sync::Mutex<SendTxData>>,
     send_tx_data: Arc<std::sync::Mutex<SendTxData>>,
-    step1_is_visible: PropertyBool,
 ) -> SceneNodePtr {
 ) -> SceneNodePtr {
     let atom = &mut PropertyAtomicGuard::none();
     let atom = &mut PropertyAtomicGuard::none();
 
 
@@ -110,48 +109,8 @@ pub async fn make(
     create_bg_mesh(app, atom, &send_step2_layer, "send_bg2").await;
     create_bg_mesh(app, atom, &send_step2_layer, "send_bg2").await;
     create_header_bg(app, atom, &send_step2_layer, "send_header_bg2").await;
     create_header_bg(app, atom, &send_step2_layer, "send_header_bg2").await;
 
 
-    // Back button
-    let node = create_vector_art("send_back_btn_bg2");
-    let prop = node.get_property("rect").unwrap();
-    prop.set_f32(atom, Role::App, 0, BACKARROW_X).unwrap();
-    prop.set_f32(atom, Role::App, 1, BACKARROW_Y).unwrap();
-    prop.set_f32(atom, Role::App, 2, 500.).unwrap();
-    prop.set_f32(atom, Role::App, 3, 500.).unwrap();
-    node.set_property_u32(atom, Role::App, "z_index", 2).unwrap();
-    let shape = shape::create_back_arrow().scaled(BACKARROW_SCALE);
-    let node = node
-        .setup(|me| VectorArt::new(me, shape, app.renderer.clone(), app.redraw_trigger.clone()))
-        .await;
-    send_step2_layer.link(node);
-
     let mut y = 0.;
     let mut y = 0.;
 
 
-    let node = create_button("send_back_btn2");
-    node.set_property_bool(atom, Role::App, "is_active", true).unwrap();
-    let prop = node.get_property("rect").unwrap();
-    prop.set_f32(atom, Role::App, 0, 0.).unwrap();
-    prop.set_f32(atom, Role::App, 1, y).unwrap();
-    prop.set_f32(atom, Role::App, 2, WALLET_BTN_SIZE * 2.).unwrap();
-    prop.set_f32(atom, Role::App, 3, HEADER_HEIGHT).unwrap();
-
-    let step1_is_visible2 = step1_is_visible.clone();
-    let step2_is_visible1 = step2_is_visible.clone();
-    let redraw = app.redraw_trigger.clone();
-    let (slot, recvr) = Slot::new("send_back_clicked2");
-    node.register("click", slot).unwrap();
-    let listen_click = app.ex.spawn(async move {
-        while let Ok(_) = recvr.recv().await {
-            let atom = &mut redraw.make_guard(gfxtag!("send step2 back button"));
-            step2_is_visible1.set(atom, false);
-            step1_is_visible2.set(atom, true);
-        }
-    });
-    app.tasks.lock().unwrap().push(listen_click);
-
-    let node =
-        node.setup(|me| Button::new(me, app.renderer.clone(), app.redraw_trigger.clone())).await;
-    send_step2_layer.link(node);
-
     y += HEADER_HEIGHT;
     y += HEADER_HEIGHT;
 
 
     create_title(app, atom, &send_step2_layer, &window_scale, i18n_fish, "SEND", &mut y).await;
     create_title(app, atom, &send_step2_layer, &window_scale, i18n_fish, "SEND", &mut y).await;

+ 2 - 54
bin/app/src/app/schema/wallet/send_step3.rs

@@ -23,10 +23,7 @@ use darkfi_serial::Encodable;
 
 
 use crate::{
 use crate::{
     app::{
     app::{
-        node::{
-            create_button, create_decimal_edit, create_layer, create_singleline_edit, create_text,
-            create_vector_art,
-        },
+        node::{create_decimal_edit, create_layer, create_singleline_edit, create_text},
         schema::COLOR_SCHEME,
         schema::COLOR_SCHEME,
         App,
         App,
     },
     },
@@ -35,8 +32,7 @@ use crate::{
     mesh::COLOR_CYAN,
     mesh::COLOR_CYAN,
     prop::{PropertyAtomicGuard, PropertyBool, PropertyFloat32, PropertyRect, Role},
     prop::{PropertyAtomicGuard, PropertyBool, PropertyFloat32, PropertyRect, Role},
     scene::{SceneNodePtr, Slot},
     scene::{SceneNodePtr, Slot},
-    shape,
-    ui::{BaseEdit, BaseEditType, Button, Layer, Text, VectorArt},
+    ui::{BaseEdit, BaseEditType, Layer, Text},
     util::i18n::I18nBabelFish,
     util::i18n::I18nBabelFish,
 };
 };
 
 
@@ -48,7 +44,6 @@ pub async fn make(
     i18n_fish: &I18nBabelFish,
     i18n_fish: &I18nBabelFish,
     window_scale: PropertyFloat32,
     window_scale: PropertyFloat32,
     send_tx_data: Arc<std::sync::Mutex<SendTxData>>,
     send_tx_data: Arc<std::sync::Mutex<SendTxData>>,
-    step2_is_visible: PropertyBool,
 ) -> SceneNodePtr {
 ) -> SceneNodePtr {
     let atom = &mut PropertyAtomicGuard::none();
     let atom = &mut PropertyAtomicGuard::none();
 
 
@@ -85,55 +80,8 @@ pub async fn make(
     create_bg_mesh(app, atom, &send_step3_layer, "send_bg3").await;
     create_bg_mesh(app, atom, &send_step3_layer, "send_bg3").await;
     create_header_bg(app, atom, &send_step3_layer, "send_header_bg3").await;
     create_header_bg(app, atom, &send_step3_layer, "send_header_bg3").await;
 
 
-    // Back button
-    let node = create_vector_art("send_back_btn_bg3");
-    let prop = node.get_property("rect").unwrap();
-    prop.set_f32(atom, Role::App, 0, BACKARROW_X).unwrap();
-    prop.set_f32(atom, Role::App, 1, BACKARROW_Y).unwrap();
-    prop.set_f32(atom, Role::App, 2, 500.).unwrap();
-    prop.set_f32(atom, Role::App, 3, 500.).unwrap();
-    node.set_property_u32(atom, Role::App, "z_index", 2).unwrap();
-    let shape = shape::create_back_arrow().scaled(BACKARROW_SCALE);
-    let node = node
-        .setup(|me| VectorArt::new(me, shape, app.renderer.clone(), app.redraw_trigger.clone()))
-        .await;
-    send_step3_layer.link(node);
-
     let mut y = 0.;
     let mut y = 0.;
 
 
-    let node = create_button("send_back_btn3");
-    node.set_property_bool(atom, Role::App, "is_active", true).unwrap();
-    let prop = node.get_property("rect").unwrap();
-    prop.set_f32(atom, Role::App, 0, 0.).unwrap();
-    prop.set_f32(atom, Role::App, 1, y).unwrap();
-    prop.set_f32(atom, Role::App, 2, WALLET_BTN_SIZE * 2.).unwrap();
-    prop.set_f32(atom, Role::App, 3, HEADER_HEIGHT).unwrap();
-
-    let step2_is_visible3 = step2_is_visible.clone();
-    let step3_is_visible1 = step3_is_visible.clone();
-    let redraw = app.redraw_trigger.clone();
-    let sg_root2 = app.sg_root.clone();
-    let (slot, recvr) = Slot::new("send_back_clicked3");
-    node.register("click", slot).unwrap();
-    let listen_click = app.ex.spawn(async move {
-        while let Ok(_) = recvr.recv().await {
-            let atom = &mut redraw.make_guard(gfxtag!("send step3 back button"));
-            // Reset error message on back button click
-            if let Some(error_node) =
-                sg_root2.lookup_node("/window/content/wallet/send_step3_layer/error")
-            {
-                error_node.set_property_str(atom, Role::App, "text", "").unwrap();
-            }
-            step3_is_visible1.set(atom, false);
-            step2_is_visible3.set(atom, true);
-        }
-    });
-    app.tasks.lock().unwrap().push(listen_click);
-
-    let node =
-        node.setup(|me| Button::new(me, app.renderer.clone(), app.redraw_trigger.clone())).await;
-    send_step3_layer.link(node);
-
     y += HEADER_HEIGHT;
     y += HEADER_HEIGHT;
 
 
     create_title(app, atom, &send_step3_layer, &window_scale, i18n_fish, "SEND", &mut y).await;
     create_title(app, atom, &send_step3_layer, &window_scale, i18n_fish, "SEND", &mut y).await;

+ 2 - 44
bin/app/src/app/schema/wallet/send_step4.rs

@@ -22,7 +22,7 @@ use darkfi_serial::{Decodable, Encodable};
 
 
 use crate::{
 use crate::{
     app::{
     app::{
-        node::{create_button, create_layer, create_text, create_vector_art},
+        node::{create_layer, create_text},
         schema::COLOR_SCHEME,
         schema::COLOR_SCHEME,
         App,
         App,
     },
     },
@@ -30,8 +30,7 @@ use crate::{
     gfx::gfxtag,
     gfx::gfxtag,
     prop::{PropertyAtomicGuard, PropertyBool, PropertyFloat32, Role},
     prop::{PropertyAtomicGuard, PropertyBool, PropertyFloat32, Role},
     scene::{SceneNodePtr, Slot},
     scene::{SceneNodePtr, Slot},
-    shape,
-    ui::{Button, Layer, Text, VectorArt},
+    ui::{Layer, Text},
     util::i18n::I18nBabelFish,
     util::i18n::I18nBabelFish,
 };
 };
 
 
@@ -43,7 +42,6 @@ pub async fn make(
     i18n_fish: &I18nBabelFish,
     i18n_fish: &I18nBabelFish,
     window_scale: PropertyFloat32,
     window_scale: PropertyFloat32,
     send_tx_data: Arc<std::sync::Mutex<SendTxData>>,
     send_tx_data: Arc<std::sync::Mutex<SendTxData>>,
-    step3_is_visible: PropertyBool,
 ) -> SceneNodePtr {
 ) -> SceneNodePtr {
     let atom = &mut PropertyAtomicGuard::none();
     let atom = &mut PropertyAtomicGuard::none();
 
 
@@ -80,48 +78,8 @@ pub async fn make(
     create_bg_mesh(app, atom, &send_step4_layer, "send_bg4").await;
     create_bg_mesh(app, atom, &send_step4_layer, "send_bg4").await;
     create_header_bg(app, atom, &send_step4_layer, "send_header_bg4").await;
     create_header_bg(app, atom, &send_step4_layer, "send_header_bg4").await;
 
 
-    // Back button
-    let node = create_vector_art("send_back_btn_bg4");
-    let prop = node.get_property("rect").unwrap();
-    prop.set_f32(atom, Role::App, 0, BACKARROW_X).unwrap();
-    prop.set_f32(atom, Role::App, 1, BACKARROW_Y).unwrap();
-    prop.set_f32(atom, Role::App, 2, 500.).unwrap();
-    prop.set_f32(atom, Role::App, 3, 500.).unwrap();
-    node.set_property_u32(atom, Role::App, "z_index", 2).unwrap();
-    let shape = shape::create_back_arrow().scaled(BACKARROW_SCALE);
-    let node = node
-        .setup(|me| VectorArt::new(me, shape, app.renderer.clone(), app.redraw_trigger.clone()))
-        .await;
-    send_step4_layer.link(node);
-
     let mut y = 0.;
     let mut y = 0.;
 
 
-    let node = create_button("send_back_btn4");
-    node.set_property_bool(atom, Role::App, "is_active", true).unwrap();
-    let prop = node.get_property("rect").unwrap();
-    prop.set_f32(atom, Role::App, 0, 0.).unwrap();
-    prop.set_f32(atom, Role::App, 1, y).unwrap();
-    prop.set_f32(atom, Role::App, 2, WALLET_BTN_SIZE * 2.).unwrap();
-    prop.set_f32(atom, Role::App, 3, HEADER_HEIGHT).unwrap();
-
-    let step3_is_visible2 = step3_is_visible.clone();
-    let step4_is_visible1 = step4_is_visible.clone();
-    let redraw = app.redraw_trigger.clone();
-    let (slot, recvr) = Slot::new("send_back_clicked4");
-    node.register("click", slot).unwrap();
-    let listen_click = app.ex.spawn(async move {
-        while let Ok(_) = recvr.recv().await {
-            let atom = &mut redraw.make_guard(gfxtag!("send step4 back button"));
-            step4_is_visible1.set(atom, false);
-            step3_is_visible2.set(atom, true);
-        }
-    });
-    app.tasks.lock().unwrap().push(listen_click);
-
-    let node =
-        node.setup(|me| Button::new(me, app.renderer.clone(), app.redraw_trigger.clone())).await;
-    send_step4_layer.link(node);
-
     y += HEADER_HEIGHT;
     y += HEADER_HEIGHT;
 
 
     create_title(app, atom, &send_step4_layer, &window_scale, i18n_fish, "SEND", &mut y).await;
     create_title(app, atom, &send_step4_layer, &window_scale, i18n_fish, "SEND", &mut y).await;

+ 1 - 15
bin/app/src/app/schema/wallet/util.rs

@@ -294,20 +294,6 @@ pub async fn create_header_bg(
         expr::load_var("h"),
         expr::load_var("h"),
         bg_color,
         bg_color,
     );
     );
-    shape.add_filled_box(
-        expr::const_f32(0.),
-        expr::const_f32(0.),
-        expr::const_f32(BACKARROW_BG_W),
-        expr::load_var("h"),
-        [0.0, 0.106, 0.114, 1.0],
-    );
-    shape.add_filled_box(
-        expr::const_f32(BACKARROW_BG_W),
-        expr::const_f32(0.),
-        expr::const_f32(BACKARROW_BG_W + BACK_SEP_W),
-        expr::load_var("h"),
-        sep_color,
-    );
     shape.add_filled_box(
     shape.add_filled_box(
         expr::const_f32(0.),
         expr::const_f32(0.),
         expr::load_var("h"),
         expr::load_var("h"),
@@ -318,7 +304,7 @@ pub async fn create_header_bg(
     let color1 = [0., 0.17, 0.18, 0.5];
     let color1 = [0., 0.17, 0.18, 0.5];
     let color2 = [0., 0.88, 1., 0.];
     let color2 = [0., 0.88, 1., 0.];
     shape.add_smooth_vertical_gradient(
     shape.add_smooth_vertical_gradient(
-        expr::const_f32(BACKARROW_BG_W + 1.),
+        expr::const_f32(0.),
         expr::const_f32(0.),
         expr::const_f32(0.),
         expr::load_var("w"),
         expr::load_var("w"),
         cc.compile("h / 2").unwrap(),
         cc.compile("h / 2").unwrap(),

+ 30 - 0
bin/app/src/shape/cross.rs

@@ -0,0 +1,30 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2026 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::{
+    gfx::Point,
+    mesh::Color,
+    ui::VectorShape,
+};
+
+pub fn create_cross(center: Point, thickness: f32, color: Color) -> VectorShape {
+    let mut shape = VectorShape::new();
+    shape.add_line(Point::new(center.x - 1., center.y), Point::new(center.x + 1., center.y), thickness, color.clone());
+    shape.add_line(Point::new(center.x, center.y - 1.), Point::new(center.x, center.y + 1.), thickness, color);
+    shape
+}

+ 17 - 0
bin/app/src/ui/vector_art/shape.rs

@@ -48,6 +48,16 @@ impl ShapeVertex {
         y.push(Op::Mul((Box::new(Op::ConstFloat32(scale)), Box::new(last_y))));
         y.push(Op::Mul((Box::new(Op::ConstFloat32(scale)), Box::new(last_y))));
         Self { x, y, color: self.color }
         Self { x, y, color: self.color }
     }
     }
+
+    pub fn offset(mut self, off: Point) -> Self {
+        let last_x = self.x.pop().unwrap();
+        let last_y = self.y.pop().unwrap();
+        let mut x = self.x;
+        x.push(Op::Add((Box::new(last_x), Box::new(Op::ConstFloat32(off.x)))));
+        let mut y = self.y;
+        y.push(Op::Add((Box::new(last_y), Box::new(Op::ConstFloat32(off.y)))));
+        Self { x, y, color: self.color }
+    }
 }
 }
 
 
 // This is bullshit. We need something in expr to support joining exprs somehow. Subroutines.
 // This is bullshit. We need something in expr to support joining exprs somehow. Subroutines.
@@ -332,4 +342,11 @@ impl VectorShape {
             indices: self.indices,
             indices: self.indices,
         }
         }
     }
     }
+
+    pub fn offset(self, off: Point) -> Self {
+        Self {
+            verts: self.verts.into_iter().map(|v| v.offset(off)).collect(),
+            indices: self.indices,
+        }
+    }
 }
 }