Procházet zdrojové kódy

wallet: editbox highlighting text with the mouse

darkfi před 2 roky
rodič
revize
8a40ac3cf8
2 změnil soubory, kde provedl 224 přidání a 39 odebrání
  1. 15 15
      bin/darkwallet/src/gfx2.rs
  2. 209 24
      bin/darkwallet/src/ui/editbox.rs

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

@@ -348,8 +348,8 @@ pub struct GraphicsEventPublisher {
     lock_key_up: SyncMutex<Option<SubscriptionId>>,
     key_up: PublisherPtr<(KeyCode, KeyMods)>,
 
-    lock_mouse_motion: SyncMutex<Option<SubscriptionId>>,
-    mouse_motion: PublisherPtr<(f32, f32)>,
+    lock_mouse_move: SyncMutex<Option<SubscriptionId>>,
+    mouse_move: PublisherPtr<(f32, f32)>,
 
     lock_mouse_wheel: SyncMutex<Option<SubscriptionId>>,
     mouse_wheel: PublisherPtr<(f32, f32)>,
@@ -376,8 +376,8 @@ impl GraphicsEventPublisher {
             lock_key_up: SyncMutex::new(None),
             key_up: Publisher::new(),
 
-            lock_mouse_motion: SyncMutex::new(None),
-            mouse_motion: Publisher::new(),
+            lock_mouse_move: SyncMutex::new(None),
+            mouse_move: Publisher::new(),
 
             lock_mouse_wheel: SyncMutex::new(None),
             mouse_wheel: Publisher::new(),
@@ -410,11 +410,11 @@ impl GraphicsEventPublisher {
         *self.lock_key_up.lock().unwrap() = None;
     }
 
-    fn lock_mouse_motion(&self, sub_id: SubscriptionId) {
-        *self.lock_mouse_motion.lock().unwrap() = Some(sub_id);
+    fn lock_mouse_move(&self, sub_id: SubscriptionId) {
+        *self.lock_mouse_move.lock().unwrap() = Some(sub_id);
     }
-    fn unlock_mouse_motion(&self) {
-        *self.lock_mouse_motion.lock().unwrap() = None;
+    fn unlock_mouse_move(&self) {
+        *self.lock_mouse_move.lock().unwrap() = None;
     }
 
     fn lock_mouse_wheel(&self, sub_id: SubscriptionId) {
@@ -473,14 +473,14 @@ impl GraphicsEventPublisher {
         }
     }
 
-    fn notify_mouse_motion(&self, x: f32, y: f32) {
+    fn notify_mouse_move(&self, x: f32, y: f32) {
         let ev = (x, y);
 
-        let locked = self.lock_mouse_motion.lock().unwrap().clone();
+        let locked = self.lock_mouse_move.lock().unwrap().clone();
         if let Some(locked) = locked {
-            self.mouse_motion.notify_with_include(ev, &[locked]);
+            self.mouse_move.notify_with_include(ev, &[locked]);
         } else {
-            self.mouse_motion.notify(ev);
+            self.mouse_move.notify(ev);
         }
     }
 
@@ -544,8 +544,8 @@ impl GraphicsEventPublisher {
     pub fn subscribe_key_up(&self) -> Subscription<(KeyCode, KeyMods)> {
         self.key_up.clone().subscribe()
     }
-    pub fn subscribe_mouse_motion(&self) -> Subscription<(f32, f32)> {
-        self.mouse_motion.clone().subscribe()
+    pub fn subscribe_mouse_move(&self) -> Subscription<(f32, f32)> {
+        self.mouse_move.clone().subscribe()
     }
     pub fn subscribe_mouse_wheel(&self) -> Subscription<(f32, f32)> {
         self.mouse_wheel.clone().subscribe()
@@ -772,7 +772,7 @@ impl EventHandler for Stage {
     }
 
     fn mouse_motion_event(&mut self, x: f32, y: f32) {
-        self.event_pub.notify_mouse_motion(x, y);
+        self.event_pub.notify_mouse_move(x, y);
     }
     fn mouse_wheel_event(&mut self, x: f32, y: f32) {
         self.event_pub.notify_mouse_wheel(x, y);

+ 209 - 24
bin/darkwallet/src/ui/editbox.rs

@@ -2,7 +2,10 @@ use miniquad::{window, BufferId, KeyCode, KeyMods, MouseButton, TextureId};
 use rand::{rngs::OsRng, Rng};
 use std::{
     collections::HashMap,
-    sync::{Arc, Mutex as SyncMutex, Weak},
+    sync::{
+        atomic::{AtomicBool, Ordering},
+        Arc, Mutex as SyncMutex, Weak,
+    },
     time::Instant,
 };
 
@@ -139,6 +142,8 @@ pub struct EditBox {
     selected: PropertyPtr,
     z_index: PropertyUint32,
     debug: PropertyBool,
+
+    mouse_btn_held: AtomicBool,
 }
 
 impl EditBox {
@@ -224,11 +229,34 @@ impl EditBox {
                 }
             });
 
+            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_mouse_move();
+            let me2 = me.clone();
+            let mouse_move_task = ex.spawn(async move {
+                loop {
+                    Self::process_mouse_move(&me2, &ev_sub).await;
+                }
+            });
+
             let mut on_modify = OnModify::new(ex, node_name, node_id, me.clone());
             on_modify.when_change(is_focused.prop(), Self::change_focus);
 
             // on modify tasks too
-            let tasks = vec![char_task, key_down_task, mouse_btn_down_task];
+            let mut tasks = vec![
+                char_task,
+                key_down_task,
+                mouse_btn_down_task,
+                mouse_btn_up_task,
+                mouse_move_task,
+            ];
+            tasks.append(&mut on_modify.tasks);
 
             Self {
                 node_id,
@@ -257,6 +285,8 @@ impl EditBox {
                 selected,
                 z_index,
                 debug,
+
+                mouse_btn_held: AtomicBool::new(false),
             }
         });
 
@@ -269,6 +299,7 @@ impl EditBox {
         clip.x = 0.;
         clip.y = 0.;
 
+        let is_focused = self.is_focused.get();
         let text = self.text.get();
         let font_size = self.font_size.get();
         let text_color = self.text_color.get();
@@ -301,7 +332,7 @@ impl EditBox {
             }
             mesh.draw_box(&glyph_rect, color, &uv_rect);
 
-            if cursor_pos != 0 && cursor_pos == glyph_idx {
+            if is_focused && cursor_pos != 0 && cursor_pos == glyph_idx {
                 let cursor_rect =
                     Rectangle { x: glyph_rect.x - CURSOR_WIDTH, y: 0., w: CURSOR_WIDTH, h: clip.h };
                 mesh.draw_box(&cursor_rect, cursor_color, &Rectangle::zero());
@@ -310,10 +341,10 @@ impl EditBox {
             rhs = glyph_rect.rhs();
         }
 
-        if cursor_pos == 0 {
+        if is_focused && cursor_pos == 0 {
             let cursor_rect = Rectangle { x: 0., y: 0., w: CURSOR_WIDTH, h: clip.h };
             mesh.draw_box(&cursor_rect, cursor_color, &Rectangle::zero());
-        } else if cursor_pos == glyphs.len() {
+        } else if is_focused && cursor_pos == glyphs.len() {
             let cursor_rect =
                 Rectangle { x: rhs - CURSOR_WIDTH, y: 0., w: CURSOR_WIDTH, h: clip.h };
             mesh.draw_box(&cursor_rect, cursor_color, &Rectangle::zero());
@@ -472,7 +503,7 @@ impl EditBox {
 
         let Some(self_) = me.upgrade() else {
             // Should not happen
-            panic!("self destroyed before char_task was stopped!");
+            panic!("self destroyed before mouse_btn_down_task was stopped!");
         };
 
         if !self_.is_active.get() {
@@ -482,12 +513,52 @@ impl EditBox {
         self_.handle_mouse_btn_down(btn, mouse_x, mouse_y).await;
     }
 
+    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::editbox", "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_mouse_move(me: &Weak<Self>, ev_sub: &Subscription<(f32, f32)>) {
+        let Ok((mouse_x, mouse_y)) = ev_sub.receive().await else {
+            debug!(target: "ui::editbox", "Event relayer closed");
+            return
+        };
+
+        let Some(self_) = me.upgrade() else {
+            // Should not happen
+            panic!("self destroyed before mouse_move_task was stopped!");
+        };
+
+        if !self_.is_active.get() {
+            return
+        }
+
+        self_.handle_mouse_move(mouse_x, mouse_y).await;
+    }
+
     async fn change_focus(self: Arc<Self>) {
         if !self.is_active.get() {
             return
         }
+        debug!(target: "ui::editbox", "Focus changed");
 
         let is_focused = self.is_focused.get();
+
+        // Cursor visibility will change so just redraw everything lol
+        self.redraw().await;
     }
 
     async fn handle_mouse_btn_down(&self, btn: MouseButton, mouse_x: f32, mouse_y: f32) {
@@ -495,36 +566,126 @@ impl EditBox {
             return
         }
 
-        // 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 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;
-        };
-        drop(sg);
-
-        // Offset rect which is now in world coords
-        rect.x += parent_rect.x;
-        rect.y += parent_rect.y;
-
         let mouse_pos = Point::from([mouse_x, mouse_y]);
 
+        let mut focus_changed = false;
+
+        let Some(rect) = self.get_cached_world_rect().await else { return };
+        // clicking inside box will:
+        // 1. make it active
+        // 2. begin selection
         if rect.contains(&mouse_pos) {
+            window::show_keyboard(true);
+
             if self.is_focused.get() {
                 debug!(target: "ui::editbox", "EditBox clicked");
             } else {
                 debug!(target: "ui::editbox", "EditBox focused");
                 self.is_focused.set(true);
+                focus_changed = true;
             }
+
+            let cpos = self.find_closest_glyph_idx(mouse_x, &rect);
+
+            // set cursor pos
+            self.cursor_pos.set(cpos);
+            self.apply_cursor_scrolling();
+
+            // begin selection
+            self.selected.set_u32(0, cpos).unwrap();
+            self.selected.set_u32(1, cpos).unwrap();
+
+            self.mouse_btn_held.store(true, Ordering::Relaxed);
+        // click outside the box will make it unfocused
         } else if self.is_focused.get() {
             debug!(target: "ui::editbox", "EditBox unfocused");
             self.is_focused.set(false);
+            focus_changed = true;
+        }
+
+        // Further on_focus logic change is handled by property modified callback
+        // which calls Self::change_focus()
+        // We still need to redraw if cursor is changed though, but we want to avoid redrawing
+        // twice, so we do this check:
+        if !focus_changed {
+            self.redraw().await;
+        }
+    }
+    fn handle_mouse_btn_up(&self, button: MouseButton, x: f32, y: f32) {
+        // releasing mouse button will end selection
+        self.mouse_btn_held.store(false, Ordering::Relaxed);
+    }
+    async fn handle_mouse_move(&self, mouse_x: f32, mouse_y: f32) {
+        if !self.mouse_btn_held.load(Ordering::Relaxed) {
+            return;
+        }
+
+        // if active and selection_active, then use x to modify the selection.
+        // also implement scrolling when cursor is to the left or right
+        // just scroll to the end
+        // also set cursor_pos too
+
+        let Some(rect) = self.get_cached_world_rect().await else { return };
+        let cpos = self.find_closest_glyph_idx(mouse_x, &rect);
+
+        self.cursor_pos.set(cpos);
+        self.selected.set_u32(1, cpos).unwrap();
+
+        self.apply_cursor_scrolling();
+        self.redraw().await;
+    }
+
+    /// Used when clicking the text. Given the x coord of the mouse, it finds the index
+    /// of the closest glyph to that x coord.
+    fn find_closest_glyph_idx(&self, x: f32, rect: &Rectangle) -> u32 {
+        let font_size = self.font_size.get();
+        let baseline = self.baseline.get();
+        let glyphs = self.glyphs.lock().unwrap().clone();
+
+        let mouse_x = x - rect.x;
+
+        if mouse_x > rect.w {
+            // Highlight to the end
+            let cpos = glyphs.len() as u32;
+            return cpos;
+            // Scroll to the right handled in render
+        } else if mouse_x < 0. {
+            return 0;
+        }
+
+        let scroll = self.scroll.get();
+
+        let mut cpos = 0;
+        let lhs = 0.;
+        let mut last_d = (lhs - mouse_x).abs();
+
+        let mut glyph_pos_iter = GlyphPositionIter::new(font_size, &glyphs, baseline);
+        let mut rhs = 0.;
+
+        for (i, glyph_rect) in glyph_pos_iter.skip(1).enumerate() {
+            // Because we skip the first item
+            let glyph_idx = (i + 1) as u32;
+
+            let x1 = glyph_rect.x + scroll;
+
+            // I don't know what this is doing but it works so I won't touch it for now.
+            let curr_d = (x1 - mouse_x).abs();
+            if curr_d < last_d {
+                last_d = curr_d;
+                cpos = glyph_idx;
+            }
+
+            rhs = glyph_rect.rhs();
+        }
+
+        // also check the right hand side
+        let curr_d = (rhs - mouse_x).abs();
+        if curr_d < last_d {
+            //last_d = curr_d;
+            cpos = glyphs.len() as u32;
         }
+
+        cpos
     }
 
     async fn insert_char(&self, key: char) {
@@ -804,6 +965,30 @@ impl EditBox {
         };
         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)
+    }
 
     /// Whenever the cursor property is modified this MUST be called
     /// to recalculate the scroll x property.
@@ -817,7 +1002,7 @@ impl EditBox {
         let cursor_x = {
             let font_size = self.font_size.get();
             let baseline = self.baseline.get();
-            let glyphs = &*self.glyphs.lock().unwrap();
+            let glyphs = self.glyphs.lock().unwrap().clone();
 
             let mut glyph_pos_iter = GlyphPositionIter::new(font_size, &glyphs, baseline);