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

wallet: android gesture to change the window scale

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

+ 1 - 0
bin/darkwallet/Cargo.toml

@@ -56,6 +56,7 @@ parking_lot = { version = "0.12", features = ["nightly"] }
 
 [features]
 emulate-android = []
+enable-plugins = []
 
 [patch.crates-io]
 # We can remove these patches. But unfortunately harfbuzz-sys is still linking

+ 1 - 0
bin/darkwallet/Makefile

@@ -12,6 +12,7 @@ SRC = \
 	$(shell find res -type f)
 
 #FEATURES = --features=emulate-android
+#FEATURES = --features=enable-plugins
 
 all: $(SRC) fonts
 	# You can either install cargo-limit or just s/lbuild/build

+ 4 - 5
bin/darkwallet/src/app/mod.rs

@@ -54,8 +54,6 @@ macro_rules! d { ($($arg:tt)*) => { debug!(target: "app", $($arg)*); } }
 macro_rules! t { ($($arg:tt)*) => { trace!(target: "app", $($arg)*); } }
 macro_rules! i { ($($arg:tt)*) => { info!(target: "app", $($arg)*); } }
 
-const PLUGINS_ENABLED: bool = true;
-
 //fn print_type_of<T>(_: &T) {
 //    println!("{}", std::any::type_name::<T>())
 //}
@@ -182,10 +180,11 @@ impl App {
         let plugin = Arc::new(SceneNode3::new("plugin", SceneNodeType3::PluginRoot));
         self.sg_root.clone().link(plugin.clone());
 
-        if !PLUGINS_ENABLED {
-            return
-        }
+        #[cfg(feature = "enable-plugins")]
+        self.load_plugins(plugin).await;
+    }
 
+    async fn load_plugins(&self, plugin: SceneNodePtr) {
         let darkirc = create_darkirc("darkirc");
         let darkirc = darkirc
             .setup(|me| async {

+ 17 - 0
bin/darkwallet/src/app/node.rs

@@ -110,6 +110,23 @@ pub fn create_shortcut(name: &str) -> SceneNode {
     node
 }
 
+pub fn create_gesture(name: &str) -> SceneNode {
+    t!("create_gesture({name})");
+    let mut node = SceneNode::new(name, SceneNodeType::Gesture);
+
+    let prop = Property::new("priority", PropertyType::Uint32, PropertySubType::Null);
+    node.add_property(prop).unwrap();
+
+    node.add_signal(
+        "gesture",
+        "Gesture triggered",
+        vec![("distance", "Distance", CallArgType::Float32)],
+    )
+    .unwrap();
+
+    node
+}
+
 pub fn create_image(name: &str) -> SceneNode {
     t!("create_image({name})");
     let mut node = SceneNode::new(name, SceneNodeType::Image);

+ 32 - 6
bin/darkwallet/src/app/schema/mod.rs

@@ -16,15 +16,15 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use darkfi_serial::Encodable;
+use darkfi_serial::{deserialize, Encodable};
 use sled_overlay::sled;
 use std::fs::File;
 
 use crate::{
     app::{
         node::{
-            create_button, create_chatedit, create_chatview, create_editbox, create_image,
-            create_layer, create_shortcut, create_text, create_vector_art,
+            create_button, create_chatedit, create_chatview, create_editbox, create_gesture,
+            create_image, create_layer, create_shortcut, create_text, create_vector_art,
         },
         populate_tree, App,
     },
@@ -40,8 +40,8 @@ use crate::{
     shape,
     text::TextShaperPtr,
     ui::{
-        emoji_picker, Button, ChatEdit, ChatView, EditBox, Image, Layer, ShapeVertex, Shortcut,
-        Text, VectorArt, VectorShape, Window,
+        emoji_picker, Button, ChatEdit, ChatView, EditBox, Gesture, Image, Layer, ShapeVertex,
+        Shortcut, Text, VectorArt, VectorShape, Window,
     },
     ExecutorPtr,
 };
@@ -164,7 +164,6 @@ pub async fn make(app: &App, window: SceneNodePtr) {
     node.set_property_u32(atom, Role::App, "priority", 10).unwrap();
     let (slot, recvr) = Slot::new("zoom_in_pressed");
     node.register("shortcut", slot).unwrap();
-    let window_scale = PropertyFloat32::wrap(&window, Role::App, "scale", 0).unwrap();
     let window_scale2 = window_scale.clone();
     let listen_zoom = app.ex.spawn(async move {
         while let Ok(_) = recvr.recv().await {
@@ -186,6 +185,33 @@ pub async fn make(app: &App, window: SceneNodePtr) {
     let node = node.setup(|me| Shortcut::new(me)).await;
     window.clone().link(node);
 
+    let node = create_gesture("zoom_gesture");
+    node.set_property_u32(atom, Role::App, "priority", 10).unwrap();
+    let (slot, recvr) = Slot::new("zoom_gesture");
+    node.register("gesture", slot).unwrap();
+    let listen_zoom = app.ex.spawn(async move {
+        while let Ok(data) = recvr.recv().await {
+            let distance: f32 = deserialize(&data).unwrap();
+            // Dampen it a little
+            let r = (distance - 1.) / 2. + 1.;
+            let scale = r * window_scale.get();
+
+            let filename = get_window_scale_filename();
+            if let Some(parent) = filename.parent() {
+                let _ = std::fs::create_dir_all(parent);
+            }
+            if let Ok(mut file) = File::create(filename) {
+                scale.encode(&mut file).unwrap();
+            }
+
+            let atom = &mut PropertyAtomicGuard::new();
+            window_scale.set(atom, scale);
+        }
+    });
+    app.tasks.lock().unwrap().push(listen_zoom);
+    let node = node.setup(|me| Gesture::new(me)).await;
+    window.clone().link(node);
+
     if COLOR_SCHEME == ColorScheme::DarkMode {
         // Bg layer
         let layer_node = create_layer("bg_layer");

+ 4 - 1
bin/darkwallet/src/scene.rs

@@ -117,7 +117,8 @@ pub enum SceneNodeType {
     Image = 19,
     Button = 20,
     Shortcut = 21,
-    EmojiPicker = 22,
+    Gesture = 22,
+    EmojiPicker = 23,
     PluginRoot = 100,
     Plugin = 101,
 }
@@ -423,6 +424,7 @@ impl std::fmt::Debug for SceneNode {
 pub enum CallArgType {
     Uint32,
     Uint64,
+    Float32,
     Bool,
     Str,
     Hash,
@@ -517,6 +519,7 @@ pub enum Pimpl {
     Image(ui::ImagePtr),
     Button(ui::ButtonPtr),
     Shortcut(ui::ShortcutPtr),
+    Gesture(ui::GesturePtr),
     EmojiPicker(ui::EmojiPickerPtr),
     DarkIrc(plugin::DarkIrcPtr),
 }

+ 128 - 0
bin/darkwallet/src/ui/gesture.rs

@@ -0,0 +1,128 @@
+/* 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 async_trait::async_trait;
+use darkfi_serial::serialize;
+use miniquad::TouchPhase;
+use std::sync::{Arc, Mutex as SyncMutex};
+
+use crate::{
+    gfx::Point,
+    prop::{
+        PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr, PropertyRect, PropertyStr,
+        PropertyUint32, Role,
+    },
+    scene::{Pimpl, SceneNodePtr, SceneNodeWeak},
+};
+
+use super::UIObject;
+
+macro_rules! d { ($($arg:tt)*) => { debug!(target: "ui::gesture", $($arg)*); } }
+macro_rules! t { ($($arg:tt)*) => { trace!(target: "ui::gesture", $($arg)*); } }
+
+/// Maximum number of simultaneous touch events.
+/// Put 3 here because any more is ridiculous.
+const MAX_TOUCH: usize = 3;
+
+#[derive(Clone)]
+struct GestureState {
+    start: [Option<Point>; MAX_TOUCH],
+    curr: [Option<Point>; MAX_TOUCH],
+}
+
+pub type GesturePtr = Arc<Gesture>;
+
+pub struct Gesture {
+    node: SceneNodeWeak,
+    priority: PropertyUint32,
+    state: SyncMutex<GestureState>,
+}
+
+impl Gesture {
+    pub async fn new(node: SceneNodeWeak) -> Pimpl {
+        t!("Gesture::new()");
+
+        let node_ref = &node.upgrade().unwrap();
+        let priority = PropertyUint32::wrap(node_ref, Role::Internal, "priority", 0).unwrap();
+
+        let state = GestureState { start: [None; MAX_TOUCH], curr: [None; MAX_TOUCH] };
+
+        let self_ = Arc::new(Self { node, priority, state: SyncMutex::new(state) });
+
+        Pimpl::Gesture(self_)
+    }
+
+    fn handle_update(&self, state: GestureState) -> Option<f32> {
+        let Some(start_1) = state.start[0] else { return None };
+        let curr_1 = state.curr[0].unwrap();
+
+        let Some(start_2) = state.start[1] else { return None };
+        let curr_2 = state.curr[1].unwrap();
+
+        let start_dist_sq = start_1.dist_sq(&start_2);
+        let curr_dist_sq = curr_1.dist_sq(&curr_2);
+        let r = (curr_dist_sq / start_dist_sq).sqrt();
+
+        Some(r)
+    }
+}
+
+#[async_trait]
+impl UIObject for Gesture {
+    fn priority(&self) -> u32 {
+        self.priority.get()
+    }
+
+    async fn handle_touch(&self, phase: TouchPhase, id: u64, touch_pos: Point) -> bool {
+        t!("handle_touch({phase:?}, {id}, {touch_pos:?})");
+        let id = id as usize;
+        if id >= MAX_TOUCH {
+            return false
+        }
+
+        match phase {
+            TouchPhase::Started => {
+                let mut state = self.state.lock().unwrap();
+                state.start[id] = Some(touch_pos);
+                state.curr[id] = Some(touch_pos);
+                false
+            }
+            TouchPhase::Moved => {
+                let state = {
+                    let mut state = self.state.lock().unwrap();
+                    state.curr[id] = Some(touch_pos);
+                    state.clone()
+                };
+
+                if let Some(update) = self.handle_update(state) {
+                    let node = self.node.upgrade().unwrap();
+                    d!("Gesture invoked: {update}");
+                    node.trigger("gesture", serialize(&update)).await.unwrap();
+                }
+
+                false
+            }
+            TouchPhase::Ended | TouchPhase::Cancelled => {
+                let mut state = self.state.lock().unwrap();
+                state.start = [None; MAX_TOUCH];
+                state.curr = [None; MAX_TOUCH];
+                false
+            }
+        }
+    }
+}

+ 1 - 1
bin/darkwallet/src/ui/layer.rs

@@ -130,7 +130,7 @@ impl Layer {
             for child in self.get_children() {
                 let obj = get_ui_object3(&child);
                 let Some(mut draw_update) = obj.draw(rect, trace_id, atom).await else {
-                    t!("Skipped draw for {child:?} [trace_id={trace_id}]");
+                    t!("{child:?} draw returned none [trace_id={trace_id}]");
                     continue
                 };
 

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

@@ -44,6 +44,8 @@ mod editbox;
 pub use editbox::{EditBox, EditBoxPtr};
 pub mod emoji_picker;
 pub use emoji_picker::{EmojiPicker, EmojiPickerPtr};
+mod gesture;
+pub use gesture::{Gesture, GesturePtr};
 mod image;
 pub use image::{Image, ImagePtr};
 mod vector_art;
@@ -204,6 +206,7 @@ pub fn get_ui_object_ptr(node: &SceneNode3) -> Arc<dyn UIObject + Send> {
         Pimpl::Button(obj) => obj.clone(),
         Pimpl::EmojiPicker(obj) => obj.clone(),
         Pimpl::Shortcut(obj) => obj.clone(),
+        Pimpl::Gesture(obj) => obj.clone(),
         _ => panic!("unhandled type for get_ui_object: {node:?}"),
     }
 }
@@ -219,6 +222,7 @@ pub fn get_ui_object3<'a>(node: &'a SceneNode3) -> &'a dyn UIObject {
         Pimpl::Button(obj) => obj.as_ref(),
         Pimpl::EmojiPicker(obj) => obj.as_ref(),
         Pimpl::Shortcut(obj) => obj.as_ref(),
+        Pimpl::Gesture(obj) => obj.as_ref(),
         _ => panic!("unhandled type for get_ui_object: {node:?}"),
     }
 }

+ 1 - 1
bin/darkwallet/src/ui/win.rs

@@ -455,7 +455,7 @@ impl Window {
         for child in self.get_children() {
             let obj = get_ui_object3(&child);
             let Some(mut draw_update) = obj.draw(rect, trace_id, atom).await else {
-                error!(target: "ui::layer", "draw() of {child:?} failed [trace_id={trace_id}]");
+                t!("{child:?} draw returned none [trace_id={trace_id}]");
                 continue
             };