diff --git a/bin/darkwallet/data/close.obj b/bin/darkwallet/data/close.obj
new file mode 100644
index 000000000000..dd8d7d88d0fe
--- /dev/null
+++ b/bin/darkwallet/data/close.obj
@@ -0,0 +1,38 @@
+# Blender 4.3.1
+# www.blender.org
+mtllib close.mtl
+o close
+v 0.000000 0.000000 0.000000
+v 0.000000 0.000000 -0.194555
+v 0.194555 0.000000 0.000000
+v 0.550000 0.000000 -0.744555
+v 0.744555 0.000000 -0.550000
+v 0.000000 0.000000 0.000000
+v -0.194555 0.000000 0.000000
+v -0.550000 0.000000 -0.744555
+v -0.744555 0.000000 -0.550000
+v 0.000000 0.000000 0.000000
+v 0.000000 0.000000 0.194555
+v 0.550000 0.000000 0.744555
+v 0.744555 0.000000 0.550000
+v 0.000000 0.000000 0.000000
+v -0.550000 0.000000 0.744555
+v -0.744555 0.000000 0.550000
+vn -0.0000 1.0000 -0.0000
+vn -0.0000 -1.0000 -0.0000
+vt 1.000000 0.000000
+vt 1.000000 1.000000
+vt 0.000000 1.000000
+s 0
+f 1/1/1 3/2/1 2/3/1
+f 3/2/1 4/3/1 2/3/1
+f 6/1/2 7/2/2 2/3/2
+f 7/2/2 8/3/2 2/3/2
+f 10/1/2 3/2/2 11/3/2
+f 3/2/2 12/3/2 11/3/2
+f 14/1/1 7/2/1 11/3/1
+f 7/2/1 15/3/1 11/3/1
+f 3/2/1 5/2/1 4/3/1
+f 7/2/2 9/2/2 8/3/2
+f 3/2/2 13/2/2 12/3/2
+f 7/2/1 16/2/1 15/3/1
diff --git a/bin/darkwallet/src/app/node.rs b/bin/darkwallet/src/app/node.rs
index 4cea48625ae4..4b70dce0b005 100644
--- a/bin/darkwallet/src/app/node.rs
+++ b/bin/darkwallet/src/app/node.rs
@@ -47,6 +47,10 @@ pub fn create_vector_art(name: &str) -> SceneNode {
debug!(target: "app", "create_vector_art({name})");
let mut node = SceneNode::new(name, SceneNodeType::VectorArt);
+ let mut prop = Property::new("is_visible", PropertyType::Bool, PropertySubType::Null);
+ prop.set_defaults_bool(vec![true]).unwrap();
+ node.add_property(prop).unwrap();
+
let mut prop = Property::new("rect", PropertyType::Float32, PropertySubType::Pixel);
prop.set_array_len(4);
prop.allow_exprs();
@@ -474,3 +478,18 @@ pub fn create_chatview(name: &str) -> SceneNode {
node
}
+
+pub fn create_emoji_picker(name: &str) -> SceneNode {
+ debug!(target: "app", "create_emoji_picker({name})");
+ let mut node = SceneNode::new(name, SceneNodeType::Image);
+
+ let mut prop = Property::new("rect", PropertyType::Float32, PropertySubType::Pixel);
+ prop.set_array_len(4);
+ prop.allow_exprs();
+ node.add_property(prop).unwrap();
+
+ let prop = Property::new("z_index", PropertyType::Uint32, PropertySubType::Null);
+ node.add_property(prop).unwrap();
+
+ node
+}
diff --git a/bin/darkwallet/src/app/schema/chat.rs b/bin/darkwallet/src/app/schema/chat.rs
index f6a813cbeed8..e91a01106001 100644
--- a/bin/darkwallet/src/app/schema/chat.rs
+++ b/bin/darkwallet/src/app/schema/chat.rs
@@ -16,13 +16,14 @@
* along with this program. If not, see .
*/
+use darkfi::system::msleep;
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,
+ create_button, create_chatedit, create_chatview, create_editbox, create_emoji_picker,
+ create_image, create_layer, create_text, create_vector_art,
},
populate_tree, App,
},
@@ -37,8 +38,8 @@ use crate::{
shape,
text::TextShaperPtr,
ui::{
- Button, ChatEdit, ChatView, EditBox, Image, Layer, ShapeVertex, Text, VectorArt,
- VectorShape, Window,
+ Button, ChatEdit, ChatView, EditBox, EmojiPicker, Image, Layer, ShapeVertex, Text,
+ VectorArt, VectorShape, Window,
},
ExecutorPtr,
};
@@ -69,6 +70,7 @@ mod android_ui_consts {
pub const EMOJI_SCALE: f32 = 40.;
pub const EMOJI_NEG_Y: f32 = 85.;
pub const EMOJIBTN_BOX: [f32; 4] = [20., 118., 80., 75.];
+ pub const EMOJI_CLOSE_SCALE: f32 = 20.;
pub const SENDARROW_NEG_X: f32 = 50.;
pub const SENDARROW_NEG_Y: f32 = 80.;
pub const SENDBTN_BOX: [f32; 4] = [86., 120., 80., 70.];
@@ -128,6 +130,7 @@ mod ui_consts {
pub const EMOJI_SCALE: f32 = 20.;
pub const EMOJI_NEG_Y: f32 = 34.;
pub const EMOJIBTN_BOX: [f32; 4] = [16., 50., 44., 36.];
+ pub const EMOJI_CLOSE_SCALE: f32 = 10.;
pub const SENDARROW_NEG_X: f32 = 50.;
pub const SENDARROW_NEG_Y: f32 = 32.;
pub const SENDBTN_BOX: [f32; 4] = [72., 50., 45., 34.];
@@ -257,7 +260,6 @@ pub async fn make(app: &App, window: SceneNodePtr, channel: &str, db: &sled::Db)
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", CHANNEL_LABEL_BASELINE).unwrap();
node.set_property_f32(Role::App, "font_size", FONTSIZE).unwrap();
node.set_property_str(Role::App, "text", &("#".to_string() + channel)).unwrap();
@@ -283,6 +285,52 @@ pub async fn make(app: &App, window: SceneNodePtr, channel: &str, db: &sled::Db)
.await;
layer_node.clone().link(node);
+ // Create the emoji picker
+ let mut node = create_emoji_picker("emoji_picker");
+ let prop = Property::new("dynamic_h", PropertyType::Float32, PropertySubType::Pixel);
+ node.add_property(prop).unwrap();
+ let emoji_h_prop = node.get_property("dynamic_h").unwrap();
+ let prop = node.get_property("rect").unwrap();
+ prop.set_f32(Role::App, 0, 0.).unwrap();
+ let code = cc.compile("h - dynamic_h").unwrap();
+ prop.set_expr(Role::App, 1, code).unwrap();
+ prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
+ prop.set_expr(Role::App, 3, expr::load_var("dynamic_h")).unwrap();
+ prop.add_depend(&emoji_h_prop, 0, "dynamic_h");
+ let emoji_h_prop = PropertyFloat32::wrap(&node, Role::App, "dynamic_h", 0).unwrap();
+ let emoji_rect_prop = prop;
+ //node.set_property_f32(Role::App, "baseline", CHANNEL_LABEL_BASELINE).unwrap();
+ //node.set_property_f32(Role::App, "font_size", FONTSIZE).unwrap();
+ node.set_property_u32(Role::App, "z_index", 2).unwrap();
+ let node = node
+ .setup(|me| {
+ EmojiPicker::new(
+ me,
+ window_scale.clone(),
+ app.render_api.clone(),
+ app.text_shaper.clone(),
+ app.ex.clone(),
+ )
+ })
+ .await;
+ layer_node.clone().link(node);
+
+ // Main content view
+ let chat_layer_node = layer_node;
+ let layer_node = create_layer("content");
+ 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();
+ let code = cc.compile("h - emoji_h").unwrap();
+ prop.set_expr(Role::App, 3, code).unwrap();
+ prop.add_depend(&emoji_rect_prop, 3, "emoji_h");
+ 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;
+ chat_layer_node.link(layer_node.clone());
+
// ChatView
let node = create_chatview("chatty");
let prop = node.get_property("rect").unwrap();
@@ -468,6 +516,7 @@ pub async fn make(app: &App, window: SceneNodePtr, channel: &str, db: &sled::Db)
// Create the emoji button
let node = create_vector_art("emoji_btn_bg");
+ let emoji_btn_is_visible = PropertyBool::wrap(&node, Role::App, "is_visible", 0).unwrap();
let prop = node.get_property("rect").unwrap();
prop.set_f32(Role::App, 0, EMOJI_BTN_X).unwrap();
let code = cc.compile("h - EMOJI_NEG_Y").unwrap();
@@ -480,6 +529,22 @@ pub async fn make(app: &App, window: SceneNodePtr, channel: &str, db: &sled::Db)
node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
layer_node.clone().link(node);
+ // Create the emoji button
+ let node = create_vector_art("emoji_close_btn_bg");
+ node.set_property_bool(Role::App, "is_visible", false).unwrap();
+ let prop = node.get_property("rect").unwrap();
+ let emoji_close_is_visible = PropertyBool::wrap(&node, Role::App, "is_visible", 0).unwrap();
+ prop.set_f32(Role::App, 0, EMOJI_BTN_X).unwrap();
+ let code = cc.compile("h - EMOJI_NEG_Y").unwrap();
+ prop.set_expr(Role::App, 1, code).unwrap();
+ prop.set_f32(Role::App, 2, 500.).unwrap();
+ prop.set_f32(Role::App, 3, 500.).unwrap();
+ node.set_property_u32(Role::App, "z_index", 3).unwrap();
+ let shape = shape::create_close_icon().scaled(EMOJI_CLOSE_SCALE);
+ let node =
+ node.setup(|me| VectorArt::new(me, shape, app.render_api.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();
@@ -635,6 +700,25 @@ pub async fn make(app: &App, window: SceneNodePtr, channel: &str, db: &sled::Db)
let listen_click = app.ex.spawn(async move {
while let Ok(_) = recvr.recv().await {
info!(target: "app::chat", "clicked emoji");
+ if emoji_btn_is_visible.get() {
+ assert!(!emoji_close_is_visible.get());
+ assert!(emoji_h_prop.get() < 0.001);
+ emoji_btn_is_visible.set(false);
+ emoji_close_is_visible.set(true);
+ for i in 1..=20 {
+ emoji_h_prop.set((20 * i) as f32);
+ msleep(10).await;
+ }
+ } else {
+ assert!(emoji_close_is_visible.get());
+ assert!(emoji_h_prop.get() > 0.);
+ emoji_btn_is_visible.set(true);
+ emoji_close_is_visible.set(false);
+ for i in 1..=20 {
+ emoji_h_prop.set((400 - 20 * i) as f32);
+ msleep(10).await;
+ }
+ }
}
});
app.tasks.lock().unwrap().push(listen_click);
diff --git a/bin/darkwallet/src/prop/mod.rs b/bin/darkwallet/src/prop/mod.rs
index b613db54ebc3..d625b00c6028 100644
--- a/bin/darkwallet/src/prop/mod.rs
+++ b/bin/darkwallet/src/prop/mod.rs
@@ -319,6 +319,11 @@ impl Property {
}
Ok(())
}
+ pub fn set_defaults_bool(&mut self, defaults: Vec) -> Result<()> {
+ self.check_defaults_len(defaults.len())?;
+ self.defaults = defaults.into_iter().map(|v| PropertyValue::Bool(v)).collect();
+ Ok(())
+ }
pub fn set_defaults_u32(&mut self, defaults: Vec) -> Result<()> {
self.check_defaults_len(defaults.len())?;
self.defaults = defaults.into_iter().map(|v| PropertyValue::Uint32(v)).collect();
diff --git a/bin/darkwallet/src/scene.rs b/bin/darkwallet/src/scene.rs
index 0491d112ac76..188a08424702 100644
--- a/bin/darkwallet/src/scene.rs
+++ b/bin/darkwallet/src/scene.rs
@@ -476,4 +476,5 @@ pub enum Pimpl {
ChatView(ui::ChatViewPtr),
Image(ui::ImagePtr),
Button(ui::ButtonPtr),
+ EmojiPicker(ui::EmojiPickerPtr),
}
diff --git a/bin/darkwallet/src/shape/close.rs b/bin/darkwallet/src/shape/close.rs
new file mode 100644
index 000000000000..4b6404d5b940
--- /dev/null
+++ b/bin/darkwallet/src/shape/close.rs
@@ -0,0 +1,27 @@
+use crate::ui::{ShapeVertex, VectorShape};
+pub fn create_close_icon() -> VectorShape {
+ VectorShape {
+ verts: vec![
+ ShapeVertex::from_xy(0.0, 0.0, [0., 1., 1., 1.]),
+ ShapeVertex::from_xy(0.0, -0.194555, [0., 1., 1., 1.]),
+ ShapeVertex::from_xy(0.194555, 0.0, [0., 1., 1., 1.]),
+ ShapeVertex::from_xy(0.55, -0.744555, [0., 1., 1., 1.]),
+ ShapeVertex::from_xy(0.744555, -0.55, [0., 1., 1., 1.]),
+ ShapeVertex::from_xy(0.0, 0.0, [0., 1., 1., 1.]),
+ ShapeVertex::from_xy(-0.194555, 0.0, [0., 1., 1., 1.]),
+ ShapeVertex::from_xy(-0.55, -0.744555, [0., 1., 1., 1.]),
+ ShapeVertex::from_xy(-0.744555, -0.55, [0., 1., 1., 1.]),
+ ShapeVertex::from_xy(0.0, 0.0, [0., 1., 1., 1.]),
+ ShapeVertex::from_xy(0.0, 0.194555, [0., 1., 1., 1.]),
+ ShapeVertex::from_xy(0.55, 0.744555, [0., 1., 1., 1.]),
+ ShapeVertex::from_xy(0.744555, 0.55, [0., 1., 1., 1.]),
+ ShapeVertex::from_xy(0.0, 0.0, [0., 1., 1., 1.]),
+ ShapeVertex::from_xy(-0.55, 0.744555, [0., 1., 1., 1.]),
+ ShapeVertex::from_xy(-0.744555, 0.55, [0., 1., 1., 1.]),
+ ],
+ indices: vec![
+ 0, 2, 1, 2, 3, 1, 5, 6, 1, 6, 7, 1, 9, 2, 10, 2, 11, 10, 13, 6, 10, 6, 14, 10, 2, 4, 3,
+ 6, 8, 7, 2, 12, 11, 6, 15, 14,
+ ],
+ }
+}
diff --git a/bin/darkwallet/src/shape/mod.rs b/bin/darkwallet/src/shape/mod.rs
index 99e55c8e6aae..a134462bbdaf 100644
--- a/bin/darkwallet/src/shape/mod.rs
+++ b/bin/darkwallet/src/shape/mod.rs
@@ -21,6 +21,9 @@ use crate::ui::{ShapeVertex, VectorShape};
mod back_arrow;
pub use back_arrow::create_back_arrow;
+mod close;
+pub use close::create_close_icon;
+
mod send_arrow;
pub use send_arrow::create_send_arrow;
diff --git a/bin/darkwallet/src/ui/emoji_picker.rs b/bin/darkwallet/src/ui/emoji_picker.rs
new file mode 100644
index 000000000000..777c38e9de70
--- /dev/null
+++ b/bin/darkwallet/src/ui/emoji_picker.rs
@@ -0,0 +1,188 @@
+/* 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 .
+ */
+
+use async_trait::async_trait;
+use image::ImageReader;
+use rand::{rngs::OsRng, Rng};
+use std::{
+ io::Cursor,
+ sync::{Arc, Mutex as SyncMutex, OnceLock, Weak},
+};
+
+use crate::{
+ gfx::{
+ GfxDrawCall, GfxDrawInstruction, GfxDrawMesh, GfxTextureId, ManagedTexturePtr, Rectangle,
+ RenderApi,
+ },
+ mesh::{MeshBuilder, MeshInfo, COLOR_WHITE},
+ prop::{PropertyFloat32, PropertyPtr, PropertyRect, PropertyStr, PropertyUint32, Role},
+ scene::{Pimpl, SceneNodePtr, SceneNodeWeak},
+ text::{self, GlyphPositionIter, TextShaper, TextShaperPtr},
+ ExecutorPtr,
+};
+
+use super::{DrawUpdate, OnModify, UIObject};
+
+macro_rules! d {
+ ($($arg:tt)*) => {
+ debug!(target: "ui::emoji_picker", $($arg)*);
+ }
+}
+
+pub type EmojiPickerPtr = Arc;
+
+pub struct EmojiPicker {
+ node: SceneNodeWeak,
+ render_api: RenderApi,
+ text_shaper: TextShaperPtr,
+ tasks: OnceLock>>,
+
+ dc_key: u64,
+
+ rect: PropertyRect,
+ z_index: PropertyUint32,
+
+ window_scale: PropertyFloat32,
+ parent_rect: SyncMutex