From 759af23a916ba6441892fc7ea6f3dff20c7a22b2 Mon Sep 17 00:00:00 2001 From: darkfi Date: Sat, 3 Aug 2024 12:19:05 +0200 Subject: [PATCH] wallet: chatview - add smooth mouse scrolling accel/decel type movement in the buffer --- bin/darkwallet/Cargo.toml | 1 + bin/darkwallet/src/app.rs | 23 +++++++ bin/darkwallet/src/ui/chatview.rs | 109 ++++++++++++++++++++++++++++-- 3 files changed, 127 insertions(+), 6 deletions(-) diff --git a/bin/darkwallet/Cargo.toml b/bin/darkwallet/Cargo.toml index b1408491a..743f097db 100644 --- a/bin/darkwallet/Cargo.toml +++ b/bin/darkwallet/Cargo.toml @@ -23,6 +23,7 @@ glam = "0.28.0" #async_zmq = "0.4.0" zeromq = { version = "0.4.0", default-features = false, features = ["async-std-runtime", "all-transport"] } darkfi-serial = { git = "https://codeberg.org/darkrenaissance/darkfi" } +darkfi = { git = "https://codeberg.org/darkrenaissance/darkfi", features = ["system"] } thiserror = "1.0.61" smol = "2.0.0" atomic_float = "1.0.0" diff --git a/bin/darkwallet/src/app.rs b/bin/darkwallet/src/app.rs index 6a5b374dc..1d805367f 100644 --- a/bin/darkwallet/src/app.rs +++ b/bin/darkwallet/src/app.rs @@ -964,6 +964,29 @@ fn create_chatview( let mut prop = Property::new("debug", PropertyType::Bool, PropertySubType::Null); 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::>(); let method = move |data: Vec, response_fn: MethodResponseFn| { if sender.try_send(data).is_err() { diff --git a/bin/darkwallet/src/ui/chatview.rs b/bin/darkwallet/src/ui/chatview.rs index 14e2d9a9d..ece2ede71 100644 --- a/bin/darkwallet/src/ui/chatview.rs +++ b/bin/darkwallet/src/ui/chatview.rs @@ -17,6 +17,8 @@ */ use async_lock::Mutex as AsyncMutex; +use atomic_float::AtomicF32; +use darkfi::system::{msleep, CondVar}; use darkfi_serial::{deserialize, Decodable, Encodable, SerialDecodable, SerialEncodable}; use miniquad::{KeyCode, KeyMods, TouchPhase}; use rand::{rngs::OsRng, Rng}; @@ -24,7 +26,7 @@ use std::{ collections::BTreeMap, hash::{DefaultHasher, Hash, Hasher}, io::Cursor, - sync::{Arc, Mutex as SyncMutex, Weak}, + sync::{atomic::Ordering, Arc, Mutex as SyncMutex, Weak}, }; use crate::{ @@ -48,10 +50,17 @@ use super::{eval_rect, get_parent_rect, read_rect, DrawUpdate, OnModify, Stoppab const DEBUG_RENDER: bool = false; +const EPSILON: f32 = 0.001; +const BIG_EPSILON: f32 = 0.05; + fn is_whitespace(s: &str) -> bool { s.chars().all(char::is_whitespace) } +fn is_zero(x: f32) -> bool { + x.abs() < EPSILON +} + #[derive(Clone, Debug, SerialEncodable, SerialDecodable)] pub struct ChatMsg { pub nick: String, @@ -236,6 +245,15 @@ pub struct ChatView { text_color: PropertyColor, nick_colors: PropertyPtr, z_index: PropertyUint32, + + mouse_scroll_start_accel: PropertyFloat32, + mouse_scroll_decel: PropertyFloat32, + mouse_scroll_resist: PropertyFloat32, + + // Scroll accel + motion_cv: Arc, + accel: AtomicF32, + speed: AtomicF32, } impl ChatView { @@ -264,6 +282,12 @@ impl ChatView { 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 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); let self_ = Arc::new_cyclic(|me: &Weak| { @@ -293,6 +317,21 @@ impl ChatView { 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()); async fn reload_view(self_: Arc) { @@ -311,6 +350,7 @@ impl ChatView { touch_task, key_down_task, insert_line_method_task, + motion_task, ]; tasks.append(&mut on_modify.tasks); @@ -339,6 +379,14 @@ impl ChatView { text_color, nick_colors, 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"); return } + //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) { @@ -566,6 +618,48 @@ impl ChatView { 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 fn descent(&self) -> f32 { self.line_height.get() - self.baseline.get() @@ -771,13 +865,15 @@ impl ChatView { /// Basically a version of redraw() where regen_mesh() is never called. /// 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 node = sg.get_node(self.node_id).unwrap(); let Some(parent_rect) = get_parent_rect(&sg, node) else { - return; + return 0.; }; 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.scroll.set(scroll); + scroll - old_scroll } async fn redraw(&self) {