wallet: add button

This commit is contained in:
darkfi
2024-08-02 10:40:47 +02:00
parent 2f86574d32
commit 8f3b239bb7
9 changed files with 337 additions and 8 deletions

View File

@@ -43,6 +43,8 @@ def print_node_info(parent_id, indent):
child_type = "chat_view"
case SceneNodeType.EDIT_BOX:
child_type = "edit_box"
case SceneNodeType.BUTTON:
child_type = "button"
desc = f"{ws}{child_name}:{child_id}/"
desc += " "*(50 - len(desc))

View File

@@ -60,6 +60,7 @@ class SceneNodeType:
PLUGIN = 15
CHAT_VIEW = 16
EDIT_BOX = 17
BUTTON = 18
class PropertyType:
NULL = 0

View File

@@ -27,7 +27,7 @@ use crate::{
prop::{Property, PropertySubType, PropertyType},
scene::{Pimpl, SceneGraph, SceneGraphPtr2, SceneNodeId, SceneNodeType},
text2::TextShaperPtr,
ui::{chatview, ChatView, EditBox, Image, Mesh, RenderLayer, Stoppable, Text, Window},
ui::{chatview, Button, ChatView, EditBox, Image, Mesh, RenderLayer, Stoppable, Text, Window},
};
//fn print_type_of<T>(_: &T) {
@@ -278,6 +278,87 @@ impl App {
sg.link(node_id, layer_node_id).unwrap();
// Create button bg
let node_id = create_mesh(&mut sg, "btnbg");
let node = sg.get_node_mut(node_id).unwrap();
let prop = node.get_property("rect").unwrap();
let code = vec![Op::Sub((
Box::new(Op::LoadVar("w".to_string())),
Box::new(Op::ConstFloat32(220.)),
))];
prop.set_expr(0, code).unwrap();
prop.set_f32(1, 10.).unwrap();
prop.set_f32(2, 200.).unwrap();
prop.set_f32(3, 60.).unwrap();
// Setup the pimpl
let (x1, y1) = (0., 0.);
let (x2, y2) = (1., 1.);
let verts = if LIGHTMODE {
vec![
// top left
Vertex { pos: [x1, y1], color: [1., 0., 0., 1.], uv: [0., 0.] },
// top right
Vertex { pos: [x2, y1], color: [1., 0., 0., 1.], uv: [1., 0.] },
// bottom left
Vertex { pos: [x1, y2], color: [1., 0., 0., 1.], uv: [0., 1.] },
// bottom right
Vertex { pos: [x2, y2], color: [1., 0., 0., 1.], uv: [1., 1.] },
]
} else {
vec![
// top left
Vertex { pos: [x1, y1], color: [1., 0., 0., 1.], uv: [0., 0.] },
// top right
Vertex { pos: [x2, y1], color: [1., 0., 1., 1.], uv: [1., 0.] },
// bottom left
Vertex { pos: [x1, y2], color: [0., 0., 1., 1.], uv: [0., 1.] },
// bottom right
Vertex { pos: [x2, y2], color: [1., 1., 0., 1.], uv: [1., 1.] },
]
};
let indices = vec![0, 2, 1, 1, 2, 3];
drop(sg);
let pimpl = Mesh::new(
self.ex.clone(),
self.sg.clone(),
node_id,
self.render_api.clone(),
verts,
indices,
)
.await;
let mut sg = self.sg.lock().await;
let node = sg.get_node_mut(node_id).unwrap();
node.pimpl = pimpl;
sg.link(node_id, layer_node_id).unwrap();
// Create the button
let node_id = create_button(&mut sg, "btn");
let node = sg.get_node_mut(node_id).unwrap();
node.set_property_bool("is_active", true).unwrap();
let prop = node.get_property("rect").unwrap();
let code = vec![Op::Sub((
Box::new(Op::LoadVar("w".to_string())),
Box::new(Op::ConstFloat32(220.)),
))];
prop.set_expr(0, code).unwrap();
prop.set_f32(1, 10.).unwrap();
prop.set_f32(2, 200.).unwrap();
prop.set_f32(3, 60.).unwrap();
drop(sg);
let pimpl =
Button::new(self.ex.clone(), self.sg.clone(), node_id, self.event_pub.clone()).await;
let mut sg = self.sg.lock().await;
let node = sg.get_node_mut(node_id).unwrap();
node.pimpl = pimpl;
sg.link(node_id, layer_node_id).unwrap();
// Create another mesh
let node_id = create_mesh(&mut sg, "box");
@@ -706,6 +787,22 @@ pub fn create_mesh(sg: &mut SceneGraph, name: &str) -> SceneNodeId {
node.id
}
pub fn create_button(sg: &mut SceneGraph, name: &str) -> SceneNodeId {
debug!(target: "app", "create_button({name})");
let node = sg.add_node(name, SceneNodeType::Button);
let mut prop = Property::new("is_active", PropertyType::Bool, PropertySubType::Null);
prop.set_ui_text("Is Active", "An active Button can be clicked");
node.add_property(prop).unwrap();
let mut prop = Property::new("rect", PropertyType::Float32, PropertySubType::Pixel);
prop.set_array_len(4);
prop.allow_exprs();
node.add_property(prop).unwrap();
node.id
}
pub fn create_image(sg: &mut SceneGraph, name: &str) -> SceneNodeId {
debug!(target: "app", "create_image({name})");
let node = sg.add_node(name, SceneNodeType::RenderMesh);

View File

@@ -51,6 +51,7 @@ pub enum SceneNodeType {
ChatView = 16,
EditBox = 17,
Image = 18,
Button = 19,
}
pub struct ScenePath(Vec<String>);
@@ -653,6 +654,7 @@ pub enum Pimpl {
EditBox(ui::EditBoxPtr),
ChatView(ui::ChatViewPtr),
Image(ui::ImagePtr),
Button(ui::ButtonPtr),
}
impl std::fmt::Debug for SceneNode {

View File

@@ -0,0 +1,219 @@
/* 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 miniquad::{window, BufferId, KeyCode, KeyMods, MouseButton, TextureId, TouchPhase};
use rand::{rngs::OsRng, Rng};
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc, Weak,
};
use crate::{
gfx2::{
DrawCall, DrawInstruction, DrawMesh, GraphicsEventPublisherPtr, Point, Rectangle,
RenderApiPtr, Vertex,
},
prop::{PropertyBool, PropertyPtr, PropertyUint32},
pubsub::Subscription,
scene::{Pimpl, SceneGraph, SceneGraphPtr2, SceneNodeId},
};
use super::{eval_rect, get_parent_rect, read_rect, DrawUpdate, OnModify, Stoppable};
pub type ButtonPtr = Arc<Button>;
pub struct Button {
node_id: SceneNodeId,
tasks: Vec<smol::Task<()>>,
sg: SceneGraphPtr2,
is_active: PropertyBool,
rect: PropertyPtr,
mouse_btn_held: AtomicBool,
}
impl Button {
pub async fn new(
ex: Arc<smol::Executor<'static>>,
sg: SceneGraphPtr2,
node_id: SceneNodeId,
event_pub: GraphicsEventPublisherPtr,
) -> Pimpl {
let scene_graph = sg.lock().await;
let node = scene_graph.get_node(node_id).unwrap();
//let node_name = node.name.clone();
let is_active = PropertyBool::wrap(node, "is_active", 0).unwrap();
let rect = node.get_property("rect").expect("Mesh::rect");
drop(scene_graph);
let self_ = Arc::new_cyclic(|me: &Weak<Self>| {
let ev_sub = event_pub.subscribe_mouse_btn_down();
let me2 = me.clone();
let mouse_btn_down_task = ex.spawn(async move {
loop {
Self::process_mouse_btn_down(&me2, &ev_sub).await;
}
});
let ev_sub = event_pub.subscribe_mouse_btn_up();
let me2 = me.clone();
let mouse_btn_up_task = ex.spawn(async move {
loop {
Self::process_mouse_btn_up(&me2, &ev_sub).await;
}
});
let ev_sub = event_pub.subscribe_touch();
let me2 = me.clone();
let touch_task = ex.spawn(async move {
loop {
Self::process_touch(&me2, &ev_sub).await;
}
});
let tasks = vec![mouse_btn_down_task, mouse_btn_up_task, touch_task];
Self { node_id, tasks, sg, is_active, rect, mouse_btn_held: AtomicBool::new(false) }
});
Pimpl::Button(self_)
}
async fn process_mouse_btn_down(
me: &Weak<Self>,
ev_sub: &Subscription<(MouseButton, f32, f32)>,
) {
let Ok((btn, mouse_x, mouse_y)) = ev_sub.receive().await else {
debug!(target: "ui::button", "Event relayer closed");
return
};
let Some(self_) = me.upgrade() else {
// Should not happen
panic!("self destroyed before mouse_btn_down_task was stopped!");
};
if !self_.is_active.get() {
return
}
self_.handle_mouse_btn_down(btn, mouse_x, mouse_y);
}
async fn process_mouse_btn_up(me: &Weak<Self>, ev_sub: &Subscription<(MouseButton, f32, f32)>) {
let Ok((btn, mouse_x, mouse_y)) = ev_sub.receive().await else {
debug!(target: "ui::button", "Event relayer closed");
return
};
let Some(self_) = me.upgrade() else {
// Should not happen
panic!("self destroyed before mouse_btn_up_task was stopped!");
};
if !self_.is_active.get() {
return
}
self_.handle_mouse_btn_up(btn, mouse_x, mouse_y);
}
async fn process_touch(me: &Weak<Self>, ev_sub: &Subscription<(TouchPhase, u64, f32, f32)>) {
let Ok((phase, id, touch_x, touch_y)) = ev_sub.receive().await else {
debug!(target: "ui::button", "Event relayer closed");
return
};
let Some(self_) = me.upgrade() else {
// Should not happen
panic!("self destroyed before touch_task was stopped!");
};
if !self_.is_active.get() {
return
}
self_.handle_touch(phase, id, touch_x, touch_y);
}
fn handle_mouse_btn_down(&self, btn: MouseButton, mouse_x: f32, mouse_y: f32) {
if btn != MouseButton::Left {
return
}
let mouse_pos = Point::from([mouse_x, mouse_y]);
let Some(rect) = self.get_cached_rect() else { return };
if !rect.contains(&mouse_pos) {
return
}
self.mouse_btn_held.store(true, Ordering::Relaxed);
}
fn handle_mouse_btn_up(&self, btn: MouseButton, mouse_x: f32, mouse_y: f32) {
if btn != MouseButton::Left {
return
}
// Did we start the click inside the button?
let btn_held = self.mouse_btn_held.swap(false, Ordering::Relaxed);
if !btn_held {
return
}
let mouse_pos = Point::from([mouse_x, mouse_y]);
// Are we releasing the click inside the button?
let Some(rect) = self.get_cached_rect() else { return };
if !rect.contains(&mouse_pos) {
return
}
debug!(target: "ui::button", "Mouse button clicked!");
}
fn handle_touch(&self, phase: TouchPhase, id: u64, touch_x: f32, touch_y: f32) {
// Ignore multi-touch
if id != 0 {
return
}
// Simulate mouse events
match phase {
TouchPhase::Started => self.handle_mouse_btn_down(MouseButton::Left, touch_x, touch_y),
TouchPhase::Moved => {}
TouchPhase::Ended => self.handle_mouse_btn_up(MouseButton::Left, touch_x, touch_y),
TouchPhase::Cancelled => {}
}
}
fn get_cached_rect(&self) -> Option<Rectangle> {
let Ok(rect) = read_rect(self.rect.clone()) else {
error!(target: "ui::button", "cached_rect is None");
return None
};
Some(rect)
}
pub fn set_parent_rect(&self, parent_rect: &Rectangle) {
if let Err(err) = eval_rect(self.rect.clone(), parent_rect) {
panic!("Button bad rect property: {}", err);
}
}
}

View File

@@ -738,17 +738,16 @@ impl ChatView {
let total_height = current_height + descent;
// If lines aren't enough to fill the available buffer then start from the top
let start_pos = if total_height < rect.h {
total_height
} else {
rect.h
};
let start_pos = if total_height < rect.h { total_height } else { rect.h };
let mut scroll = self.scroll.get();
assert!(scroll >= 0.);
// For when we resize the window and scroll is no longer valid
let max_allowed_scroll = total_height - rect.h;
debug!("max_allowed_scroll = {max_allowed_scroll} = total_height={total_height} - rect.h={}", rect.h);
debug!(
"max_allowed_scroll = {max_allowed_scroll} = total_height={total_height} - rect.h={}",
rect.h
);
if scroll > max_allowed_scroll {
scroll = max_allowed_scroll;
self.scroll.set(scroll);

View File

@@ -681,6 +681,9 @@ impl EditBox {
self.selected.set_null(0).unwrap();
self.selected.set_null(1).unwrap();
focus_changed = true;
} else {
// Do nothing. Click was outside editbox, and editbox wasn't focused
return
}
// Further on_focus logic change is handled by property modified callback
@@ -691,7 +694,7 @@ impl EditBox {
self.redraw().await;
}
}
fn handle_mouse_btn_up(&self, btn: MouseButton, x: f32, y: f32) {
fn handle_mouse_btn_up(&self, btn: MouseButton, _mouse_x: f32, _mouse_y: f32) {
if btn != MouseButton::Left {
return
}

View File

@@ -144,6 +144,10 @@ impl RenderLayer {
Pimpl::EditBox(editb) => editb.draw(&sg, &rect).await,
Pimpl::ChatView(chat) => chat.draw(&sg, &rect).await,
Pimpl::Image(img) => img.draw(&sg, &rect).await,
Pimpl::Button(btn) => {
btn.set_parent_rect(&rect);
continue
}
_ => {
error!(target: "ui::layer", "unhandled pimpl type");
continue

View File

@@ -27,6 +27,8 @@ use crate::{
scene::{SceneGraph, SceneNode, SceneNodeId, SceneNodeType},
};
mod button;
pub use button::{Button, ButtonPtr};
pub mod chatview;
pub use chatview::{ChatView, ChatViewPtr};
mod editbox;