Przeglądaj źródła

wallet: editbox use handles to change selected text

darkfi 1 rok temu
rodzic
commit
840cbb857c

+ 3 - 0
bin/darkwallet/Cargo.toml

@@ -53,6 +53,9 @@ simplelog = "0.12.2"
 # For log files
 file-rotate = "0.7.6"
 
+[features]
+emulate-android = []
+
 [patch.crates-io]
 freetype-rs = { git = "https://github.com/narodnik/freetype-rs" }
 freetype-sys = { git = "https://github.com/narodnik/freetype-sys2" }

+ 3 - 1
bin/darkwallet/Makefile

@@ -11,8 +11,10 @@ SRC = \
 	$(shell find src -type f) \
 	$(shell find res -type f)
 
+#FEATURES = --features=emulate-android
+
 all: $(SRC) fonts
-	cargo lbuild
+	cargo lbuild $(FEATURES)
 	mv target/debug/darkwallet .
 	./darkwallet
 

+ 23 - 8
bin/darkwallet/src/app/schema.rs

@@ -44,16 +44,12 @@ use super::{
 
 const LIGHTMODE: bool = false;
 
-#[cfg(target_os = "android")]
-mod ui_consts {
-    pub const CHATDB_PATH: &str = "/data/data/darkfi.darkwallet/chatdb/";
-    pub const KING_PATH: &str = "king.png";
-    pub const BG_PATH: &str = "bg.png";
+mod android_ui_consts {
     pub const EDITCHAT_HEIGHT: f32 = 163.;
     pub const EDITCHAT_CURSOR_ASCENT: f32 = 50.;
     pub const EDITCHAT_CURSOR_DESCENT: f32 = 20.;
-    pub const EDITCHAT_SELECT_ASCENT: f32 = 60.;
-    pub const EDITCHAT_SELECT_DESCENT: f32 = 30.;
+    pub const EDITCHAT_SELECT_ASCENT: f32 = 40.;
+    pub const EDITCHAT_SELECT_DESCENT: f32 = 8.;
     pub const TEXTBAR_BASELINE: f32 = 93.;
     pub const EDITCHAT_LHS_PAD: f32 = 30.;
     pub const SENDLABEL_WIDTH: f32 = 200.;
@@ -66,7 +62,26 @@ mod ui_consts {
     pub const CHATVIEW_BASELINE: f32 = 36.;
 }
 
-#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
+#[cfg(target_os = "android")]
+mod ui_consts {
+    pub const CHATDB_PATH: &str = "/data/data/darkfi.darkwallet/chatdb/";
+    pub const KING_PATH: &str = "king.png";
+    pub const BG_PATH: &str = "bg.png";
+    pub use super::android_ui_consts::*;
+}
+
+#[cfg(feature = "emulate-android")]
+mod ui_consts {
+    pub const CHATDB_PATH: &str = "chatdb";
+    pub const KING_PATH: &str = "assets/king.png";
+    pub const BG_PATH: &str = "assets/bg.png";
+    pub use super::android_ui_consts::*;
+}
+
+#[cfg(all(
+    any(target_os = "linux", target_os = "macos", target_os = "windows"),
+    not(feature = "emulate-android")
+))]
 mod ui_consts {
     pub const CHATDB_PATH: &str = "chatdb";
     pub const KING_PATH: &str = "assets/king.png";

+ 7 - 1
bin/darkwallet/src/gfx/linalg.rs

@@ -54,7 +54,7 @@ impl Div<f32> for Dimension {
     }
 }
 
-#[derive(Clone, Copy, Debug, SerialEncodable, SerialDecodable)]
+#[derive(Clone, Copy, SerialEncodable, SerialDecodable)]
 pub struct Point {
     pub x: f32,
     pub y: f32,
@@ -96,6 +96,12 @@ impl From<[f32; 2]> for Point {
     }
 }
 
+impl std::fmt::Debug for Point {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "({}, {})", self.x, self.y)
+    }
+}
+
 impl Add for Point {
     type Output = Self;
 

+ 20 - 0
bin/darkwallet/src/ui/editbox/editable.rs

@@ -150,6 +150,26 @@ impl RenderedEditable {
         // Everything to the right is at the end
         self.glyphs.len()
     }
+
+    pub fn pos_to_xw(
+        &self,
+        pos: TextPos,
+        font_size: f32,
+        window_scale: f32,
+        baseline: f32,
+    ) -> (f32, f32) {
+        debug!(target: "ui::editbox", "pos_to_xw({pos}) [glyphs_len={}]", self.glyphs.len());
+        let mut glyph_pos_iter =
+            GlyphPositionIter::new(font_size, window_scale, &self.glyphs, baseline);
+        let mut end = 0.;
+        for (glyph_idx, glyph_rect) in glyph_pos_iter.enumerate() {
+            if glyph_idx == pos {
+                return (glyph_rect.x, glyph_rect.w)
+            }
+            end = glyph_rect.rhs();
+        }
+        (end, 0.)
+    }
 }
 
 /// Represents a string with a cursor. The cursor can be moved in terms of glyphs, which does

+ 130 - 56
bin/darkwallet/src/ui/editbox/mod.rs

@@ -161,9 +161,8 @@ enum TouchStateAction {
     Started { pos: Point, instant: std::time::Instant },
     StartSelect,
     Select,
-    ScrollUp,
-    MoveCursor,
-    DragSelectHandle { side: isize }
+    DragSelectHandle { side: isize },
+    ScrollHoriz,
 }
 
 struct TouchInfo {
@@ -177,6 +176,7 @@ impl TouchInfo {
     }
 
     fn start(&mut self, pos: Point) {
+        debug!(target: "ui::editbox", "TouchStateAction::Started");
         self.state = TouchStateAction::Started { pos, instant: std::time::Instant::now() };
     }
 
@@ -188,20 +188,17 @@ impl TouchInfo {
         match &self.state {
             TouchStateAction::Started { pos: start_pos, instant } => {
                 let travel_dist = pos.dist_sq(&start_pos);
-                let y_dist = pos.y - start_pos.y;
+                let x_dist = pos.x - start_pos.x;
                 let elapsed = instant.elapsed().as_millis();
 
                 if travel_dist < 5. {
                     if elapsed > 1000 {
+                        debug!(target: "ui::editbox", "TouchStateAction::StartSelect");
                         self.state = TouchStateAction::StartSelect;
-                        debug!(target: "ui::editbox", "touch long-hold select activated");
                     }
-                } else if y_dist.abs() > 5. {
-                    self.state = TouchStateAction::ScrollUp;
-                    debug!(target: "ui::editbox", "touch scroll-up activated");
-                } else {
-                    self.state = TouchStateAction::MoveCursor;
-                    debug!(target: "ui::editbox", "move-cursor activated");
+                } else if x_dist.abs() > 5. {
+                    debug!(target: "ui::editbox", "TouchStateAction::ScrollHoriz");
+                    self.state = TouchStateAction::ScrollHoriz;
                 }
             }
             _ => {}
@@ -581,62 +578,51 @@ impl EditBox {
         mesh.draw_box(&select_rect, hi_bg_color, &Rectangle::zero());
 
         if is_phone_select {
-            self.draw_phone_select_handle(mesh, start_x);
-            self.draw_phone_select_handle(mesh, end_x);
+            self.draw_phone_select_handle(mesh, start_x, -1.);
+            self.draw_phone_select_handle(mesh, end_x, 1.);
         }
     }
 
-    fn draw_phone_select_handle(&self, mesh: &mut MeshBuilder, x: f32) {
+    fn draw_phone_select_handle(&self, mesh: &mut MeshBuilder, x: f32, side: f32) {
+        debug!(target: "ui::editbox", "draw_phone_select_handle(..., {x}, {side})");
+
         let baseline = self.baseline.get();
         let select_ascent = self.select_ascent.get();
         let select_descent = self.select_descent.get();
         let color = self.text_hi_color.get();
+        // Transparent for fade
+        let mut color_trans = color.clone();
+        color_trans[3] = 0.;
 
-        const HANDLE_HEIGHT: f32 = 10.;
-        const DIAMOND_HEIGHT: f32 = 35.;
-        const DIAMOND_RADIUS: f32 = 25.;
-        const BEVEL_HEIGHT: f32 = 15.;
-        const BEVEL_WIDTH: f32 = 5.;
-
-        let line_rect = Rectangle {
-            // Make x the middle
-            x: x - 1.,
-            y: baseline - select_ascent - HANDLE_HEIGHT,
-            // Extend 1px on either side
-            w: 2.,
-            h: select_ascent + select_descent + HANDLE_HEIGHT,
-        };
-        mesh.draw_box(&line_rect, color, &Rectangle::zero());
-
-        let y = line_rect.y;
-
-        // Go anti-clockwise
+        // Vertical line downwards. We use this instead of draw_box() so we have a fade.
         let verts = vec![
-            Vertex { pos: [x, y], color, uv: [0., 0.] },
-            Vertex { pos: [x - DIAMOND_RADIUS, y - DIAMOND_HEIGHT], color, uv: [0., 0.] },
-            Vertex { pos: [x + DIAMOND_RADIUS, y - DIAMOND_HEIGHT], color, uv: [0., 0.] },
-            Vertex {
-                pos: [x - DIAMOND_RADIUS + BEVEL_WIDTH, y - DIAMOND_HEIGHT],
-                color,
-                uv: [0., 0.],
-            },
-            Vertex {
-                pos: [x - DIAMOND_RADIUS + 2. * BEVEL_WIDTH, y - DIAMOND_HEIGHT - BEVEL_HEIGHT],
-                color,
-                uv: [0., 0.],
-            },
             Vertex {
-                pos: [x + DIAMOND_RADIUS - BEVEL_WIDTH, y - DIAMOND_HEIGHT],
-                color,
+                pos: [x - side * 1., baseline - select_ascent],
+                color: color_trans,
                 uv: [0., 0.],
             },
             Vertex {
-                pos: [x + DIAMOND_RADIUS - 2. * BEVEL_WIDTH, y - DIAMOND_HEIGHT - BEVEL_HEIGHT],
-                color,
+                pos: [x + side * 4., baseline - select_ascent],
+                color: color_trans,
                 uv: [0., 0.],
             },
+            Vertex { pos: [x - side * 1., baseline + select_descent + 5.], color, uv: [0., 0.] },
+            Vertex { pos: [x + side * 4., baseline + select_descent + 5.], color, uv: [0., 0.] },
         ];
-        let indices = vec![0, 2, 1, 1, 3, 4, 2, 5, 6, 4, 3, 5, 4, 5, 6];
+        let indices = vec![0, 2, 1, 1, 2, 3];
+        mesh.append(verts, indices);
+
+        let y = baseline + select_descent;
+
+        // The arrow itself.
+        // Go anti-clockwise
+        let verts = vec![
+            Vertex { pos: [x, y], color, uv: [0., 0.] },
+            Vertex { pos: [x, y + 50.], color, uv: [0., 0.] },
+            Vertex { pos: [x + side * 30., y + 50.], color, uv: [0., 0.] },
+            Vertex { pos: [x + side * 50., y + 25.], color, uv: [0., 0.] },
+        ];
+        let indices = vec![0, 1, 2, 0, 2, 3];
         mesh.append(verts, indices);
     }
 
@@ -956,39 +942,127 @@ impl EditBox {
         debug!(target: "ui::editbox", "handle_touch_start({pos:?})");
         let mut touch_info = self.touch_info.lock().unwrap();
 
+        if self.try_handle_drag(&mut touch_info, pos) {
+            return true
+        }
+
+        let rect = self.rect.get();
+        if !rect.contains(pos) {}
+
+        touch_info.start(pos);
+        true
+    }
+    fn try_handle_drag(&self, touch_info: &mut TouchInfo, pos: Point) -> bool {
         let selections = self.select.lock().unwrap().clone();
+
         if self.is_phone_select.load(Ordering::Relaxed) && selections.len() == 1 {
+            let select = selections.first().unwrap();
+
+            let rendered = {
+                let editable = self.editable.lock().unwrap();
+                editable.render()
+            };
+
+            let font_size = self.font_size.get();
+            let window_scale = self.window_scale.get();
+            let baseline = self.baseline.get();
+
             // Get left handle centerpoint
+            let x1 = rendered.pos_to_xw(select.start, font_size, window_scale, baseline).0;
             // Get right handle centerpoint
+            let x2 = {
+                let (x, w) = rendered.pos_to_xw(select.end, font_size, window_scale, baseline);
+                x + w
+            };
+
             // Are we within range of either one?
-            // Set touch_state status to enable begin dragging them
+            let select_descent = self.select_descent.get();
+            let y = baseline + select_descent + 25.;
+
+            let p1 = Point::new(x1, y);
+            let p2 = Point::new(x2, y);
+            debug!(target: "ui::editbox", "handle center points = ({p1:?}, {p2:?})");
+
+            const TOUCH_RADIUS_SQ: f32 = 10_000.;
+            // Make pos relative to the rect
+            let pos_rel = pos - self.rect.get().pos();
+
+            if p1.dist_sq(&pos_rel) <= TOUCH_RADIUS_SQ {
+                debug!(target: "ui::editbox", "TouchStateAction::DragSelectHandle [side=-1]");
+                // Set touch_state status to enable begin dragging them
+                touch_info.state = TouchStateAction::DragSelectHandle { side: -1 };
+                return true;
+            }
+            if p2.dist_sq(&pos_rel) <= TOUCH_RADIUS_SQ {
+                debug!(target: "ui::editbox", "TouchStateAction::DragSelectHandle [side=1]");
+                // Set touch_state status to enable begin dragging them
+                touch_info.state = TouchStateAction::DragSelectHandle { side: 1 };
+                return true;
+            }
         }
 
-        touch_info.start(pos.clone());
         false
     }
+
     async fn handle_touch_move(&self, pos: Point) -> bool {
-        debug!(target: "ui::editbox", "handle_touch_move({pos:?})");
+        //debug!(target: "ui::editbox", "handle_touch_move({pos:?})");
         let touch_state = {
             let mut touch_info = self.touch_info.lock().unwrap();
             touch_info.update(&pos);
             touch_info.state.clone()
         };
         match &touch_state {
+            TouchStateAction::Inactive => return false,
             TouchStateAction::StartSelect => {
                 let x = pos.x;
                 self.start_touch_select(x);
                 self.redraw().await;
+                debug!(target: "ui::editbox", "TouchStateAction::Select");
                 self.touch_info.lock().unwrap().state = TouchStateAction::Select;
             }
+            TouchStateAction::DragSelectHandle { side } => {
+                {
+                    assert!(*side == -1 || *side == 1);
+                    assert!(self.is_phone_select.load(Ordering::Relaxed));
+                    let mut selections = self.select.lock().unwrap();
+                    assert_eq!(selections.len(), 1);
+                    let select = selections.first_mut().unwrap();
+
+                    let rendered = {
+                        let editable = self.editable.lock().unwrap();
+                        editable.render()
+                    };
+
+                    let font_size = self.font_size.get();
+                    let window_scale = self.window_scale.get();
+                    let baseline = self.baseline.get();
+
+                    let mut pos = rendered.x_to_pos(pos.x, font_size, window_scale, baseline);
+                    if *side == -1 {
+                        let select_other_pos = &mut select.end;
+                        if pos >= *select_other_pos {
+                            pos = *select_other_pos - 1;
+                        }
+                        select.start = pos;
+                    } else {
+                        let select_other_pos = &mut select.start;
+                        if pos <= *select_other_pos {
+                            pos = *select_other_pos + 1;
+                        }
+                        select.end = pos;
+                    }
+                }
+                self.redraw().await;
+            }
             _ => {}
         }
-        false
+        true
     }
     async fn handle_touch_end(&self, pos: Point) -> bool {
         debug!(target: "ui::editbox", "handle_touch_end({pos:?})");
         let state = self.touch_info.lock().unwrap().stop();
         match state {
+            TouchStateAction::Inactive => return false,
             TouchStateAction::Started { pos: _, instant: _ } => {
                 let rect = self.rect.get();
                 let font_size = self.font_size.get();
@@ -1019,7 +1093,7 @@ impl EditBox {
             _ => {}
         }
         window::show_keyboard(true);
-        false
+        true
     }
 
     /// Whenever the cursor property is modified this MUST be called

+ 30 - 6
bin/darkwallet/src/ui/win.rs

@@ -31,6 +31,12 @@ use crate::{
 
 use super::{get_children_ordered, get_ui_object3, get_ui_object_ptr, OnModify};
 
+#[cfg(feature = "emulate-android")]
+const EMULATE_TOUCH: bool = true;
+
+#[cfg(not(feature = "emulate-android"))]
+const EMULATE_TOUCH: bool = false;
+
 pub type WindowPtr = Arc<Window>;
 
 pub struct Window {
@@ -351,8 +357,14 @@ impl Window {
         self.local_scale(&mut mouse_pos);
         for child in self.get_children() {
             let obj = get_ui_object3(&child);
-            if obj.handle_mouse_btn_down(btn.clone(), mouse_pos).await {
-                return
+            if EMULATE_TOUCH {
+                if obj.handle_touch(TouchPhase::Started, 0, mouse_pos).await {
+                    return
+                }
+            } else {
+                if obj.handle_mouse_btn_down(btn.clone(), mouse_pos).await {
+                    return
+                }
             }
         }
     }
@@ -361,8 +373,14 @@ impl Window {
         self.local_scale(&mut mouse_pos);
         for child in self.get_children() {
             let obj = get_ui_object3(&child);
-            if obj.handle_mouse_btn_up(btn.clone(), mouse_pos).await {
-                return
+            if EMULATE_TOUCH {
+                if obj.handle_touch(TouchPhase::Ended, 0, mouse_pos).await {
+                    return
+                }
+            } else {
+                if obj.handle_mouse_btn_up(btn.clone(), mouse_pos).await {
+                    return
+                }
             }
         }
     }
@@ -371,8 +389,14 @@ impl Window {
         self.local_scale(&mut mouse_pos);
         for child in self.get_children() {
             let obj = get_ui_object3(&child);
-            if obj.handle_mouse_move(mouse_pos).await {
-                return
+            if EMULATE_TOUCH {
+                if obj.handle_touch(TouchPhase::Moved, 0, mouse_pos).await {
+                    return
+                }
+            } else {
+                if obj.handle_mouse_move(mouse_pos).await {
+                    return
+                }
             }
         }
     }