瀏覽代碼

wallet: chatview - add smooth mouse scrolling accel/decel type movement in the buffer

darkfi 2 年之前
父節點
當前提交
759af23a91
共有 3 個文件被更改,包括 127 次插入6 次删除
  1. 1 0
      bin/darkwallet/Cargo.toml
  2. 23 0
      bin/darkwallet/src/app.rs
  3. 103 6
      bin/darkwallet/src/ui/chatview.rs

+ 1 - 0
bin/darkwallet/Cargo.toml

@@ -23,6 +23,7 @@ glam = "0.28.0"
 #async_zmq = "0.4.0"
 #async_zmq = "0.4.0"
 zeromq = { version = "0.4.0", default-features = false, features = ["async-std-runtime", "all-transport"] }
 zeromq = { version = "0.4.0", default-features = false, features = ["async-std-runtime", "all-transport"] }
 darkfi-serial = { git = "https://codeberg.org/darkrenaissance/darkfi" }
 darkfi-serial = { git = "https://codeberg.org/darkrenaissance/darkfi" }
+darkfi = { git = "https://codeberg.org/darkrenaissance/darkfi", features = ["system"] }
 thiserror = "1.0.61"
 thiserror = "1.0.61"
 smol = "2.0.0"
 smol = "2.0.0"
 atomic_float = "1.0.0"
 atomic_float = "1.0.0"

+ 23 - 0
bin/darkwallet/src/app.rs

@@ -964,6 +964,29 @@ fn create_chatview(
     let mut prop = Property::new("debug", PropertyType::Bool, PropertySubType::Null);
     let mut prop = Property::new("debug", PropertyType::Bool, PropertySubType::Null);
     node.add_property(prop).unwrap();
     node.add_property(prop).unwrap();
 
 
+    let mut prop =
+        Property::new("mouse_scroll_start_accel", PropertyType::Float32, PropertySubType::Pixel);
+    prop.set_ui_text("Mouse Scroll Start Acceleration", "Initial acceperation when scrolling");
+    prop.set_defaults_f32(vec![4.]).unwrap();
+    node.add_property(prop).unwrap();
+
+    let mut prop =
+        Property::new("mouse_scroll_decel", PropertyType::Float32, PropertySubType::Pixel);
+    prop.set_ui_text(
+        "Mouse Scroll Deceleration",
+        "Deceleration factor for mouse scroll acceleration",
+    );
+    prop.set_range_f32(0., 1.);
+    prop.set_defaults_f32(vec![0.5]).unwrap();
+    node.add_property(prop).unwrap();
+
+    let mut prop =
+        Property::new("mouse_scroll_resist", PropertyType::Float32, PropertySubType::Pixel);
+    prop.set_ui_text("Mouse Scroll Resistance", "How quickly scrolling speed is dampened");
+    prop.set_range_f32(0., 1.);
+    prop.set_defaults_f32(vec![0.9]).unwrap();
+    node.add_property(prop).unwrap();
+
     let (sender, recvr) = async_channel::unbounded::<Vec<u8>>();
     let (sender, recvr) = async_channel::unbounded::<Vec<u8>>();
     let method = move |data: Vec<u8>, response_fn: MethodResponseFn| {
     let method = move |data: Vec<u8>, response_fn: MethodResponseFn| {
         if sender.try_send(data).is_err() {
         if sender.try_send(data).is_err() {

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

@@ -17,6 +17,8 @@
  */
  */
 
 
 use async_lock::Mutex as AsyncMutex;
 use async_lock::Mutex as AsyncMutex;
+use atomic_float::AtomicF32;
+use darkfi::system::{msleep, CondVar};
 use darkfi_serial::{deserialize, Decodable, Encodable, SerialDecodable, SerialEncodable};
 use darkfi_serial::{deserialize, Decodable, Encodable, SerialDecodable, SerialEncodable};
 use miniquad::{KeyCode, KeyMods, TouchPhase};
 use miniquad::{KeyCode, KeyMods, TouchPhase};
 use rand::{rngs::OsRng, Rng};
 use rand::{rngs::OsRng, Rng};
@@ -24,7 +26,7 @@ use std::{
     collections::BTreeMap,
     collections::BTreeMap,
     hash::{DefaultHasher, Hash, Hasher},
     hash::{DefaultHasher, Hash, Hasher},
     io::Cursor,
     io::Cursor,
-    sync::{Arc, Mutex as SyncMutex, Weak},
+    sync::{atomic::Ordering, Arc, Mutex as SyncMutex, Weak},
 };
 };
 
 
 use crate::{
 use crate::{
@@ -48,10 +50,17 @@ use super::{eval_rect, get_parent_rect, read_rect, DrawUpdate, OnModify, Stoppab
 
 
 const DEBUG_RENDER: bool = false;
 const DEBUG_RENDER: bool = false;
 
 
+const EPSILON: f32 = 0.001;
+const BIG_EPSILON: f32 = 0.05;
+
 fn is_whitespace(s: &str) -> bool {
 fn is_whitespace(s: &str) -> bool {
     s.chars().all(char::is_whitespace)
     s.chars().all(char::is_whitespace)
 }
 }
 
 
+fn is_zero(x: f32) -> bool {
+    x.abs() < EPSILON
+}
+
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
 pub struct ChatMsg {
 pub struct ChatMsg {
     pub nick: String,
     pub nick: String,
@@ -236,6 +245,15 @@ pub struct ChatView {
     text_color: PropertyColor,
     text_color: PropertyColor,
     nick_colors: PropertyPtr,
     nick_colors: PropertyPtr,
     z_index: PropertyUint32,
     z_index: PropertyUint32,
+
+    mouse_scroll_start_accel: PropertyFloat32,
+    mouse_scroll_decel: PropertyFloat32,
+    mouse_scroll_resist: PropertyFloat32,
+
+    // Scroll accel
+    motion_cv: Arc<CondVar>,
+    accel: AtomicF32,
+    speed: AtomicF32,
 }
 }
 
 
 impl ChatView {
 impl ChatView {
@@ -264,6 +282,12 @@ impl ChatView {
         let nick_colors = node.get_property("nick_colors").expect("ChatView::nick_colors");
         let nick_colors = node.get_property("nick_colors").expect("ChatView::nick_colors");
         let z_index = PropertyUint32::wrap(node, Role::Internal, "z_index", 0).unwrap();
         let z_index = PropertyUint32::wrap(node, Role::Internal, "z_index", 0).unwrap();
 
 
+        let mouse_scroll_start_accel =
+            PropertyFloat32::wrap(node, Role::Internal, "mouse_scroll_start_accel", 0).unwrap();
+        let mouse_scroll_decel =
+            PropertyFloat32::wrap(node, Role::Internal, "mouse_scroll_decel", 0).unwrap();
+        let mouse_scroll_resist =
+            PropertyFloat32::wrap(node, Role::Internal, "mouse_scroll_resist", 0).unwrap();
         drop(scene_graph);
         drop(scene_graph);
 
 
         let self_ = Arc::new_cyclic(|me: &Weak<Self>| {
         let self_ = Arc::new_cyclic(|me: &Weak<Self>| {
@@ -293,6 +317,21 @@ impl ChatView {
                     async move { while Self::process_insert_line_method(&me2, &recvr).await {} },
                     async move { while Self::process_insert_line_method(&me2, &recvr).await {} },
                 );
                 );
 
 
+            let me2 = me.clone();
+            let motion_cv = Arc::new(CondVar::new());
+            let cv = motion_cv.clone();
+            let motion_task = ex.spawn(async move {
+                loop {
+                    cv.wait().await;
+                    let Some(self_) = me2.upgrade() else {
+                        // Should not happen
+                        panic!("self destroyed before motion_task was stopped!");
+                    };
+                    self_.handle_movement().await;
+                    cv.reset();
+                }
+            });
+
             let mut on_modify = OnModify::new(ex, node_name, node_id, me.clone());
             let mut on_modify = OnModify::new(ex, node_name, node_id, me.clone());
 
 
             async fn reload_view(self_: Arc<ChatView>) {
             async fn reload_view(self_: Arc<ChatView>) {
@@ -311,6 +350,7 @@ impl ChatView {
                 touch_task,
                 touch_task,
                 key_down_task,
                 key_down_task,
                 insert_line_method_task,
                 insert_line_method_task,
+                motion_task,
             ];
             ];
             tasks.append(&mut on_modify.tasks);
             tasks.append(&mut on_modify.tasks);
 
 
@@ -339,6 +379,14 @@ impl ChatView {
                 text_color,
                 text_color,
                 nick_colors,
                 nick_colors,
                 z_index,
                 z_index,
+
+                mouse_scroll_start_accel,
+                mouse_scroll_decel,
+                mouse_scroll_resist,
+
+                motion_cv,
+                accel: AtomicF32::new(0.),
+                speed: AtomicF32::new(0.),
             }
             }
         });
         });
 
 
@@ -471,9 +519,13 @@ impl ChatView {
             //debug!(target: "ui::chatview", "not inside rect");
             //debug!(target: "ui::chatview", "not inside rect");
             return
             return
         }
         }
+
         //debug!(target: "ui::chatview", "inside rect");
         //debug!(target: "ui::chatview", "inside rect");
-        let scroll = self.scroll.get() + wheel_y * 50.;
-        self.scrollview(scroll).await;
+        //let scroll = self.scroll.get() + wheel_y * 50.;
+        //self.scrollview(scroll).await;
+
+        self.accel.fetch_add(wheel_y * self.mouse_scroll_start_accel.get(), Ordering::Relaxed);
+        self.motion_cv.notify();
     }
     }
 
 
     async fn handle_mouse_move(&self, mouse_x: f32, mouse_y: f32) {
     async fn handle_mouse_move(&self, mouse_x: f32, mouse_y: f32) {
@@ -566,6 +618,48 @@ impl ChatView {
         self.scrollview(scroll).await;
         self.scrollview(scroll).await;
     }
     }
 
 
+    async fn handle_movement(&self) {
+        loop {
+            msleep(20).await;
+            let mut accel = self.accel.load(Ordering::Relaxed);
+            let mut speed = self.speed.fetch_add(accel, Ordering::Relaxed) + accel;
+            accel *= self.mouse_scroll_decel.get();
+            if accel.abs() < 0.05 {
+                accel = 0.;
+            }
+            self.accel.store(accel, Ordering::Relaxed);
+
+            // Apply constant decel to speed
+            if is_zero(accel) {
+                speed *= self.mouse_scroll_resist.get();
+                if speed.abs() < BIG_EPSILON {
+                    speed = 0.;
+                }
+                self.speed.store(speed, Ordering::Relaxed);
+            }
+
+            // Finished
+            if is_zero(accel) && is_zero(speed) {
+                return
+            }
+
+            if is_zero(speed) {
+                self.accel.store(0., Ordering::Relaxed);
+                self.speed.store(0., Ordering::Relaxed);
+                return
+            }
+
+            let scroll = self.scroll.get() + speed;
+            let dist = self.scrollview(scroll).await;
+
+            if is_zero(dist) {
+                self.accel.store(0., Ordering::Relaxed);
+                self.speed.store(0., Ordering::Relaxed);
+                return
+            }
+        }
+    }
+
     /// Descent = line height - baseline
     /// Descent = line height - baseline
     fn descent(&self) -> f32 {
     fn descent(&self) -> f32 {
         self.line_height.get() - self.baseline.get()
         self.line_height.get() - self.baseline.get()
@@ -771,13 +865,15 @@ impl ChatView {
 
 
     /// Basically a version of redraw() where regen_mesh() is never called.
     /// Basically a version of redraw() where regen_mesh() is never called.
     /// Instead we use the cached version.
     /// Instead we use the cached version.
-    async fn scrollview(&self, mut scroll: f32) {
-        debug!(target: "ui::chatview", "scrollview()");
+    async fn scrollview(&self, mut scroll: f32) -> f32 {
+        //debug!(target: "ui::chatview", "scrollview()");
+        let old_scroll = self.scroll.get();
+
         let sg = self.sg.lock().await;
         let sg = self.sg.lock().await;
         let node = sg.get_node(self.node_id).unwrap();
         let node = sg.get_node(self.node_id).unwrap();
 
 
         let Some(parent_rect) = get_parent_rect(&sg, node) else {
         let Some(parent_rect) = get_parent_rect(&sg, node) else {
-            return;
+            return 0.;
         };
         };
 
 
         if let Err(err) = eval_rect(self.rect.clone(), &parent_rect) {
         if let Err(err) = eval_rect(self.rect.clone(), &parent_rect) {
@@ -798,6 +894,7 @@ impl ChatView {
         self.render_api.replace_draw_calls(draw_calls).await;
         self.render_api.replace_draw_calls(draw_calls).await;
 
 
         self.scroll.set(scroll);
         self.scroll.set(scroll);
+        scroll - old_scroll
     }
     }
 
 
     async fn redraw(&self) {
     async fn redraw(&self) {