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

+ 0 - 1
bin/darkwallet/Makefile

@@ -35,7 +35,6 @@ android: $(SRC) fonts
 	adb uninstall darkfi.darkwallet
 	adb install $(APK)
 	reset
-	ding 0
 	adb logcat -c
 	adb logcat -v color -s darkfi -s SAPP
 

+ 1 - 0
bin/darkwallet/src/app/mod.rs

@@ -42,6 +42,7 @@ use crate::{
 
 mod node;
 mod schema;
+mod schema_test;
 
 //fn print_type_of<T>(_: &T) {
 //    println!("{}", std::any::type_name::<T>())

+ 5 - 467
bin/darkwallet/src/app/schema.rs

@@ -50,8 +50,8 @@ mod android_ui_consts {
     pub const EDITCHAT_BOTTOM_PAD: f32 = 30.;
     pub const EDITCHAT_CURSOR_ASCENT: f32 = 50.;
     pub const EDITCHAT_CURSOR_DESCENT: f32 = 20.;
-    pub const EDITCHAT_SELECT_ASCENT: f32 = 40.;
-    pub const EDITCHAT_SELECT_DESCENT: f32 = 8.;
+    pub const EDITCHAT_SELECT_ASCENT: f32 = 50.;
+    pub const EDITCHAT_SELECT_DESCENT: f32 = 20.;
     pub const EDITCHAT_HANDLE_DESCENT: f32 = 10.;
     pub const EDITCHAT_LINESPACING: f32 = 70.;
     pub const TEXTBAR_BASELINE: f32 = 60.;
@@ -114,468 +114,6 @@ mod ui_consts {
 
 use ui_consts::*;
 
-pub(super) async fn make_test(app: &App, window: SceneNodePtr) {
-    let window_scale = PropertyFloat32::wrap(&window, Role::Internal, "scale", 0).unwrap();
-
-    let mut cc = Compiler::new();
-
-    // Create a layer called view
-    let layer_node = create_layer("view");
-    let prop = layer_node.get_property("rect").unwrap();
-    prop.set_f32(Role::App, 0, 0.).unwrap();
-    prop.set_f32(Role::App, 1, 0.).unwrap();
-    prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
-    prop.set_expr(Role::App, 3, expr::load_var("h")).unwrap();
-    layer_node.set_property_bool(Role::App, "is_visible", true).unwrap();
-    let layer_node =
-        layer_node.setup(|me| Layer::new(me, app.render_api.clone(), app.ex.clone())).await;
-    window.link(layer_node.clone());
-
-    // Create a bg mesh
-    let node = create_vector_art("bg");
-    let prop = node.get_property("rect").unwrap();
-    prop.set_f32(Role::App, 0, 0.).unwrap();
-    prop.set_f32(Role::App, 1, 0.).unwrap();
-    prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
-    prop.set_expr(Role::App, 3, expr::load_var("h")).unwrap();
-    node.set_property_u32(Role::App, "z_index", 0).unwrap();
-
-    let c = if LIGHTMODE { 1. } else { 0. };
-    let mut shape = VectorShape::new();
-    shape.add_filled_box(
-        expr::const_f32(0.),
-        expr::const_f32(0.),
-        expr::load_var("w"),
-        expr::load_var("h"),
-        [c, c, c, 1.],
-    );
-    let node =
-        node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
-    layer_node.clone().link(node);
-
-    // Create button bg
-    let node = create_vector_art("btnbg");
-    let prop = node.get_property("rect").unwrap();
-    let code = cc.compile("w - 210").unwrap();
-    prop.set_expr(Role::App, 0, code).unwrap();
-    prop.set_f32(Role::App, 1, 10.).unwrap();
-    prop.set_f32(Role::App, 2, 200.).unwrap();
-    prop.set_f32(Role::App, 3, 60.).unwrap();
-    node.set_property_u32(Role::App, "z_index", 1).unwrap();
-
-    // Setup the pimpl
-    let verts = if LIGHTMODE {
-        vec![
-            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![
-            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 };
-    let node =
-        node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
-    layer_node.clone().link(node);
-
-    // Create the button
-    let node = create_button("btn");
-    node.set_property_bool(Role::App, "is_active", true).unwrap();
-    let prop = node.get_property("rect").unwrap();
-    let code = cc.compile("w - 220").unwrap();
-    prop.set_expr(Role::App, 0, code).unwrap();
-    prop.set_f32(Role::App, 1, 10.).unwrap();
-    prop.set_f32(Role::App, 2, 200.).unwrap();
-    prop.set_f32(Role::App, 3, 60.).unwrap();
-
-    //let (sender, btn_click_recvr) = async_channel::unbounded();
-    //let slot_click = Slot { name: "button_clicked".to_string(), notify: sender };
-    //node.register("click", slot_click).unwrap();
-
-    let node = node.setup(|me| Button::new(me, app.ex.clone())).await;
-    layer_node.clone().link(node);
-
-    // Create another mesh
-    let node = create_vector_art("box");
-    let prop = node.get_property("rect").unwrap();
-    prop.set_f32(Role::App, 0, 10.).unwrap();
-    prop.set_f32(Role::App, 1, 10.).unwrap();
-    prop.set_f32(Role::App, 2, 60.).unwrap();
-    prop.set_f32(Role::App, 3, 60.).unwrap();
-    node.set_property_u32(Role::App, "z_index", 1).unwrap();
-
-    // Setup the pimpl
-    let verts = if LIGHTMODE {
-        vec![
-            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![
-            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 };
-    let node =
-        node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
-    layer_node.clone().link(node);
-
-    // Debugging tool
-    let node = create_vector_art("debugtool");
-    let prop = node.get_property("rect").unwrap();
-    prop.set_f32(Role::App, 0, 0.).unwrap();
-    let code = cc.compile("h/2").unwrap();
-    prop.set_expr(Role::App, 1, code).unwrap();
-    prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
-    let code = cc.compile("h/2 - 200").unwrap();
-    prop.set_expr(Role::App, 3, code).unwrap();
-    node.set_property_u32(Role::App, "z_index", 2).unwrap();
-
-    let mut shape = VectorShape::new();
-    shape.add_filled_box(
-        expr::const_f32(0.),
-        expr::const_f32(0.),
-        expr::load_var("w"),
-        expr::const_f32(5.),
-        [0., 1., 0., 1.],
-    );
-    shape.add_filled_box(
-        expr::const_f32(0.),
-        cc.compile("h - 5").unwrap(),
-        expr::load_var("w"),
-        expr::load_var("h"),
-        [0., 1., 0., 1.],
-    );
-    let node =
-        node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
-    layer_node.clone().link(node);
-
-    // Another debug tool for the chatedit
-    let node = create_vector_art("debugtool-chatedit");
-    let prop = node.get_property("rect").unwrap();
-    prop.set_f32(Role::App, 0, 0.).unwrap();
-    prop.set_f32(Role::App, 1, 300. - 5.).unwrap();
-    prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
-    prop.set_f32(Role::App, 3, 400. + 10.).unwrap();
-    node.set_property_u32(Role::App, "z_index", 2).unwrap();
-
-    let mut shape = VectorShape::new();
-    shape.add_filled_box(
-        expr::const_f32(0.),
-        expr::const_f32(0.),
-        expr::load_var("w"),
-        expr::const_f32(5.),
-        [0., 1., 1., 1.],
-    );
-    shape.add_filled_box(
-        expr::const_f32(0.),
-        cc.compile("h - 5").unwrap(),
-        expr::load_var("w"),
-        expr::load_var("h"),
-        [0., 1., 1., 1.],
-    );
-    let node =
-        node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
-    layer_node.clone().link(node);
-
-    // Create KING GNU!
-    let node = create_image("king");
-    let prop = node.get_property("rect").unwrap();
-    prop.set_f32(Role::App, 0, 80.).unwrap();
-    prop.set_f32(Role::App, 1, 10.).unwrap();
-    prop.set_f32(Role::App, 2, 60.).unwrap();
-    prop.set_f32(Role::App, 3, 60.).unwrap();
-    node.set_property_str(Role::App, "path", KING_PATH).unwrap();
-    node.set_property_u32(Role::App, "z_index", 1).unwrap();
-    let node = node.setup(|me| Image::new(me, app.render_api.clone(), app.ex.clone())).await;
-    layer_node.clone().link(node);
-
-    // Create some text
-    let node = create_text("label");
-    let prop = node.get_property("rect").unwrap();
-    prop.set_f32(Role::App, 0, 100.).unwrap();
-    prop.set_f32(Role::App, 1, 100.).unwrap();
-    prop.set_f32(Role::App, 2, 800.).unwrap();
-    prop.set_f32(Role::App, 3, 200.).unwrap();
-    node.set_property_f32(Role::App, "baseline", 40.).unwrap();
-    node.set_property_f32(Role::App, "font_size", 60.).unwrap();
-    node.set_property_str(Role::App, "text", "anon1🍆").unwrap();
-    //node.set_property_str(Role::App, "text", "anon1").unwrap();
-    let prop = node.get_property("text_color").unwrap();
-    prop.set_f32(Role::App, 0, 0.).unwrap();
-    prop.set_f32(Role::App, 1, 1.).unwrap();
-    prop.set_f32(Role::App, 2, 0.).unwrap();
-    prop.set_f32(Role::App, 3, 1.).unwrap();
-    node.set_property_u32(Role::App, "z_index", 1).unwrap();
-
-    let node = node
-        .setup(|me| {
-            Text::new(
-                me,
-                window_scale.clone(),
-                app.render_api.clone(),
-                app.text_shaper.clone(),
-                app.ex.clone(),
-            )
-        })
-        .await;
-    layer_node.clone().link(node);
-
-    // Text edit
-    let node = create_editbox("editz");
-    node.set_property_bool(Role::App, "is_active", true).unwrap();
-    node.set_property_bool(Role::App, "is_focused", true).unwrap();
-    let prop = node.get_property("rect").unwrap();
-    prop.set_f32(Role::App, 0, 150.).unwrap();
-    prop.set_f32(Role::App, 1, 150.).unwrap();
-    prop.set_f32(Role::App, 2, 380.).unwrap();
-    //let code = vec![Op::Sub((
-    //    Box::new(Op::LoadVar("h".to_string())),
-    //    Box::new(Op::ConstFloat32(60.)),
-    //))];
-    //prop.set_expr(Role::App, 1, code).unwrap();
-    //let code = vec![Op::Sub((
-    //    Box::new(Op::LoadVar("w".to_string())),
-    //    Box::new(Op::ConstFloat32(120.)),
-    //))];
-    //prop.set_expr(Role::App, 2, code).unwrap();
-    prop.set_f32(Role::App, 3, 60.).unwrap();
-    node.set_property_f32(Role::App, "baseline", 40.).unwrap();
-    node.set_property_f32(Role::App, "font_size", 20.).unwrap();
-    node.set_property_f32(Role::App, "font_size", 40.).unwrap();
-    node.set_property_str(Role::App, "text", "hello king!😁🍆jelly 🍆1234").unwrap();
-    let prop = node.get_property("text_color").unwrap();
-    if LIGHTMODE {
-        prop.set_f32(Role::App, 0, 0.).unwrap();
-        prop.set_f32(Role::App, 1, 0.).unwrap();
-        prop.set_f32(Role::App, 2, 0.).unwrap();
-        prop.set_f32(Role::App, 3, 1.).unwrap();
-    } else {
-        prop.set_f32(Role::App, 0, 1.).unwrap();
-        prop.set_f32(Role::App, 1, 1.).unwrap();
-        prop.set_f32(Role::App, 2, 1.).unwrap();
-        prop.set_f32(Role::App, 3, 1.).unwrap();
-    }
-    let prop = node.get_property("cursor_color").unwrap();
-    prop.set_f32(Role::App, 0, 1.).unwrap();
-    prop.set_f32(Role::App, 1, 0.5).unwrap();
-    prop.set_f32(Role::App, 2, 0.5).unwrap();
-    prop.set_f32(Role::App, 3, 1.).unwrap();
-    node.set_property_f32(Role::App, "cursor_ascent", 40.).unwrap();
-    node.set_property_f32(Role::App, "cursor_descent", 40.).unwrap();
-    node.set_property_f32(Role::App, "select_ascent", 60.).unwrap();
-    node.set_property_f32(Role::App, "select_descent", 60.).unwrap();
-    let prop = node.get_property("hi_bg_color").unwrap();
-    if LIGHTMODE {
-        prop.set_f32(Role::App, 0, 0.5).unwrap();
-        prop.set_f32(Role::App, 1, 0.5).unwrap();
-        prop.set_f32(Role::App, 2, 0.5).unwrap();
-        prop.set_f32(Role::App, 3, 1.).unwrap();
-    } else {
-        prop.set_f32(Role::App, 0, 1.).unwrap();
-        prop.set_f32(Role::App, 1, 1.).unwrap();
-        prop.set_f32(Role::App, 2, 1.).unwrap();
-        prop.set_f32(Role::App, 3, 0.5).unwrap();
-    }
-    let prop = node.get_property("selected").unwrap();
-    prop.set_null(Role::App, 0).unwrap();
-    prop.set_null(Role::App, 1).unwrap();
-    node.set_property_u32(Role::App, "z_index", 1).unwrap();
-    //node.set_property_bool(Role::App, "debug", true).unwrap();
-
-    //let editbox_text = PropertyStr::wrap(node, Role::App, "text", 0).unwrap();
-    //let editbox_focus = PropertyBool::wrap(node, Role::App, "is_focused", 0).unwrap();
-    //let darkirc_backend = app.darkirc_backend.clone();
-    //let task = app.ex.spawn(async move {
-    //    while let Ok(_) = btn_click_recvr.recv().await {
-    //        let text = editbox_text.get();
-    //        editbox_text.prop().unset(Role::App, 0).unwrap();
-    //        // Clicking outside the editbox makes it lose focus
-    //        // So lets focus it again
-    //        editbox_focus.set(true);
-
-    //        debug!(target: "app", "sending text {text}");
-
-    //        let privmsg =
-    //            Privmsg { channel: "#random".to_string(), nick: "king".to_string(), msg: text };
-    //        darkirc_backend.send(privmsg).await;
-    //    }
-    //});
-    //tasks.push(task);
-
-    let node = node
-        .setup(|me| {
-            EditBox::new(
-                me,
-                window_scale.clone(),
-                app.render_api.clone(),
-                app.text_shaper.clone(),
-                app.ex.clone(),
-            )
-        })
-        .await;
-    layer_node.clone().link(node);
-
-    // ChatView
-    let node = create_chatview("chatty");
-    let prop = node.get_property("rect").unwrap();
-    prop.set_f32(Role::App, 0, 10.).unwrap();
-    let code = cc.compile("h/2").unwrap();
-    prop.set_expr(Role::App, 1, code).unwrap();
-    prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
-    let code = cc.compile("h/2 - 200").unwrap();
-    prop.set_expr(Role::App, 3, code).unwrap();
-    node.set_property_f32(Role::App, "font_size", 20.).unwrap();
-    node.set_property_f32(Role::App, "timestamp_font_size", 10.).unwrap();
-    node.set_property_f32(Role::App, "timestamp_width", 80.).unwrap();
-    node.set_property_f32(Role::App, "line_height", 30.).unwrap();
-    node.set_property_f32(Role::App, "baseline", 20.).unwrap();
-    node.set_property_u32(Role::App, "z_index", 1).unwrap();
-    //node.set_property_bool(Role::App, "debug", true).unwrap();
-
-    let prop = node.get_property("timestamp_color").unwrap();
-    prop.set_f32(Role::App, 0, 0.5).unwrap();
-    prop.set_f32(Role::App, 1, 0.5).unwrap();
-    prop.set_f32(Role::App, 2, 0.5).unwrap();
-    prop.set_f32(Role::App, 3, 0.5).unwrap();
-    let prop = node.get_property("text_color").unwrap();
-    if LIGHTMODE {
-        prop.set_f32(Role::App, 0, 0.).unwrap();
-        prop.set_f32(Role::App, 1, 0.).unwrap();
-        prop.set_f32(Role::App, 2, 0.).unwrap();
-        prop.set_f32(Role::App, 3, 1.).unwrap();
-    } else {
-        prop.set_f32(Role::App, 0, 1.).unwrap();
-        prop.set_f32(Role::App, 1, 1.).unwrap();
-        prop.set_f32(Role::App, 2, 1.).unwrap();
-        prop.set_f32(Role::App, 3, 1.).unwrap();
-    }
-
-    let prop = node.get_property("nick_colors").unwrap();
-    #[rustfmt::skip]
-    let nick_colors = [
-        0.00, 0.94, 1.00, 1.,
-        0.36, 1.00, 0.69, 1.,
-        0.29, 1.00, 0.45, 1.,
-        0.00, 0.73, 0.38, 1.,
-        0.21, 0.67, 0.67, 1.,
-        0.56, 0.61, 1.00, 1.,
-        0.84, 0.48, 1.00, 1.,
-        1.00, 0.61, 0.94, 1.,
-        1.00, 0.36, 0.48, 1.,
-        1.00, 0.30, 0.00, 1.
-    ];
-    for c in nick_colors {
-        prop.push_f32(Role::App, c).unwrap();
-    }
-
-    let prop = node.get_property("hi_bg_color").unwrap();
-    if LIGHTMODE {
-        prop.set_f32(Role::App, 0, 0.5).unwrap();
-        prop.set_f32(Role::App, 1, 0.5).unwrap();
-        prop.set_f32(Role::App, 2, 0.5).unwrap();
-        prop.set_f32(Role::App, 3, 1.).unwrap();
-    } else {
-        prop.set_f32(Role::App, 0, 0.5).unwrap();
-        prop.set_f32(Role::App, 1, 0.5).unwrap();
-        prop.set_f32(Role::App, 2, 0.5).unwrap();
-        prop.set_f32(Role::App, 3, 1.).unwrap();
-    }
-
-    let db = sled::open(CHATDB_PATH).expect("cannot open sleddb");
-    let chat_tree = db.open_tree(b"chat").unwrap();
-    if chat_tree.is_empty() {
-        populate_tree(&chat_tree);
-    }
-    debug!(target: "app", "db has {} lines", chat_tree.len());
-    let node = node
-        .setup(|me| {
-            ChatView::new(
-                me,
-                chat_tree,
-                window_scale.clone(),
-                app.render_api.clone(),
-                app.text_shaper.clone(),
-                app.ex.clone(),
-            )
-        })
-        .await;
-    layer_node.clone().link(node);
-
-    // Text edit
-    let node = create_chatedit("editz");
-    node.set_property_bool(Role::App, "is_active", true).unwrap();
-    node.set_property_bool(Role::App, "is_focused", true).unwrap();
-
-    node.set_property_f32(Role::App, "max_height", 400.).unwrap();
-
-    let prop = node.get_property("rect").unwrap();
-    prop.set_f32(Role::App, 0, 0.).unwrap();
-    prop.set_f32(Role::App, 1, 300.).unwrap();
-    prop.set_expr(Role::App, 2, expr::load_var("parent_w")).unwrap();
-    prop.set_f32(Role::App, 3, 50.).unwrap();
-
-    node.set_property_f32(Role::App, "baseline", 34.).unwrap();
-    node.set_property_f32(Role::App, "linespacing", 50.).unwrap();
-    node.set_property_f32(Role::App, "descent", 10.).unwrap();
-    node.set_property_f32(Role::App, "font_size", 40.).unwrap();
-    //node.set_property_str(Role::App, "text", "hello king!😁🍆jelly 🍆1234").unwrap();
-    let prop = node.get_property("text_color").unwrap();
-    prop.set_f32(Role::App, 0, 1.).unwrap();
-    prop.set_f32(Role::App, 1, 1.).unwrap();
-    prop.set_f32(Role::App, 2, 1.).unwrap();
-    prop.set_f32(Role::App, 3, 1.).unwrap();
-    let prop = node.get_property("text_hi_color").unwrap();
-    prop.set_f32(Role::App, 0, 0.44).unwrap();
-    prop.set_f32(Role::App, 1, 0.96).unwrap();
-    prop.set_f32(Role::App, 2, 1.).unwrap();
-    prop.set_f32(Role::App, 3, 1.).unwrap();
-    let prop = node.get_property("cursor_color").unwrap();
-    prop.set_f32(Role::App, 0, 0.816).unwrap();
-    prop.set_f32(Role::App, 1, 0.627).unwrap();
-    prop.set_f32(Role::App, 2, 1.).unwrap();
-    prop.set_f32(Role::App, 3, 1.).unwrap();
-    node.set_property_f32(Role::App, "cursor_ascent", EDITCHAT_CURSOR_ASCENT).unwrap();
-    node.set_property_f32(Role::App, "cursor_descent", EDITCHAT_CURSOR_DESCENT).unwrap();
-    node.set_property_f32(Role::App, "select_ascent", EDITCHAT_SELECT_ASCENT).unwrap();
-    node.set_property_f32(Role::App, "select_descent", EDITCHAT_SELECT_DESCENT).unwrap();
-    node.set_property_f32(Role::App, "handle_descent", 25.).unwrap();
-    let prop = node.get_property("hi_bg_color").unwrap();
-    prop.set_f32(Role::App, 0, 0.5).unwrap();
-    prop.set_f32(Role::App, 1, 0.5).unwrap();
-    prop.set_f32(Role::App, 2, 0.5).unwrap();
-    prop.set_f32(Role::App, 3, 1.).unwrap();
-    let prop = node.get_property("selected").unwrap();
-    prop.set_null(Role::App, 0).unwrap();
-    prop.set_null(Role::App, 1).unwrap();
-    node.set_property_u32(Role::App, "z_index", 3).unwrap();
-    let node = node
-        .setup(|me| {
-            ChatEdit::new(
-                me,
-                window_scale.clone(),
-                app.render_api.clone(),
-                app.text_shaper.clone(),
-                app.ex.clone(),
-            )
-        })
-        .await;
-    layer_node.clone().link(node);
-}
-
 pub(super) async fn make(app: &App, window: SceneNodePtr) {
     let window_scale = PropertyFloat32::wrap(&window, Role::Internal, "scale", 0).unwrap();
 
@@ -824,10 +362,10 @@ pub(super) async fn make(app: &App, window: SceneNodePtr) {
     let node = create_vector_art("editbox_bg");
     let prop = node.get_property("rect").unwrap();
     prop.set_f32(Role::App, 0, 0.).unwrap();
-    let code = cc.compile("h - editz_h").unwrap();
+    let code = cc.compile("h - editz_h - 2 * EDITCHAT_BOTTOM_PAD").unwrap();
     prop.set_expr(Role::App, 1, code).unwrap();
     prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
-    let code = cc.compile("editz_h").unwrap();
+    let code = cc.compile("editz_h + 2 * EDITCHAT_BOTTOM_PAD").unwrap();
     prop.set_expr(Role::App, 3, code).unwrap();
     node.set_property_u32(Role::App, "z_index", 2).unwrap();
 
@@ -965,7 +503,7 @@ pub(super) async fn make(app: &App, window: SceneNodePtr) {
     prop.set_null(Role::App, 0).unwrap();
     prop.set_null(Role::App, 1).unwrap();
     node.set_property_u32(Role::App, "z_index", 3).unwrap();
-    node.set_property_bool(Role::App, "debug", true).unwrap();
+    //node.set_property_bool(Role::App, "debug", true).unwrap();
 
     //let editbox_text = PropertyStr::wrap(node, Role::App, "text", 0).unwrap();
     //let editbox_focus = PropertyBool::wrap(node, Role::App, "is_focused", 0).unwrap();

+ 511 - 0
bin/darkwallet/src/app/schema_test.rs

@@ -0,0 +1,511 @@
+/* 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 sled_overlay::sled;
+
+use crate::{
+    error::Error,
+    expr::{self, Compiler, Op},
+    gfx::{GraphicsEventPublisherPtr, Rectangle, RenderApi, Vertex},
+    mesh::{Color, MeshBuilder},
+    prop::{
+        Property, PropertyBool, PropertyFloat32, PropertyStr, PropertySubType, PropertyType, Role,
+    },
+    scene::{SceneNodePtr, Slot},
+    text::TextShaperPtr,
+    ui::{
+        Button, ChatEdit, ChatView, EditBox, Image, Layer, ShapeVertex, Text, VectorArt,
+        VectorShape, Window,
+    },
+    ExecutorPtr,
+};
+
+use super::{
+    node::{
+        create_button, create_chatedit, create_chatview, create_editbox, create_image,
+        create_layer, create_text, create_vector_art,
+    },
+    populate_tree, App,
+};
+
+const LIGHTMODE: bool = false;
+
+pub const CHATDB_PATH: &str = "chatdb";
+pub const KING_PATH: &str = "assets/king.png";
+
+pub(super) async fn make_test(app: &App, window: SceneNodePtr) {
+    let window_scale = PropertyFloat32::wrap(&window, Role::Internal, "scale", 0).unwrap();
+
+    let mut cc = Compiler::new();
+
+    // Create a layer called view
+    let layer_node = create_layer("view");
+    let prop = layer_node.get_property("rect").unwrap();
+    prop.set_f32(Role::App, 0, 0.).unwrap();
+    prop.set_f32(Role::App, 1, 0.).unwrap();
+    prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
+    prop.set_expr(Role::App, 3, expr::load_var("h")).unwrap();
+    layer_node.set_property_bool(Role::App, "is_visible", true).unwrap();
+    let layer_node =
+        layer_node.setup(|me| Layer::new(me, app.render_api.clone(), app.ex.clone())).await;
+    window.link(layer_node.clone());
+
+    // Create a bg mesh
+    let node = create_vector_art("bg");
+    let prop = node.get_property("rect").unwrap();
+    prop.set_f32(Role::App, 0, 0.).unwrap();
+    prop.set_f32(Role::App, 1, 0.).unwrap();
+    prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
+    prop.set_expr(Role::App, 3, expr::load_var("h")).unwrap();
+    node.set_property_u32(Role::App, "z_index", 0).unwrap();
+
+    let c = if LIGHTMODE { 1. } else { 0. };
+    let mut shape = VectorShape::new();
+    shape.add_filled_box(
+        expr::const_f32(0.),
+        expr::const_f32(0.),
+        expr::load_var("w"),
+        expr::load_var("h"),
+        [c, c, c, 1.],
+    );
+    let node =
+        node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
+    layer_node.clone().link(node);
+
+    // Create button bg
+    let node = create_vector_art("btnbg");
+    let prop = node.get_property("rect").unwrap();
+    let code = cc.compile("w - 210").unwrap();
+    prop.set_expr(Role::App, 0, code).unwrap();
+    prop.set_f32(Role::App, 1, 10.).unwrap();
+    prop.set_f32(Role::App, 2, 200.).unwrap();
+    prop.set_f32(Role::App, 3, 60.).unwrap();
+    node.set_property_u32(Role::App, "z_index", 1).unwrap();
+
+    // Setup the pimpl
+    let verts = if LIGHTMODE {
+        vec![
+            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![
+            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 };
+    let node =
+        node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
+    layer_node.clone().link(node);
+
+    // Create the button
+    let node = create_button("btn");
+    node.set_property_bool(Role::App, "is_active", true).unwrap();
+    let prop = node.get_property("rect").unwrap();
+    let code = cc.compile("w - 220").unwrap();
+    prop.set_expr(Role::App, 0, code).unwrap();
+    prop.set_f32(Role::App, 1, 10.).unwrap();
+    prop.set_f32(Role::App, 2, 200.).unwrap();
+    prop.set_f32(Role::App, 3, 60.).unwrap();
+
+    //let (sender, btn_click_recvr) = async_channel::unbounded();
+    //let slot_click = Slot { name: "button_clicked".to_string(), notify: sender };
+    //node.register("click", slot_click).unwrap();
+
+    let node = node.setup(|me| Button::new(me, app.ex.clone())).await;
+    layer_node.clone().link(node);
+
+    // Create another mesh
+    let node = create_vector_art("box");
+    let prop = node.get_property("rect").unwrap();
+    prop.set_f32(Role::App, 0, 10.).unwrap();
+    prop.set_f32(Role::App, 1, 10.).unwrap();
+    prop.set_f32(Role::App, 2, 60.).unwrap();
+    prop.set_f32(Role::App, 3, 60.).unwrap();
+    node.set_property_u32(Role::App, "z_index", 1).unwrap();
+
+    // Setup the pimpl
+    let verts = if LIGHTMODE {
+        vec![
+            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![
+            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 };
+    let node =
+        node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
+    layer_node.clone().link(node);
+
+    // Debugging tool
+    let node = create_vector_art("debugtool");
+    let prop = node.get_property("rect").unwrap();
+    prop.set_f32(Role::App, 0, 0.).unwrap();
+    let code = cc.compile("h/2").unwrap();
+    prop.set_expr(Role::App, 1, code).unwrap();
+    prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
+    let code = cc.compile("h/2 - 200").unwrap();
+    prop.set_expr(Role::App, 3, code).unwrap();
+    node.set_property_u32(Role::App, "z_index", 2).unwrap();
+
+    let mut shape = VectorShape::new();
+    shape.add_filled_box(
+        expr::const_f32(0.),
+        expr::const_f32(0.),
+        expr::load_var("w"),
+        expr::const_f32(5.),
+        [0., 1., 0., 1.],
+    );
+    shape.add_filled_box(
+        expr::const_f32(0.),
+        cc.compile("h - 5").unwrap(),
+        expr::load_var("w"),
+        expr::load_var("h"),
+        [0., 1., 0., 1.],
+    );
+    let node =
+        node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
+    layer_node.clone().link(node);
+
+    // Another debug tool for the chatedit
+    let node = create_vector_art("debugtool-chatedit");
+    let prop = node.get_property("rect").unwrap();
+    prop.set_f32(Role::App, 0, 0.).unwrap();
+    prop.set_f32(Role::App, 1, 300. - 5.).unwrap();
+    prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
+    prop.set_f32(Role::App, 3, 400. + 10.).unwrap();
+    node.set_property_u32(Role::App, "z_index", 2).unwrap();
+
+    let mut shape = VectorShape::new();
+    shape.add_filled_box(
+        expr::const_f32(0.),
+        expr::const_f32(0.),
+        expr::load_var("w"),
+        expr::const_f32(5.),
+        [0., 1., 1., 1.],
+    );
+    shape.add_filled_box(
+        expr::const_f32(0.),
+        cc.compile("h - 5").unwrap(),
+        expr::load_var("w"),
+        expr::load_var("h"),
+        [0., 1., 1., 1.],
+    );
+    let node =
+        node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
+    layer_node.clone().link(node);
+
+    // Create KING GNU!
+    let node = create_image("king");
+    let prop = node.get_property("rect").unwrap();
+    prop.set_f32(Role::App, 0, 80.).unwrap();
+    prop.set_f32(Role::App, 1, 10.).unwrap();
+    prop.set_f32(Role::App, 2, 60.).unwrap();
+    prop.set_f32(Role::App, 3, 60.).unwrap();
+    node.set_property_str(Role::App, "path", KING_PATH).unwrap();
+    node.set_property_u32(Role::App, "z_index", 1).unwrap();
+    let node = node.setup(|me| Image::new(me, app.render_api.clone(), app.ex.clone())).await;
+    layer_node.clone().link(node);
+
+    // Create some text
+    let node = create_text("label");
+    let prop = node.get_property("rect").unwrap();
+    prop.set_f32(Role::App, 0, 100.).unwrap();
+    prop.set_f32(Role::App, 1, 100.).unwrap();
+    prop.set_f32(Role::App, 2, 800.).unwrap();
+    prop.set_f32(Role::App, 3, 200.).unwrap();
+    node.set_property_f32(Role::App, "baseline", 40.).unwrap();
+    node.set_property_f32(Role::App, "font_size", 60.).unwrap();
+    node.set_property_str(Role::App, "text", "anon1🍆").unwrap();
+    //node.set_property_str(Role::App, "text", "anon1").unwrap();
+    let prop = node.get_property("text_color").unwrap();
+    prop.set_f32(Role::App, 0, 0.).unwrap();
+    prop.set_f32(Role::App, 1, 1.).unwrap();
+    prop.set_f32(Role::App, 2, 0.).unwrap();
+    prop.set_f32(Role::App, 3, 1.).unwrap();
+    node.set_property_u32(Role::App, "z_index", 1).unwrap();
+
+    let node = node
+        .setup(|me| {
+            Text::new(
+                me,
+                window_scale.clone(),
+                app.render_api.clone(),
+                app.text_shaper.clone(),
+                app.ex.clone(),
+            )
+        })
+        .await;
+    layer_node.clone().link(node);
+
+    // Text edit
+    let node = create_editbox("editz");
+    node.set_property_bool(Role::App, "is_active", true).unwrap();
+    node.set_property_bool(Role::App, "is_focused", true).unwrap();
+    let prop = node.get_property("rect").unwrap();
+    prop.set_f32(Role::App, 0, 150.).unwrap();
+    prop.set_f32(Role::App, 1, 150.).unwrap();
+    prop.set_f32(Role::App, 2, 380.).unwrap();
+    //let code = vec![Op::Sub((
+    //    Box::new(Op::LoadVar("h".to_string())),
+    //    Box::new(Op::ConstFloat32(60.)),
+    //))];
+    //prop.set_expr(Role::App, 1, code).unwrap();
+    //let code = vec![Op::Sub((
+    //    Box::new(Op::LoadVar("w".to_string())),
+    //    Box::new(Op::ConstFloat32(120.)),
+    //))];
+    //prop.set_expr(Role::App, 2, code).unwrap();
+    prop.set_f32(Role::App, 3, 60.).unwrap();
+    node.set_property_f32(Role::App, "baseline", 40.).unwrap();
+    node.set_property_f32(Role::App, "font_size", 20.).unwrap();
+    node.set_property_f32(Role::App, "font_size", 40.).unwrap();
+    node.set_property_str(Role::App, "text", "hello king!😁🍆jelly 🍆1234").unwrap();
+    let prop = node.get_property("text_color").unwrap();
+    if LIGHTMODE {
+        prop.set_f32(Role::App, 0, 0.).unwrap();
+        prop.set_f32(Role::App, 1, 0.).unwrap();
+        prop.set_f32(Role::App, 2, 0.).unwrap();
+        prop.set_f32(Role::App, 3, 1.).unwrap();
+    } else {
+        prop.set_f32(Role::App, 0, 1.).unwrap();
+        prop.set_f32(Role::App, 1, 1.).unwrap();
+        prop.set_f32(Role::App, 2, 1.).unwrap();
+        prop.set_f32(Role::App, 3, 1.).unwrap();
+    }
+    let prop = node.get_property("cursor_color").unwrap();
+    prop.set_f32(Role::App, 0, 1.).unwrap();
+    prop.set_f32(Role::App, 1, 0.5).unwrap();
+    prop.set_f32(Role::App, 2, 0.5).unwrap();
+    prop.set_f32(Role::App, 3, 1.).unwrap();
+    node.set_property_f32(Role::App, "cursor_ascent", 40.).unwrap();
+    node.set_property_f32(Role::App, "cursor_descent", 40.).unwrap();
+    node.set_property_f32(Role::App, "select_ascent", 60.).unwrap();
+    node.set_property_f32(Role::App, "select_descent", 60.).unwrap();
+    let prop = node.get_property("hi_bg_color").unwrap();
+    if LIGHTMODE {
+        prop.set_f32(Role::App, 0, 0.5).unwrap();
+        prop.set_f32(Role::App, 1, 0.5).unwrap();
+        prop.set_f32(Role::App, 2, 0.5).unwrap();
+        prop.set_f32(Role::App, 3, 1.).unwrap();
+    } else {
+        prop.set_f32(Role::App, 0, 1.).unwrap();
+        prop.set_f32(Role::App, 1, 1.).unwrap();
+        prop.set_f32(Role::App, 2, 1.).unwrap();
+        prop.set_f32(Role::App, 3, 0.5).unwrap();
+    }
+    let prop = node.get_property("selected").unwrap();
+    prop.set_null(Role::App, 0).unwrap();
+    prop.set_null(Role::App, 1).unwrap();
+    node.set_property_u32(Role::App, "z_index", 1).unwrap();
+    //node.set_property_bool(Role::App, "debug", true).unwrap();
+
+    //let editbox_text = PropertyStr::wrap(node, Role::App, "text", 0).unwrap();
+    //let editbox_focus = PropertyBool::wrap(node, Role::App, "is_focused", 0).unwrap();
+    //let darkirc_backend = app.darkirc_backend.clone();
+    //let task = app.ex.spawn(async move {
+    //    while let Ok(_) = btn_click_recvr.recv().await {
+    //        let text = editbox_text.get();
+    //        editbox_text.prop().unset(Role::App, 0).unwrap();
+    //        // Clicking outside the editbox makes it lose focus
+    //        // So lets focus it again
+    //        editbox_focus.set(true);
+
+    //        debug!(target: "app", "sending text {text}");
+
+    //        let privmsg =
+    //            Privmsg { channel: "#random".to_string(), nick: "king".to_string(), msg: text };
+    //        darkirc_backend.send(privmsg).await;
+    //    }
+    //});
+    //tasks.push(task);
+
+    let node = node
+        .setup(|me| {
+            EditBox::new(
+                me,
+                window_scale.clone(),
+                app.render_api.clone(),
+                app.text_shaper.clone(),
+                app.ex.clone(),
+            )
+        })
+        .await;
+    layer_node.clone().link(node);
+
+    // ChatView
+    let node = create_chatview("chatty");
+    let prop = node.get_property("rect").unwrap();
+    prop.set_f32(Role::App, 0, 10.).unwrap();
+    let code = cc.compile("h/2").unwrap();
+    prop.set_expr(Role::App, 1, code).unwrap();
+    prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
+    let code = cc.compile("h/2 - 200").unwrap();
+    prop.set_expr(Role::App, 3, code).unwrap();
+    node.set_property_f32(Role::App, "font_size", 20.).unwrap();
+    node.set_property_f32(Role::App, "timestamp_font_size", 10.).unwrap();
+    node.set_property_f32(Role::App, "timestamp_width", 80.).unwrap();
+    node.set_property_f32(Role::App, "line_height", 30.).unwrap();
+    node.set_property_f32(Role::App, "baseline", 20.).unwrap();
+    node.set_property_u32(Role::App, "z_index", 1).unwrap();
+    //node.set_property_bool(Role::App, "debug", true).unwrap();
+
+    let prop = node.get_property("timestamp_color").unwrap();
+    prop.set_f32(Role::App, 0, 0.5).unwrap();
+    prop.set_f32(Role::App, 1, 0.5).unwrap();
+    prop.set_f32(Role::App, 2, 0.5).unwrap();
+    prop.set_f32(Role::App, 3, 0.5).unwrap();
+    let prop = node.get_property("text_color").unwrap();
+    if LIGHTMODE {
+        prop.set_f32(Role::App, 0, 0.).unwrap();
+        prop.set_f32(Role::App, 1, 0.).unwrap();
+        prop.set_f32(Role::App, 2, 0.).unwrap();
+        prop.set_f32(Role::App, 3, 1.).unwrap();
+    } else {
+        prop.set_f32(Role::App, 0, 1.).unwrap();
+        prop.set_f32(Role::App, 1, 1.).unwrap();
+        prop.set_f32(Role::App, 2, 1.).unwrap();
+        prop.set_f32(Role::App, 3, 1.).unwrap();
+    }
+
+    let prop = node.get_property("nick_colors").unwrap();
+    #[rustfmt::skip]
+    let nick_colors = [
+        0.00, 0.94, 1.00, 1.,
+        0.36, 1.00, 0.69, 1.,
+        0.29, 1.00, 0.45, 1.,
+        0.00, 0.73, 0.38, 1.,
+        0.21, 0.67, 0.67, 1.,
+        0.56, 0.61, 1.00, 1.,
+        0.84, 0.48, 1.00, 1.,
+        1.00, 0.61, 0.94, 1.,
+        1.00, 0.36, 0.48, 1.,
+        1.00, 0.30, 0.00, 1.
+    ];
+    for c in nick_colors {
+        prop.push_f32(Role::App, c).unwrap();
+    }
+
+    let prop = node.get_property("hi_bg_color").unwrap();
+    if LIGHTMODE {
+        prop.set_f32(Role::App, 0, 0.5).unwrap();
+        prop.set_f32(Role::App, 1, 0.5).unwrap();
+        prop.set_f32(Role::App, 2, 0.5).unwrap();
+        prop.set_f32(Role::App, 3, 1.).unwrap();
+    } else {
+        prop.set_f32(Role::App, 0, 0.5).unwrap();
+        prop.set_f32(Role::App, 1, 0.5).unwrap();
+        prop.set_f32(Role::App, 2, 0.5).unwrap();
+        prop.set_f32(Role::App, 3, 1.).unwrap();
+    }
+
+    let db = sled::open(CHATDB_PATH).expect("cannot open sleddb");
+    let chat_tree = db.open_tree(b"chat").unwrap();
+    if chat_tree.is_empty() {
+        populate_tree(&chat_tree);
+    }
+    debug!(target: "app", "db has {} lines", chat_tree.len());
+    let node = node
+        .setup(|me| {
+            ChatView::new(
+                me,
+                chat_tree,
+                window_scale.clone(),
+                app.render_api.clone(),
+                app.text_shaper.clone(),
+                app.ex.clone(),
+            )
+        })
+        .await;
+    layer_node.clone().link(node);
+
+    // Text edit
+    let node = create_chatedit("editz");
+    node.set_property_bool(Role::App, "is_active", true).unwrap();
+    node.set_property_bool(Role::App, "is_focused", true).unwrap();
+
+    node.set_property_f32(Role::App, "max_height", 400.).unwrap();
+
+    let prop = node.get_property("rect").unwrap();
+    prop.set_f32(Role::App, 0, 0.).unwrap();
+    prop.set_f32(Role::App, 1, 300.).unwrap();
+    prop.set_expr(Role::App, 2, expr::load_var("parent_w")).unwrap();
+    prop.set_f32(Role::App, 3, 50.).unwrap();
+
+    node.set_property_f32(Role::App, "baseline", 34.).unwrap();
+    node.set_property_f32(Role::App, "linespacing", 50.).unwrap();
+    node.set_property_f32(Role::App, "descent", 10.).unwrap();
+    node.set_property_f32(Role::App, "font_size", 40.).unwrap();
+    //node.set_property_str(Role::App, "text", "hello king!😁🍆jelly 🍆1234").unwrap();
+    let prop = node.get_property("text_color").unwrap();
+    prop.set_f32(Role::App, 0, 1.).unwrap();
+    prop.set_f32(Role::App, 1, 1.).unwrap();
+    prop.set_f32(Role::App, 2, 1.).unwrap();
+    prop.set_f32(Role::App, 3, 1.).unwrap();
+    let prop = node.get_property("text_hi_color").unwrap();
+    prop.set_f32(Role::App, 0, 0.44).unwrap();
+    prop.set_f32(Role::App, 1, 0.96).unwrap();
+    prop.set_f32(Role::App, 2, 1.).unwrap();
+    prop.set_f32(Role::App, 3, 1.).unwrap();
+    let prop = node.get_property("cursor_color").unwrap();
+    prop.set_f32(Role::App, 0, 0.816).unwrap();
+    prop.set_f32(Role::App, 1, 0.627).unwrap();
+    prop.set_f32(Role::App, 2, 1.).unwrap();
+    prop.set_f32(Role::App, 3, 1.).unwrap();
+    node.set_property_f32(Role::App, "cursor_ascent", 50.).unwrap();
+    node.set_property_f32(Role::App, "cursor_descent", 20.).unwrap();
+    node.set_property_f32(Role::App, "select_ascent", 40.).unwrap();
+    node.set_property_f32(Role::App, "select_descent", 8.).unwrap();
+    node.set_property_f32(Role::App, "handle_descent", 25.).unwrap();
+    let prop = node.get_property("hi_bg_color").unwrap();
+    prop.set_f32(Role::App, 0, 0.5).unwrap();
+    prop.set_f32(Role::App, 1, 0.5).unwrap();
+    prop.set_f32(Role::App, 2, 0.5).unwrap();
+    prop.set_f32(Role::App, 3, 1.).unwrap();
+    let prop = node.get_property("selected").unwrap();
+    prop.set_null(Role::App, 0).unwrap();
+    prop.set_null(Role::App, 1).unwrap();
+    node.set_property_u32(Role::App, "z_index", 3).unwrap();
+    let node = node
+        .setup(|me| {
+            ChatEdit::new(
+                me,
+                window_scale.clone(),
+                app.render_api.clone(),
+                app.text_shaper.clone(),
+                app.ex.clone(),
+            )
+        })
+        .await;
+    layer_node.clone().link(node);
+}

+ 16 - 1
bin/darkwallet/src/ui/chatedit.rs

@@ -659,7 +659,7 @@ impl ChatEdit {
 
         //debug!(target: "ui::chatedit", "regen_text_mesh() selections={selections:?}");
 
-        for wrap_line in &wrapped_lines.lines {
+        for (line_idx, wrap_line) in wrapped_lines.lines.iter().enumerate() {
             let select_marks = self.mark_selected_glyphs(&wrap_line, &selections);
             self.draw_selected(&mut mesh, &select_marks, &wrap_line, curr_y);
 
@@ -692,6 +692,21 @@ impl ChatEdit {
                 if glyph.sprite.has_color {
                     color = COLOR_WHITE;
                 }
+
+                // Some kewl styling
+                let top_y = (line_idx + 1) as f32 * linespacing - baseline - scroll;
+                if top_y < 0. {
+                    let pct = 1. + (top_y / baseline).clamp(-1., 0.);
+                    color[3] = pct;
+                }
+                let bottom_y = top_y + linespacing;
+                let bottom_rect = clip.h - linespacing + baseline;
+                if bottom_y > bottom_rect {
+                    let dist = bottom_y - bottom_rect;
+                    let pct = 1. - (dist / linespacing).clamp(0., 1.);
+                    color[3] = pct;
+                }
+
                 mesh.draw_box(&glyph_rect, color, uv_rect);
                 if self.debug.get() {
                     mesh.draw_outline(&glyph_rect, COLOR_RED, 1.);