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

wallet: added main menu and buttons for navigating between channels

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

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

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

+ 60 - 93
bin/darkwallet/src/app/schema.rs → bin/darkwallet/src/app/schema/chat.rs

@@ -19,6 +19,13 @@
 use sled_overlay::sled;
 
 use crate::{
+    app::{
+        node::{
+            create_button, create_chatedit, create_chatview, create_editbox, create_image,
+            create_layer, create_text, create_vector_art,
+        },
+        populate_tree, App,
+    },
     error::Error,
     expr::{self, Compiler, Op},
     gfx::{GraphicsEventPublisherPtr, Rectangle, RenderApi, Vertex},
@@ -36,18 +43,10 @@ use crate::{
     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;
 
 mod android_ui_consts {
-    pub const CHANNEL_LABEL_Y: f32 = 90.;
+    pub const CHANNEL_LABEL_BASELINE: f32 = 90.;
     pub const BACKARROW_SCALE: f32 = 30.;
     pub const BACKARROW_X: f32 = 50.;
     pub const BACKARROW_Y: f32 = 70.;
@@ -82,7 +81,6 @@ mod android_ui_consts {
 #[cfg(target_os = "android")]
 mod ui_consts {
     pub const CHATDB_PATH: &str = "/data/data/darkfi.darkwallet/chatdb/";
-    pub const KING_PATH: &str = "king.png";
     pub const BG_PATH: &str = "bg.png";
     pub use super::android_ui_consts::*;
 }
@@ -90,7 +88,6 @@ mod ui_consts {
 #[cfg(feature = "emulate-android")]
 mod ui_consts {
     pub const CHATDB_PATH: &str = "chatdb";
-    pub const KING_PATH: &str = "assets/king.png";
     pub const BG_PATH: &str = "assets/bg.png";
     pub use super::android_ui_consts::*;
 }
@@ -101,13 +98,15 @@ mod ui_consts {
 ))]
 mod ui_consts {
     pub const CHATDB_PATH: &str = "chatdb";
-    pub const KING_PATH: &str = "assets/king.png";
     pub const BG_PATH: &str = "assets/bg.png";
 
-    pub const CHANNEL_LABEL_Y: f32 = 30.;
+    // Main menu
+
+    // Chat UI
+    pub const CHANNEL_LABEL_BASELINE: f32 = 35.;
     pub const BACKARROW_SCALE: f32 = 15.;
     pub const BACKARROW_X: f32 = 38.;
-    pub const BACKARROW_Y: f32 = 23.;
+    pub const BACKARROW_Y: f32 = 26.;
     pub const CHATEDIT_MIN_HEIGHT: f32 = 60.;
     pub const CHATEDIT_HEIGHT: f32 = 60.;
     pub const CHATEDIT_SINGLE_LINE_Y: f32 = 58.;
@@ -122,12 +121,12 @@ mod ui_consts {
     pub const CHATEDIT_LHS_PAD: f32 = 100.;
     pub const TEXTBAR_BASELINE: f32 = 34.;
     pub const TEXT_DESCENT: f32 = 10.;
-    pub const EMOJI_BTN_X: f32 = 40.;
+    pub const EMOJI_BTN_X: f32 = 38.;
     pub const EMOJI_BG_W: f32 = 80.;
     pub const EMOJI_SCALE: f32 = 20.;
-    pub const EMOJI_NEG_Y: f32 = 30.;
+    pub const EMOJI_NEG_Y: f32 = 34.;
     pub const SENDARROW_NEG_X: f32 = 50.;
-    pub const SENDARROW_NEG_Y: f32 = 28.;
+    pub const SENDARROW_NEG_Y: f32 = 32.;
     pub const FONTSIZE: f32 = 20.;
     pub const TIMESTAMP_FONTSIZE: f32 = 12.;
     pub const TIMESTAMP_WIDTH: f32 = 60.;
@@ -138,7 +137,7 @@ mod ui_consts {
 
 use ui_consts::*;
 
-pub(super) async fn make(app: &App, window: SceneNodePtr) {
+pub async fn make(app: &App, window: SceneNodePtr) {
     let window_scale = PropertyFloat32::wrap(&window, Role::Internal, "scale", 0).unwrap();
 
     let mut cc = Compiler::new();
@@ -153,83 +152,18 @@ pub(super) async fn make(app: &App, window: SceneNodePtr) {
     cc.add_const_f32("EMOJI_NEG_Y", EMOJI_NEG_Y);
 
     // Main view
-    let layer_node = create_layer("view");
+    let layer_node = create_layer("chat_layer");
     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();
+    layer_node.set_property_bool(Role::App, "is_visible", false).unwrap();
+    layer_node.set_property_u32(Role::App, "z_index", 1).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 image
-    let node = create_image("bg_image");
-    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();
-
-    // Image aspect ratio
-    //let R = 1.78;
-    let R = 1.555;
-    cc.add_const_f32("R", R);
-
-    let prop = node.get_property("uv").unwrap();
-    prop.set_f32(Role::App, 0, 0.).unwrap();
-    prop.set_f32(Role::App, 1, 0.).unwrap();
-    #[rustfmt::skip]
-    let code = cc.compile("
-        r = w / h;
-        if r < R {
-            r / R
-        } else {
-            1
-        }
-    ").unwrap();
-    prop.set_expr(Role::App, 2, code).unwrap();
-    #[rustfmt::skip]
-    let code = cc.compile("
-        r = w / h;
-        if r < R {
-            1
-        } else {
-            R / r
-        }
-    ").unwrap();
-    prop.set_expr(Role::App, 3, code).unwrap();
-
-    node.set_property_str(Role::App, "path", BG_PATH).unwrap();
-    node.set_property_u32(Role::App, "z_index", 0).unwrap();
-    let node = node.setup(|me| Image::new(me, app.render_api.clone(), app.ex.clone())).await;
-    layer_node.clone().link(node);
-
-    // 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", 1).unwrap();
-
-    let c = if LIGHTMODE { 1. } else { 0. };
-    // Setup the pimpl
-    let node_id = node.id;
-    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, 0.3],
-    );
-    let node =
-        node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
-    layer_node.clone().link(node);
-
     // Create the toolbar bg
     let node = create_vector_art("toolbar_bg");
     let prop = node.get_property("rect").unwrap();
@@ -282,17 +216,47 @@ pub(super) async fn make(app: &App, window: SceneNodePtr) {
         node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
     layer_node.clone().link(node);
 
+    // Create the back button
+    let node = create_button("back_btn");
+    node.set_property_bool(Role::App, "is_active", true).unwrap();
+    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_f32(Role::App, 2, EMOJI_BG_W).unwrap();
+    prop.set_f32(Role::App, 3, CHATEDIT_HEIGHT).unwrap();
+
+    let (slot, recvr) = Slot::new("back_clicked");
+    node.register("click", slot).unwrap();
+    // Menu doesn't exist yet ;)
+    let sg_root = app.sg_root.clone();
+    let chatview_is_visible = PropertyBool::wrap(&layer_node, Role::App, "is_visible", 0).unwrap();
+    let listen_click = app.ex.spawn(async move {
+        while let Ok(_) = recvr.recv().await {
+            info!(target: "app::chat", "clicked back");
+
+            let menu_node = sg_root.clone().lookup_node("/window/menu_layer").unwrap();
+            menu_node.set_property_bool(Role::App, "is_visible", true).unwrap();
+
+            chatview_is_visible.set(false);
+        }
+    });
+    app.tasks.lock().unwrap().push(listen_click);
+
+    let node = node.setup(|me| Button::new(me, app.ex.clone())).await;
+    layer_node.clone().link(node);
+
     // Create some text
     let node = create_text("channel_label");
     let prop = node.get_property("rect").unwrap();
     prop.set_f32(Role::App, 0, CHATEDIT_LHS_PAD).unwrap();
-    prop.set_f32(Role::App, 1, CHANNEL_LABEL_Y).unwrap();
+    prop.set_f32(Role::App, 1, 0.).unwrap();
     prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
     prop.set_f32(Role::App, 3, CHATEDIT_HEIGHT).unwrap();
     node.set_property_u32(Role::App, "z_index", 2).unwrap();
-    node.set_property_f32(Role::App, "baseline", 0.).unwrap();
+    node.set_property_f32(Role::App, "baseline", CHANNEL_LABEL_BASELINE).unwrap();
     node.set_property_f32(Role::App, "font_size", FONTSIZE).unwrap();
     node.set_property_str(Role::App, "text", "#random").unwrap();
+    //node.set_property_bool(Role::App, "debug", true).unwrap();
     //node.set_property_str(Role::App, "text", "anon1").unwrap();
     let prop = node.get_property("text_color").unwrap();
     prop.set_f32(Role::App, 0, 1.).unwrap();
@@ -321,8 +285,9 @@ pub(super) async fn make(app: &App, window: SceneNodePtr) {
     prop.set_f32(Role::App, 1, CHATEDIT_HEIGHT).unwrap();
     let code = cc.compile("w - 30").unwrap();
     prop.set_expr(Role::App, 2, code).unwrap();
-    let code = cc.compile(
-"
+    let code = cc
+        .compile(
+            "
         height = if editz_h < CHATEDIT_MIN_HEIGHT {
             CHATEDIT_MIN_HEIGHT
         } else {
@@ -330,7 +295,9 @@ pub(super) async fn make(app: &App, window: SceneNodePtr) {
         };
 
         h - CHATEDIT_HEIGHT - height - 2 * CHATEDIT_BOTTOM_PAD
-").unwrap();
+",
+        )
+        .unwrap();
     prop.set_expr(Role::App, 3, code).unwrap();
     let chatview_rect_prop = prop.clone();
     node.set_property_f32(Role::App, "font_size", FONTSIZE).unwrap();
@@ -424,7 +391,7 @@ pub(super) async fn make(app: &App, window: SceneNodePtr) {
     prop.set_f32(Role::App, 0, 0.).unwrap();
     let code = cc
         .compile(
-"
+            "
         height = if editz_h < CHATEDIT_MIN_HEIGHT {
             CHATEDIT_MIN_HEIGHT
         } else {
@@ -525,7 +492,7 @@ pub(super) async fn make(app: &App, window: SceneNodePtr) {
     prop.set_f32(Role::App, 0, CHATEDIT_LHS_PAD).unwrap();
     let code = cc
         .compile(
-"
+            "
         height = if rect_h < CHATEDIT_MIN_HEIGHT {
             CHATEDIT_SINGLE_LINE_Y
         } else {

+ 266 - 0
bin/darkwallet/src/app/schema/menu.rs

@@ -0,0 +1,266 @@
+/* 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::{
+    app::{
+        node::{
+            create_button, create_chatedit, create_chatview, create_editbox, create_image,
+            create_layer, create_text, create_vector_art,
+        },
+        populate_tree, App,
+    },
+    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},
+    shape,
+    text::TextShaperPtr,
+    ui::{
+        Button, ChatEdit, ChatView, EditBox, Image, Layer, ShapeVertex, Text, VectorArt,
+        VectorShape, Window,
+    },
+    ExecutorPtr,
+};
+
+mod android_ui_consts {
+    pub const CHANNEL_LABEL_X: f32 = 40.;
+    pub const CHANNEL_LABEL_LINESPACE: f32 = 120.;
+    pub const CHANNEL_LABEL_FONTSIZE: f32 = 40.;
+    pub const CHANNEL_LABEL_BASELINE: f32 = 76.;
+}
+
+#[cfg(target_os = "android")]
+mod ui_consts {
+    pub use super::android_ui_consts::*;
+}
+
+#[cfg(feature = "emulate-android")]
+mod ui_consts {
+    pub use super::android_ui_consts::*;
+}
+
+#[cfg(all(
+    any(target_os = "linux", target_os = "macos", target_os = "windows"),
+    not(feature = "emulate-android")
+))]
+mod ui_consts {
+    pub const CHANNEL_LABEL_X: f32 = 20.;
+    pub const CHANNEL_LABEL_LINESPACE: f32 = 60.;
+    pub const CHANNEL_LABEL_FONTSIZE: f32 = 20.;
+    pub const CHANNEL_LABEL_BASELINE: f32 = 38.;
+}
+
+use ui_consts::*;
+
+static CHANNELS: &'static [&str] =
+    &["dev", "media", "hackers", "memes", "philosophy", "markets", "math", "random"];
+
+pub async fn make(app: &App, window: SceneNodePtr) {
+    let window_scale = PropertyFloat32::wrap(&window, Role::Internal, "scale", 0).unwrap();
+
+    let mut cc = Compiler::new();
+
+    // Main view
+    let layer_node = create_layer("menu_layer");
+    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();
+    layer_node.set_property_u32(Role::App, "z_index", 1).unwrap();
+    let layer_node =
+        layer_node.setup(|me| Layer::new(me, app.render_api.clone(), app.ex.clone())).await;
+    window.link(layer_node.clone());
+
+    let mut channel_y = 0.;
+
+    // Channels label bg
+    let node = create_vector_art("channels_label_bg");
+    let prop = node.get_property("rect").unwrap();
+    prop.set_f32(Role::App, 0, 0.).unwrap();
+    prop.set_f32(Role::App, 1, channel_y).unwrap();
+    prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
+    prop.set_f32(Role::App, 3, CHANNEL_LABEL_LINESPACE).unwrap();
+    node.set_property_u32(Role::App, "z_index", 0).unwrap();
+
+    let mut shape = VectorShape::new();
+
+    let x1 = expr::const_f32(0.);
+    let y1 = expr::const_f32(0.);
+    let x2 = expr::load_var("w");
+    let y2 = expr::const_f32(CHANNEL_LABEL_LINESPACE);
+    let color1 = [0., 0.11, 0.11, 1.];
+    let color2 = [0., 0., 0., 1.];
+    let mut verts = vec![
+        ShapeVertex::new(x1.clone(), y1.clone(), color1),
+        ShapeVertex::new(x2.clone(), y1.clone(), color1),
+        ShapeVertex::new(x1.clone(), y2.clone(), color2),
+        ShapeVertex::new(x2, y2, color2),
+    ];
+    let mut indices = vec![0, 2, 1, 1, 2, 3];
+    shape.verts.append(&mut verts);
+    shape.indices.append(&mut indices);
+
+    shape.add_filled_box(
+        expr::const_f32(0.),
+        expr::const_f32(CHANNEL_LABEL_LINESPACE - 1.),
+        expr::load_var("w"),
+        expr::const_f32(CHANNEL_LABEL_LINESPACE),
+        [0.15, 0.2, 0.19, 1.],
+    );
+
+    let node =
+        node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
+    layer_node.clone().link(node);
+
+    // Create some text
+    let node = create_text("channels_label");
+    let prop = node.get_property("rect").unwrap();
+    prop.set_f32(Role::App, 0, CHANNEL_LABEL_X).unwrap();
+    prop.set_f32(Role::App, 1, channel_y).unwrap();
+    prop.set_f32(Role::App, 2, 1000.).unwrap();
+    prop.set_f32(Role::App, 3, 200.).unwrap();
+    node.set_property_u32(Role::App, "z_index", 1).unwrap();
+    node.set_property_f32(Role::App, "baseline", CHANNEL_LABEL_BASELINE).unwrap();
+    node.set_property_f32(Role::App, "font_size", CHANNEL_LABEL_FONTSIZE).unwrap();
+    node.set_property_str(Role::App, "text", "CHANNELS").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.65).unwrap();
+    prop.set_f32(Role::App, 1, 0.87).unwrap();
+    prop.set_f32(Role::App, 2, 0.83).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);
+
+    channel_y += CHANNEL_LABEL_LINESPACE;
+
+    for channel in CHANNELS {
+        let text = "#".to_string() + channel;
+
+        let node = create_vector_art(&(channel.to_string() + "_channel_label_bg"));
+        let prop = node.get_property("rect").unwrap();
+        prop.set_f32(Role::App, 0, 0.).unwrap();
+        prop.set_f32(Role::App, 1, channel_y).unwrap();
+        prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
+        prop.set_f32(Role::App, 3, CHANNEL_LABEL_LINESPACE).unwrap();
+        node.set_property_u32(Role::App, "z_index", 0).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(CHANNEL_LABEL_LINESPACE),
+            [0.05, 0.05, 0.05, 1.],
+        );
+        shape.add_filled_box(
+            expr::const_f32(0.),
+            expr::const_f32(CHANNEL_LABEL_LINESPACE - 1.),
+            expr::load_var("w"),
+            expr::const_f32(CHANNEL_LABEL_LINESPACE),
+            [0.4, 0.4, 0.4, 1.],
+        );
+
+        let node = node
+            .setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone()))
+            .await;
+        layer_node.clone().link(node);
+
+        // Create some text
+        let node = create_text(&(channel.to_string() + "_channel_label"));
+        let prop = node.get_property("rect").unwrap();
+        prop.set_f32(Role::App, 0, CHANNEL_LABEL_X).unwrap();
+        prop.set_f32(Role::App, 1, channel_y).unwrap();
+        prop.set_f32(Role::App, 2, 1000.).unwrap();
+        prop.set_f32(Role::App, 3, 200.).unwrap();
+        node.set_property_u32(Role::App, "z_index", 1).unwrap();
+        node.set_property_f32(Role::App, "baseline", CHANNEL_LABEL_BASELINE).unwrap();
+        node.set_property_f32(Role::App, "font_size", CHANNEL_LABEL_FONTSIZE).unwrap();
+        node.set_property_str(Role::App, "text", text).unwrap();
+        //node.set_property_bool(Role::App, "debug", true).unwrap();
+        //node.set_property_str(Role::App, "text", "anon1").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();
+        node.set_property_u32(Role::App, "z_index", 3).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);
+
+        // Create the button
+        let node = create_button(&(channel.to_string() + "_channel_btn"));
+        node.set_property_bool(Role::App, "is_active", true).unwrap();
+        let prop = node.get_property("rect").unwrap();
+        prop.set_f32(Role::App, 0, 0.).unwrap();
+        prop.set_f32(Role::App, 1, channel_y).unwrap();
+        prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
+        prop.set_f32(Role::App, 3, CHANNEL_LABEL_LINESPACE).unwrap();
+
+        let (slot, recvr) = Slot::new(channel.to_string() + "_clicked");
+        node.register("click", slot).unwrap();
+        let chatview_node = app.sg_root.clone().lookup_node("/window/chat_layer").unwrap();
+        let chatview_is_visible =
+            PropertyBool::wrap(&chatview_node, Role::App, "is_visible", 0).unwrap();
+        let menu_is_visible = PropertyBool::wrap(&layer_node, Role::App, "is_visible", 0).unwrap();
+        let listen_click = app.ex.spawn(async move {
+            while let Ok(_) = recvr.recv().await {
+                info!(target: "app::menu", "clicked: {channel}!");
+                chatview_is_visible.set(true);
+                menu_is_visible.set(false);
+            }
+        });
+        app.tasks.lock().unwrap().push(listen_click);
+
+        let node = node.setup(|me| Button::new(me, app.ex.clone())).await;
+        layer_node.clone().link(node);
+
+        channel_y += CHANNEL_LABEL_LINESPACE;
+    }
+}

+ 148 - 0
bin/darkwallet/src/app/schema/mod.rs

@@ -0,0 +1,148 @@
+/* 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::{
+    app::{
+        node::{
+            create_button, create_chatedit, create_chatview, create_editbox, create_image,
+            create_layer, create_text, create_vector_art,
+        },
+        populate_tree, App,
+    },
+    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},
+    shape,
+    text::TextShaperPtr,
+    ui::{
+        Button, ChatEdit, ChatView, EditBox, Image, Layer, ShapeVertex, Text, VectorArt,
+        VectorShape, Window,
+    },
+    ExecutorPtr,
+};
+
+mod chat;
+mod menu;
+//mod test;
+
+#[cfg(target_os = "android")]
+mod ui_consts {
+    pub const BG_PATH: &str = "bg.png";
+}
+
+#[cfg(all(
+    any(target_os = "linux", target_os = "macos", target_os = "windows"),
+    not(feature = "emulate-android")
+))]
+mod ui_consts {
+    pub const BG_PATH: &str = "assets/bg.png";
+}
+
+use ui_consts::*;
+
+pub async fn make(app: &App, window: SceneNodePtr) {
+    let mut cc = Compiler::new();
+
+    // Bg layer
+    let layer_node = create_layer("bg_layer");
+    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();
+    layer_node.set_property_u32(Role::App, "z_index", 0).unwrap();
+    let layer_node =
+        layer_node.setup(|me| Layer::new(me, app.render_api.clone(), app.ex.clone())).await;
+    window.clone().link(layer_node.clone());
+
+    // Create a bg image
+    let node = create_image("bg_image");
+    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();
+
+    // Image aspect ratio
+    //let R = 1.78;
+    let R = 1.555;
+    cc.add_const_f32("R", R);
+
+    let prop = node.get_property("uv").unwrap();
+    prop.set_f32(Role::App, 0, 0.).unwrap();
+    prop.set_f32(Role::App, 1, 0.).unwrap();
+    #[rustfmt::skip]
+    let code = cc.compile("
+        r = w / h;
+        if r < R {
+            r / R
+        } else {
+            1
+        }
+    ").unwrap();
+    prop.set_expr(Role::App, 2, code).unwrap();
+    #[rustfmt::skip]
+    let code = cc.compile("
+        r = w / h;
+        if r < R {
+            1
+        } else {
+            R / r
+        }
+    ").unwrap();
+    prop.set_expr(Role::App, 3, code).unwrap();
+
+    node.set_property_str(Role::App, "path", BG_PATH).unwrap();
+    node.set_property_u32(Role::App, "z_index", 0).unwrap();
+    let node = node.setup(|me| Image::new(me, app.render_api.clone(), app.ex.clone())).await;
+    layer_node.clone().link(node);
+
+    // Create a bg mesh on top to fade the bg image
+    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", 1).unwrap();
+
+    //let c = if LIGHTMODE { 1. } else { 0. };
+    let c = 0.;
+    // Setup the pimpl
+    let node_id = node.id;
+    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, 0.3],
+    );
+    let node =
+        node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
+    layer_node.clone().link(node);
+
+    chat::make(app, window.clone()).await;
+    menu::make(app, window.clone()).await;
+}

+ 5 - 0
bin/darkwallet/src/app/schema_test.rs → bin/darkwallet/src/app/schema/test.rs

@@ -45,6 +45,11 @@ use super::{
 
 const LIGHTMODE: bool = false;
 
+#[cfg(target_os = "android")]
+pub const CHATDB_PATH: &str = "/data/data/darkfi.darkwallet/chatdb/";
+pub const KING_PATH: &str = "king.png";
+
+#[cfg(not(target_os = "android"))]
 pub const CHATDB_PATH: &str = "chatdb";
 pub const KING_PATH: &str = "assets/king.png";
 

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

@@ -83,7 +83,7 @@ pub type ExecutorPtr = Arc<smol::Executor<'static>>;
 
 fn panic_hook(panic_info: &std::panic::PanicInfo) {
     error!("panic occurred: {panic_info}");
-    //error!("panic: {}", std::backtrace::Backtrace::force_capture().to_string());
+    error!("{}", std::backtrace::Backtrace::force_capture().to_string());
     std::process::exit(1);
 }
 

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

@@ -560,7 +560,7 @@ impl Property {
         Ok(val.is_unset())
     }
     pub fn is_null(&self, i: usize) -> Result<bool> {
-        let val = self.get_raw_value(i)?;
+        let val = self.get_value(i)?;
         if val.is_unset() {
             return Ok(self.defaults[i].is_null())
         }

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

@@ -352,4 +352,19 @@ impl PropertyRect {
     pub fn prop(&self) -> PropertyPtr {
         self.prop.clone()
     }
+
+    fn is_f32_or_has_cached(&self, i: usize) -> bool {
+        if self.prop.is_expr(i).unwrap() {
+            if self.prop.get_cached(i).unwrap().is_null() {
+                return false
+            }
+        }
+        true
+    }
+    pub fn has_cached(&self) -> bool {
+        self.is_f32_or_has_cached(0) &&
+            self.is_f32_or_has_cached(1) &&
+            self.is_f32_or_has_cached(2) &&
+            self.is_f32_or_has_cached(3)
+    }
 }

+ 1 - 1
bin/darkwallet/src/text/mod.rs

@@ -179,7 +179,7 @@ impl TextShaper {
                     }
                 }
 
-                warn!(target: "text", "no font fallback for char: '{}'", chr);
+                //warn!(target: "text", "no font fallback for char: '{}'", chr);
                 // Skip this char
                 continue 'next_char
             };

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

@@ -634,7 +634,11 @@ impl ChatEdit {
     }
 
     fn wrap_width(&self) -> f32 {
-        self.rect.prop().get_f32(2).unwrap() - self.cursor_width.get()
+        let w = self.rect.prop().get_f32(2).unwrap() - self.cursor_width.get();
+        if w < 0. {
+            return 0.
+        }
+        w
     }
 
     fn abs_to_local(&self, point: &mut Point) {
@@ -1675,6 +1679,10 @@ impl UIObject for ChatEdit {
                     continue
                 }
 
+                if !self_.rect.has_cached() {
+                    continue
+                }
+
                 // Invert the bool
                 self_.cursor_is_visible.fetch_not(Ordering::Relaxed);
                 self_.redraw_cursor();

+ 3 - 1
bin/darkwallet/src/ui/chatview/mod.rs

@@ -750,10 +750,12 @@ impl UIObject for ChatView {
         on_modify.when_change(self.scroll.prop(), reload_view);
 
         async fn redraw(self_: Arc<ChatView>) {
+            if !self_.rect.has_cached() {
+                return
+            }
             self_.redraw_all().await;
         }
 
-        // These should be inside the class
         //on_modify.when_change(self.baseline.prop(), redraw);
         //on_modify.when_change(self.font_size.prop(), redraw);
         //on_modify.when_change(self.timestamp_font_size.prop(), redraw);

+ 8 - 1
bin/darkwallet/src/ui/text.rs

@@ -25,7 +25,7 @@ use crate::{
         GfxDrawCall, GfxDrawInstruction, GfxDrawMesh, GfxTextureId, ManagedTexturePtr, Rectangle,
         RenderApi,
     },
-    mesh::{Color, MeshBuilder, MeshInfo, COLOR_BLUE, COLOR_WHITE},
+    mesh::{Color, MeshBuilder, MeshInfo, COLOR_BLUE, COLOR_RED, COLOR_WHITE},
     prop::{
         PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr, PropertyRect, PropertyStr,
         PropertyUint32, Role,
@@ -137,6 +137,13 @@ impl Text {
             mesh.draw_box(&glyph_rect, color, uv_rect);
         }
 
+        if debug {
+            let mut rect = self.rect.get();
+            rect.x = 0.;
+            rect.y = 0.;
+            mesh.draw_outline(&rect, COLOR_RED, 1.);
+        }
+
         let mesh = mesh.alloc(&self.render_api);
 
         TextRenderInfo { mesh, texture: atlas.texture }