mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-04-28 03:00:18 -04:00
wallet: add button
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -60,6 +60,7 @@ class SceneNodeType:
|
||||
PLUGIN = 15
|
||||
CHAT_VIEW = 16
|
||||
EDIT_BOX = 17
|
||||
BUTTON = 18
|
||||
|
||||
class PropertyType:
|
||||
NULL = 0
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
219
bin/darkwallet/src/ui/button.rs
Normal file
219
bin/darkwallet/src/ui/button.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user