Jelajahi Sumber

darkwallet: android touch scrolling chatview

darkfi 2 tahun lalu
induk
melakukan
46279cb748
3 mengubah file dengan 317 tambahan dan 25 penghapusan
  1. 1 1
      bin/darkwallet/src/gfx2.rs
  2. 5 0
      bin/darkwallet/src/main.rs
  3. 311 24
      bin/darkwallet/src/ui/chatview.rs

+ 1 - 1
bin/darkwallet/src/gfx2.rs

@@ -58,7 +58,7 @@ impl Vertex {
     }
 }
 
-#[derive(Debug)]
+#[derive(Clone, Debug)]
 pub struct Point {
     pub x: f32,
     pub y: f32,

+ 5 - 0
bin/darkwallet/src/main.rs

@@ -70,6 +70,11 @@ fn main() {
         android_logger::init_once(
             android_logger::Config::default().with_max_level(LevelFilter::Debug).with_tag("darkfi"),
         );
+
+        let paths = std::fs::read_dir("/data/data/darkfi.darkwallet/").unwrap();
+        for path in paths {
+            debug!("{}", path.unwrap().path().display())
+        }
     }
 
     #[cfg(target_os = "linux")]

+ 311 - 24
bin/darkwallet/src/ui/chatview.rs

@@ -17,6 +17,7 @@
  */
 
 use darkfi_serial::{deserialize, Decodable, Encodable, SerialDecodable, SerialEncodable};
+use miniquad::TouchPhase;
 use rand::{rngs::OsRng, Rng};
 use std::{
     collections::BTreeMap,
@@ -67,17 +68,37 @@ struct Page {
     atlas: text2::RenderedAtlas,
 }
 
+struct TouchInfo {
+    start_y: f32,
+    start_instant: std::time::Instant,
+    last_y: f32,
+}
+
+impl TouchInfo {
+    fn new() -> Self {
+        Self { start_y: 0., start_instant: std::time::Instant::now(), last_y: 0. }
+    }
+}
+
 pub type ChatViewPtr = Arc<ChatView>;
 
 pub struct ChatView {
     node_id: SceneNodeId,
+    tasks: Vec<smol::Task<()>>,
+    sg: SceneGraphPtr2,
     render_api: RenderApiPtr,
     text_shaper: TextShaperPtr,
     tree: sled::Tree,
 
     pages: SyncMutex<Vec<Page>>,
+    drawcalls: SyncMutex<Vec<DrawMesh>>,
     dc_key: u64,
 
+    /// Used for detecting when scrolling view
+    mouse_pos: SyncMutex<Point>,
+    /// Touch scrolling
+    touch_info: SyncMutex<TouchInfo>,
+
     rect: PropertyPtr,
     scroll: PropertyFloat32,
     font_size: PropertyFloat32,
@@ -88,14 +109,18 @@ pub struct ChatView {
 
 impl ChatView {
     pub async fn new(
+        ex: Arc<smol::Executor<'static>>,
         sg: SceneGraphPtr2,
         node_id: SceneNodeId,
         render_api: RenderApiPtr,
+        event_pub: GraphicsEventPublisherPtr,
         text_shaper: TextShaperPtr,
         tree: sled::Tree,
     ) -> Pimpl {
+        debug!(target: "ui::chatview", "ChatView::new()");
         let scene_graph = sg.lock().await;
         let node = scene_graph.get_node(node_id).unwrap();
+        let node_name = node.name.clone();
 
         let rect = node.get_property("rect").expect("ChatView::rect");
         let scroll = PropertyFloat32::wrap(node, "scroll", 0).unwrap();
@@ -106,21 +131,65 @@ impl ChatView {
 
         drop(scene_graph);
 
-        let self_ = Arc::new_cyclic(|me: &Weak<Self>| Self {
-            node_id,
-            render_api,
-            text_shaper,
-            tree,
-
-            pages: SyncMutex::new(Vec::new()),
-            dc_key: OsRng.gen(),
-
-            rect,
-            scroll,
-            font_size,
-            line_height,
-            baseline,
-            z_index,
+        let self_ = Arc::new_cyclic(|me: &Weak<Self>| {
+            let ev_sub = event_pub.subscribe_mouse_wheel();
+            let me2 = me.clone();
+            let mouse_wheel_task = ex.spawn(async move {
+                loop {
+                    Self::process_mouse_wheel(&me2, &ev_sub).await;
+                }
+            });
+
+            let ev_sub = event_pub.subscribe_mouse_move();
+            let me2 = me.clone();
+            let mouse_move_task = ex.spawn(async move {
+                loop {
+                    Self::process_mouse_move(&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 mut on_modify = OnModify::new(ex, node_name, node_id, me.clone());
+
+            //on_modify.when_change(scroll.prop(), Self::scrollview);
+
+            async fn redraw(self_: Arc<ChatView>) {
+                self_.redraw().await;
+            }
+            on_modify.when_change(rect.clone(), redraw);
+
+            let mut tasks = vec![mouse_wheel_task, mouse_move_task, touch_task];
+            tasks.append(&mut on_modify.tasks);
+
+            Self {
+                node_id,
+                tasks,
+                sg,
+                render_api,
+                text_shaper,
+                tree,
+
+                pages: SyncMutex::new(Vec::new()),
+                drawcalls: SyncMutex::new(Vec::new()),
+                dc_key: OsRng.gen(),
+
+                mouse_pos: SyncMutex::new(Point::from([0., 0.])),
+                touch_info: SyncMutex::new(TouchInfo::new()),
+
+                rect,
+                scroll,
+                font_size,
+                line_height,
+                baseline,
+                z_index,
+            }
         });
 
         self_.populate().await;
@@ -128,6 +197,141 @@ impl ChatView {
         Pimpl::ChatView(self_)
     }
 
+    async fn process_mouse_wheel(me: &Weak<Self>, ev_sub: &Subscription<(f32, f32)>) {
+        let Ok((wheel_x, wheel_y)) = ev_sub.receive().await else {
+            debug!(target: "ui::chatview", "Event relayer closed");
+            return
+        };
+
+        let Some(self_) = me.upgrade() else {
+            // Should not happen
+            panic!("self destroyed before mouse_wheel_task was stopped!");
+        };
+
+        self_.handle_mouse_wheel(wheel_x, wheel_y).await;
+    }
+
+    async fn process_mouse_move(me: &Weak<Self>, ev_sub: &Subscription<(f32, f32)>) {
+        let Ok((mouse_x, mouse_y)) = ev_sub.receive().await else {
+            debug!(target: "ui::chatview", "Event relayer closed");
+            return
+        };
+
+        let Some(self_) = me.upgrade() else {
+            // Should not happen
+            panic!("self destroyed before mouse_move_task was stopped!");
+        };
+
+        self_.handle_mouse_move(mouse_x, mouse_y).await;
+    }
+
+    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::chatview", "Event relayer closed");
+            return
+        };
+
+        let Some(self_) = me.upgrade() else {
+            // Should not happen
+            panic!("self destroyed before touch_task was stopped!");
+        };
+
+        self_.handle_touch(phase, id, touch_x, touch_y).await;
+    }
+
+    async fn handle_mouse_wheel(&self, wheel_x: f32, wheel_y: f32) {
+        debug!(target: "ui::chatview", "handle_mouse_wheel({wheel_x}, {wheel_y})");
+
+        let Some(rect) = self.get_cached_world_rect().await else { return };
+
+        let mouse_pos = self.mouse_pos.lock().unwrap().clone();
+        if !rect.contains(&mouse_pos) {
+            debug!(target: "ui::chatview", "not inside rect");
+            return
+        }
+        debug!(target: "ui::chatview", "inside rect");
+        let scroll = self.scroll.get();
+        self.scroll.set(scroll + wheel_y * 50.);
+        // Render will auto update from on_modify hook
+    }
+
+    async fn handle_mouse_move(&self, mouse_x: f32, mouse_y: f32) {
+        //debug!(target: "ui::chatview", "handle_mouse_move({mouse_x}, {mouse_y})");
+        let mut mouse_pos = self.mouse_pos.lock().unwrap();
+        mouse_pos.x = mouse_x;
+        mouse_pos.y = mouse_y;
+    }
+
+    async 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 => {
+                let mut touch_info = self.touch_info.lock().unwrap();
+                touch_info.start_y = touch_y;
+                touch_info.start_instant = std::time::Instant::now();
+                touch_info.last_y = touch_y;
+            }
+            TouchPhase::Moved => {
+                let start_y = {
+                    let mut touch_info = self.touch_info.lock().unwrap();
+                    touch_info.last_y = touch_y;
+                    touch_info.start_y
+                };
+
+                let dist = touch_y - start_y;
+                let scroll = self.scroll.get();
+                // TODO the line selected should be fixed and move exactly that distance
+                // No use of multipliers
+                // TODO we are maybe doing too many updates so make a widget to 'slow down'
+                // how often we move to fixed intervals.
+                // draw a poly shape and eval each line segment.
+                self.scroll.set(scroll + dist*0.05);
+                self.scrollview().await;
+            }
+            TouchPhase::Ended => {
+                // Now calculate scroll acceleration
+            }
+            TouchPhase::Cancelled => {}
+        }
+    }
+
+    /// Beware of this method. Here be dragons.
+    /// Possibly racy so we limit it just to mouse stuff (for now).
+    fn cached_rect(&self) -> Rectangle {
+        let Ok(rect) = read_rect(self.rect.clone()) else {
+            panic!("Node bad rect property");
+        };
+        rect
+    }
+    async fn get_parent_rect(&self) -> Option<Rectangle> {
+        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 None;
+        };
+        drop(sg);
+        Some(parent_rect)
+    }
+    async fn get_cached_world_rect(&self) -> Option<Rectangle> {
+        // NBD if it's slightly wrong
+        let mut rect = self.cached_rect();
+
+        // If layers can be nested and we use offsets for (x, y)
+        // then this will be incorrect for nested layers.
+        // For now we don't allow nesting of layers.
+        let parent_rect = self.get_parent_rect().await?;
+
+        // Offset rect which is now in world coords
+        rect.x += parent_rect.x;
+        rect.y += parent_rect.y;
+
+        Some(rect)
+    }
+
     async fn populate(&self) {
         let mut pages = vec![];
         let mut msgs = vec![];
@@ -172,7 +376,80 @@ impl ChatView {
         *self.pages.lock().unwrap() = pages;
     }
 
-    async fn regen_mesh(&self, mut clip: Rectangle) -> Vec<DrawInstruction> {
+    /// Basically a version of redraw() where regen_mesh() is never called.
+    /// Instead we use the cached version.
+    async fn scrollview(&self) {
+        debug!(target: "ui::chatview", "scrollview()");
+        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;
+        };
+
+        if let Err(err) = eval_rect(self.rect.clone(), &parent_rect) {
+            panic!("Node {:?} bad rect property: {}", node, err);
+        }
+
+        let Ok(mut rect) = read_rect(self.rect.clone()) else {
+            panic!("Node {:?} bad rect property", node);
+        };
+
+        //let mut drawcalls = self.regen_mesh(rect.clone()).await;
+
+        debug!(target: "ui::chatview", "chatview rect = {:?}", rect);
+
+        // Apply scroll and scissor
+        // We use the scissor for scrolling
+        // Because we use the scissor, our actual rect is now rect instead of parent_rect
+        let off_x = 0.;
+        // This calc decides whether scroll is in terms of pages or pixels
+        let off_y = (self.scroll.get() + rect.h) / rect.h;
+        let scale_x = 1. / rect.w;
+        let scale_y = 1. / rect.h;
+        let model = glam::Mat4::from_translation(glam::Vec3::new(off_x, off_y, 0.)) *
+            glam::Mat4::from_scale(glam::Vec3::new(scale_x, scale_y, 1.));
+
+        let mut instrs =
+            vec![DrawInstruction::ApplyViewport(rect), DrawInstruction::ApplyMatrix(model)];
+        let drawcalls = self.drawcalls.lock().unwrap().clone();
+        let mut drawcalls: Vec<_> = drawcalls.into_iter().map(|dc| DrawInstruction::Draw(dc)).collect();
+        instrs.append(&mut drawcalls);
+
+        let draw_calls = vec![(
+            self.dc_key,
+            DrawCall { instrs, dcs: vec![], z_index: self.z_index.get() },
+        )];
+
+        self.render_api.replace_draw_calls(draw_calls).await;
+
+        debug!(target: "ui::chatview", "scrollview done");
+    }
+
+    async fn redraw(&self) {
+        debug!(target: "ui::chatview", "redraw()");
+        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;
+        };
+
+        let Some(draw_update) = self.draw(&sg, &parent_rect).await else {
+            error!(target: "ui::chatview", "ChatView {:?} failed to draw", node);
+            return;
+        };
+        self.render_api.replace_draw_calls(draw_update.draw_calls).await;
+        debug!(target: "ui::chatview", "replace draw calls done");
+        for buffer_id in draw_update.freed_buffers {
+            self.render_api.delete_buffer(buffer_id);
+        }
+        for texture_id in draw_update.freed_textures {
+            self.render_api.delete_texture(texture_id);
+        }
+    }
+
+    async fn regen_mesh(&self, mut clip: Rectangle) -> Vec<DrawMesh> {
         let font_size = self.font_size.get();
         let line_height = self.line_height.get();
         let baseline = self.baseline.get();
@@ -220,12 +497,12 @@ impl ChatView {
 
             let mesh = mesh.alloc(&self.render_api).await.unwrap();
 
-            draws.push(DrawInstruction::Draw(DrawMesh {
+            draws.push(DrawMesh {
                 vertex_buffer: mesh.vertex_buffer,
                 index_buffer: mesh.index_buffer,
                 texture: Some(page.atlas.texture_id),
                 num_elements: mesh.num_elements,
-            }));
+            });
         }
 
         if DEBUG_RENDER {
@@ -236,12 +513,12 @@ impl ChatView {
                 2.,
             );
             let mesh = debug_mesh.alloc(&self.render_api).await.unwrap();
-            draws.push(DrawInstruction::Draw(DrawMesh {
+            draws.push(DrawMesh {
                 vertex_buffer: mesh.vertex_buffer,
                 index_buffer: mesh.index_buffer,
                 texture: None,
                 num_elements: mesh.num_elements,
-            }));
+            });
         }
 
         draws
@@ -260,14 +537,23 @@ impl ChatView {
             panic!("Node {:?} bad rect property", node);
         };
 
+        // TODO: Do we need this? Because of the viewport clipping
         rect.x += parent_rect.x;
         rect.y += parent_rect.y;
 
-        let mut drawcalls = self.regen_mesh(rect.clone()).await;
-        // TODO: delete old buffers
-        // we need to save the buffers for that
+        let drawcalls = self.regen_mesh(rect.clone()).await;
+        let old_drawcalls = std::mem::replace(&mut *self.drawcalls.lock().unwrap(), drawcalls.clone());
+        let mut drawcalls: Vec<_> = drawcalls.into_iter().map(|dc| DrawInstruction::Draw(dc)).collect();
+
         let mut freed_textures = vec![];
         let mut freed_buffers = vec![];
+        for old_dc in old_drawcalls {
+            freed_buffers.push(old_dc.vertex_buffer);
+            freed_buffers.push(old_dc.index_buffer);
+            if let Some(texture_id) = old_dc.texture {
+                freed_textures.push(texture_id);
+            }
+        }
 
         debug!(target: "ui::chatview", "chatview rect = {:?}", rect);
 
@@ -275,7 +561,8 @@ impl ChatView {
         // We use the scissor for scrolling
         // Because we use the scissor, our actual rect is now rect instead of parent_rect
         let off_x = 0.;
-        let off_y = self.scroll.get() + rect.h / rect.h;
+        // This calc decides whether scroll is in terms of pages or pixels
+        let off_y = (self.scroll.get() + rect.h) / rect.h;
         let scale_x = 1. / rect.w;
         let scale_y = 1. / rect.h;
         let model = glam::Mat4::from_translation(glam::Vec3::new(off_x, off_y, 0.)) *