darkfi пре 2 година
родитељ
комит
8f3b239bb7

+ 2 - 0
bin/darkwallet/gui/print_tree.py

@@ -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))

+ 1 - 0
bin/darkwallet/pydrk/api.py

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

+ 98 - 1
bin/darkwallet/src/app.rs

@@ -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);

+ 2 - 0
bin/darkwallet/src/scene.rs

@@ -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 - 0
bin/darkwallet/src/ui/button.rs

@@ -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);
+        }
+    }
+}

+ 5 - 6
bin/darkwallet/src/ui/chatview.rs

@@ -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);

+ 4 - 1
bin/darkwallet/src/ui/editbox.rs

@@ -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
         }

+ 4 - 0
bin/darkwallet/src/ui/layer.rs

@@ -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

+ 2 - 0
bin/darkwallet/src/ui/mod.rs

@@ -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;