Просмотр исходного кода

wallet: add actions bar with copy, paste and select all actions

darkfi 1 год назад
Родитель
Сommit
695ab91031

+ 3 - 3
bin/darkwallet/gui/print_tree.py

@@ -6,9 +6,9 @@ def join(parent_path, child_name):
         return f"/{child_name}"
     return f"{parent_path}/{child_name}"
 
-def print_tree():
-    print("/")
-    print_node_info("/", indent=1)
+def print_tree(node_path="/"):
+    print(node_path)
+    print_node_info(node_path, indent=1)
 
 def print_node_info(parent_path, indent):
     ws = " "*4*indent

+ 1 - 2
bin/darkwallet/src/app/node.rs

@@ -350,8 +350,7 @@ pub fn create_chatedit(name: &str) -> SceneNode {
     prop.set_range_f32(0., 1.);
     node.add_property(prop).unwrap();
 
-    let mut prop = Property::new("selected", PropertyType::Uint32, PropertySubType::Color);
-    prop.set_array_len(2);
+    let mut prop = Property::new("select_text", PropertyType::Str, PropertySubType::Null);
     prop.allow_null_values();
     prop.set_defaults_null().unwrap();
     node.add_property(prop).unwrap();

+ 233 - 5
bin/darkwallet/src/app/schema/chat.rs

@@ -29,7 +29,7 @@ use crate::{
     },
     error::Error,
     expr::{self, Compiler, Op},
-    gfx::{GraphicsEventPublisherPtr, Rectangle, RenderApi, Vertex},
+    gfx::{GraphicsEventPublisherPtr, Point, Rectangle, RenderApi, Vertex},
     mesh::{Color, MeshBuilder},
     prop::{
         Property, PropertyBool, PropertyFloat32, PropertyStr, PropertySubType, PropertyType, Role,
@@ -47,6 +47,8 @@ use crate::{
 const LIGHTMODE: bool = false;
 
 mod android_ui_consts {
+    use crate::gfx::{Point, Rectangle};
+
     pub const CHANNEL_LABEL_BASELINE: f32 = 82.;
     pub const BACKARROW_SCALE: f32 = 30.;
     pub const BACKARROW_X: f32 = 50.;
@@ -80,6 +82,12 @@ mod android_ui_consts {
     pub const MESSAGE_SPACING: f32 = 15.;
     pub const LINE_HEIGHT: f32 = 58.;
     pub const CHATVIEW_BASELINE: f32 = 36.;
+
+    pub const ACTION_POPUP_Y_OFF: f32 = 200.;
+    pub const ACTION_COPY_RECT: Rectangle = Rectangle::new(0., 0., 200., 160.);
+    pub const ACTION_PASTE_RECT: Rectangle = Rectangle::new(220., 0., 240., 160.);
+    pub const ACTION_SELECT_ALL_RECT: Rectangle = Rectangle::new(480., 0., 400., 160.);
+    pub const ACTION_LABEL_POS: Point = Point::new(40., 92.);
 }
 
 #[cfg(target_os = "android")]
@@ -101,6 +109,8 @@ mod ui_consts {
     not(feature = "emulate-android")
 ))]
 mod ui_consts {
+    use crate::gfx::{Point, Rectangle};
+
     pub const CHATDB_PATH: &str = "chatdb";
     pub const BG_PATH: &str = "assets/bg.png";
 
@@ -140,6 +150,12 @@ mod ui_consts {
     pub const MESSAGE_SPACING: f32 = 5.;
     pub const LINE_HEIGHT: f32 = 30.;
     pub const CHATVIEW_BASELINE: f32 = 20.;
+
+    pub const ACTION_POPUP_Y_OFF: f32 = 100.;
+    pub const ACTION_COPY_RECT: Rectangle = Rectangle::new(0., 0., 100., 80.);
+    pub const ACTION_PASTE_RECT: Rectangle = Rectangle::new(110., 0., 120., 80.);
+    pub const ACTION_SELECT_ALL_RECT: Rectangle = Rectangle::new(240., 0., 200., 80.);
+    pub const ACTION_LABEL_POS: Point = Point::new(20., 46.);
 }
 
 use super::EMOJI_PICKER_ICON_SIZE;
@@ -167,6 +183,7 @@ pub async fn make(
     cc.add_const_f32("EMOJIBTN_BOX_1", EMOJIBTN_BOX[1]);
     cc.add_const_f32("SENDBTN_BOX_0", SENDBTN_BOX[0]);
     cc.add_const_f32("SENDBTN_BOX_1", SENDBTN_BOX[1]);
+    cc.add_const_f32("ACTION_POPUP_Y_OFF", ACTION_POPUP_Y_OFF);
 
     // Main view
     let layer_node = create_layer(&(channel.to_string() + "_chat_layer"));
@@ -338,7 +355,7 @@ pub async fn make(
     layer_node.set_property_u32(Role::App, "z_index", 1).unwrap();
     let layer_node =
         layer_node.setup(|me| Layer::new(me, app.render_api.clone(), app.ex.clone())).await;
-    chat_layer_node.link(layer_node.clone());
+    chat_layer_node.clone().link(layer_node.clone());
 
     // ChatView
     let node = create_chatview("chatty");
@@ -653,12 +670,11 @@ pub async fn make(
         prop.set_f32(Role::App, 2, 0.22).unwrap();
         prop.set_f32(Role::App, 3, 1.).unwrap();
     }
-    let prop = node.get_property("selected").unwrap();
-    prop.set_null(Role::App, 0).unwrap();
-    prop.set_null(Role::App, 1).unwrap();
     node.set_property_u32(Role::App, "z_index", 3).unwrap();
     //node.set_property_bool(Role::App, "debug", true).unwrap();
 
+    let editz_select_text_prop = node.get_property("select_text").unwrap();
+
     //let editbox_text = PropertyStr::wrap(node, Role::App, "text", 0).unwrap();
     //let editbox_focus = PropertyBool::wrap(node, Role::App, "is_focused", 0).unwrap();
     //let darkirc_backend = app.darkirc_backend.clone();
@@ -795,4 +811,216 @@ pub async fn make(
         node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
     layer_node.clone().link(node);
     */
+
+    // Overlay popup
+    let layer_node = create_layer("overlay");
+    let prop = layer_node.get_property("rect").unwrap();
+    prop.set_f32(Role::App, 0, 40.).unwrap();
+    let code = cc.compile("editz_bg_top_y - ACTION_POPUP_Y_OFF").unwrap();
+    //let code = cc.compile("h - 60 - 80").unwrap();
+    //let code = cc.compile("h - 60 - 300").unwrap();
+    prop.set_expr(Role::App, 1, code).unwrap();
+    prop.set_f32(Role::App, 2, ACTION_SELECT_ALL_RECT.rhs()).unwrap();
+    prop.set_f32(Role::App, 3, ACTION_SELECT_ALL_RECT.h).unwrap();
+    prop.add_depend(&editbox_bg_rect_prop, 1, "editz_bg_top_y");
+    layer_node.set_property_bool(Role::App, "is_visible", false).unwrap();
+    layer_node.set_property_u32(Role::App, "z_index", 2).unwrap();
+    let layer_node =
+        layer_node.setup(|me| Layer::new(me, app.render_api.clone(), app.ex.clone())).await;
+    chat_layer_node.link(layer_node.clone());
+
+    let actions_is_visible = PropertyBool::wrap(&layer_node, Role::App, "is_visible", 0).unwrap();
+
+    // Create the actionbar bg
+    let node = create_vector_art("actionbar_bg");
+    let prop = node.get_property("rect").unwrap();
+    prop.set_f32(Role::App, 0, 0.).unwrap();
+    prop.set_f32(Role::App, 1, 0.).unwrap();
+    prop.set_f32(Role::App, 2, ACTION_SELECT_ALL_RECT.rhs()).unwrap();
+    prop.set_f32(Role::App, 3, ACTION_SELECT_ALL_RECT.h).unwrap();
+    node.set_property_u32(Role::App, "z_index", 0).unwrap();
+
+    let mut shape = VectorShape::new();
+
+    let color1 = [0., 0., 0., 0.4];
+    let color2 = [0., 0., 0., 0.9];
+    let gradient = [color1.clone(), color1, color2.clone(), color2];
+    let hicolor = [0., 0.94, 1., 1.];
+
+    // Copy box
+    shape.add_gradient_box(
+        expr::const_f32(ACTION_COPY_RECT.x),
+        expr::const_f32(ACTION_COPY_RECT.y),
+        expr::const_f32(ACTION_COPY_RECT.rhs()),
+        expr::const_f32(ACTION_COPY_RECT.bhs()),
+        gradient.clone(),
+    );
+
+    // Copy outline
+    shape.add_outline(
+        expr::const_f32(ACTION_COPY_RECT.x),
+        expr::const_f32(ACTION_COPY_RECT.y),
+        expr::const_f32(ACTION_COPY_RECT.rhs()),
+        expr::const_f32(ACTION_COPY_RECT.bhs()),
+        1.,
+        hicolor.clone(),
+    );
+
+    // Paste box
+    shape.add_gradient_box(
+        expr::const_f32(ACTION_PASTE_RECT.x),
+        expr::const_f32(ACTION_PASTE_RECT.y),
+        expr::const_f32(ACTION_PASTE_RECT.rhs()),
+        expr::const_f32(ACTION_PASTE_RECT.bhs()),
+        gradient.clone(),
+    );
+
+    // Paste outline
+    shape.add_outline(
+        expr::const_f32(ACTION_PASTE_RECT.x),
+        expr::const_f32(ACTION_PASTE_RECT.y),
+        expr::const_f32(ACTION_PASTE_RECT.rhs()),
+        expr::const_f32(ACTION_PASTE_RECT.bhs()),
+        1.,
+        hicolor.clone(),
+    );
+
+    // Select all box
+    shape.add_gradient_box(
+        expr::const_f32(ACTION_SELECT_ALL_RECT.x),
+        expr::const_f32(ACTION_SELECT_ALL_RECT.y),
+        expr::const_f32(ACTION_SELECT_ALL_RECT.rhs()),
+        expr::const_f32(ACTION_SELECT_ALL_RECT.bhs()),
+        gradient,
+    );
+
+    // Select all outline
+    shape.add_outline(
+        expr::const_f32(ACTION_SELECT_ALL_RECT.x),
+        expr::const_f32(ACTION_SELECT_ALL_RECT.y),
+        expr::const_f32(ACTION_SELECT_ALL_RECT.rhs()),
+        expr::const_f32(ACTION_SELECT_ALL_RECT.bhs()),
+        1.,
+        hicolor,
+    );
+
+    let node =
+        node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
+    layer_node.clone().link(node);
+
+    // Create some text
+    let node = create_text("actions_label");
+    let prop = node.get_property("rect").unwrap();
+    prop.set_f32(Role::App, 0, ACTION_LABEL_POS.x).unwrap();
+    prop.set_f32(Role::App, 1, ACTION_LABEL_POS.y).unwrap();
+    prop.set_f32(Role::App, 2, ACTION_SELECT_ALL_RECT.rhs()).unwrap();
+    prop.set_f32(Role::App, 3, ACTION_SELECT_ALL_RECT.h).unwrap();
+    node.set_property_f32(Role::App, "baseline", 0.).unwrap();
+    node.set_property_f32(Role::App, "font_size", FONTSIZE).unwrap();
+    node.set_property_str(Role::App, "text", "copy   paste   select all").unwrap();
+    //node.set_property_bool(Role::App, "debug", true).unwrap();
+    //node.set_property_str(Role::App, "text", "anon1").unwrap();
+    let prop = node.get_property("text_color").unwrap();
+    prop.set_f32(Role::App, 0, 0.).unwrap();
+    prop.set_f32(Role::App, 1, 0.94).unwrap();
+    prop.set_f32(Role::App, 2, 1.).unwrap();
+    prop.set_f32(Role::App, 3, 1.).unwrap();
+    node.set_property_u32(Role::App, "z_index", 1).unwrap();
+
+    let node = node
+        .setup(|me| {
+            Text::new(
+                me,
+                window_scale.clone(),
+                app.render_api.clone(),
+                app.text_shaper.clone(),
+                app.ex.clone(),
+            )
+        })
+        .await;
+    layer_node.clone().link(node);
+
+    // Copy button
+    let node = create_button("copy_btn");
+    node.set_property_bool(Role::App, "is_active", true).unwrap();
+    let prop = node.get_property("rect").unwrap();
+    prop.set_f32(Role::App, 0, ACTION_COPY_RECT.x).unwrap();
+    prop.set_f32(Role::App, 1, ACTION_COPY_RECT.y).unwrap();
+    prop.set_f32(Role::App, 2, ACTION_COPY_RECT.w).unwrap();
+    prop.set_f32(Role::App, 3, ACTION_COPY_RECT.h).unwrap();
+
+    let (slot, recvr) = Slot::new("copy_clicked");
+    node.register("click", slot).unwrap();
+    let actions_is_visible2 = actions_is_visible.clone();
+    let listen_click = app.ex.spawn(async move {
+        while let Ok(_) = recvr.recv().await {
+            info!(target: "app::chat", "clicked copy");
+            actions_is_visible2.set(false);
+        }
+    });
+    app.tasks.lock().unwrap().push(listen_click);
+
+    let node = node.setup(|me| Button::new(me, app.ex.clone())).await;
+    layer_node.clone().link(node);
+
+    // Paste button
+    let node = create_button("paste_btn");
+    node.set_property_bool(Role::App, "is_active", true).unwrap();
+    let prop = node.get_property("rect").unwrap();
+    prop.set_f32(Role::App, 0, ACTION_PASTE_RECT.x).unwrap();
+    prop.set_f32(Role::App, 1, ACTION_PASTE_RECT.y).unwrap();
+    prop.set_f32(Role::App, 2, ACTION_PASTE_RECT.w).unwrap();
+    prop.set_f32(Role::App, 3, ACTION_PASTE_RECT.h).unwrap();
+
+    let (slot, recvr) = Slot::new("paste_clicked");
+    node.register("click", slot).unwrap();
+    let actions_is_visible2 = actions_is_visible.clone();
+    let listen_click = app.ex.spawn(async move {
+        while let Ok(_) = recvr.recv().await {
+            info!(target: "app::chat", "clicked paste");
+            actions_is_visible2.set(false);
+        }
+    });
+    app.tasks.lock().unwrap().push(listen_click);
+
+    let node = node.setup(|me| Button::new(me, app.ex.clone())).await;
+    layer_node.clone().link(node);
+
+    // Select all button
+    let node = create_button("select_all_btn");
+    node.set_property_bool(Role::App, "is_active", true).unwrap();
+    let prop = node.get_property("rect").unwrap();
+    prop.set_f32(Role::App, 0, ACTION_SELECT_ALL_RECT.x).unwrap();
+    prop.set_f32(Role::App, 1, ACTION_SELECT_ALL_RECT.y).unwrap();
+    prop.set_f32(Role::App, 2, ACTION_SELECT_ALL_RECT.w).unwrap();
+    prop.set_f32(Role::App, 3, ACTION_SELECT_ALL_RECT.h).unwrap();
+
+    let (slot, recvr) = Slot::new("select_all_clicked");
+    node.register("click", slot).unwrap();
+    let actions_is_visible2 = actions_is_visible.clone();
+    let listen_click = app.ex.spawn(async move {
+        while let Ok(_) = recvr.recv().await {
+            info!(target: "app::chat", "clicked select_all");
+            actions_is_visible2.set(false);
+        }
+    });
+    app.tasks.lock().unwrap().push(listen_click);
+
+    let node = node.setup(|me| Button::new(me, app.ex.clone())).await;
+    layer_node.clone().link(node);
+
+    let editz_select_sub = editz_select_text_prop.subscribe_modify();
+    let editz_select_task = app.ex.spawn(async move {
+        while let Ok(_) = editz_select_sub.receive().await {
+            if editz_select_text_prop.is_null(0).unwrap() {
+                info!(target: "app::chat", "selection changed: null");
+                actions_is_visible.set(false);
+            } else {
+                let select_text = editz_select_text_prop.get_str(0).unwrap();
+                info!(target: "app::chat", "selection changed: {select_text}");
+                actions_is_visible.set(true);
+            }
+        }
+    });
+    app.tasks.lock().unwrap().push(editz_select_task);
 }

+ 2 - 2
bin/darkwallet/src/gfx/linalg.rs

@@ -61,7 +61,7 @@ pub struct Point {
 }
 
 impl Point {
-    pub fn new(x: f32, y: f32) -> Self {
+    pub const fn new(x: f32, y: f32) -> Self {
         Self { x, y }
     }
 
@@ -139,7 +139,7 @@ pub struct Rectangle {
 }
 
 impl Rectangle {
-    pub fn new(x: f32, y: f32, w: f32, h: f32) -> Self {
+    pub const fn new(x: f32, y: f32, w: f32, h: f32) -> Self {
         Self { x, y, w, h }
     }
 

+ 1 - 1
bin/darkwallet/src/text/wrap.rs

@@ -34,7 +34,7 @@ impl std::fmt::Debug for Token {
 }
 
 /// Get the string represented by a vec of glyphs. Useful for debugging.
-pub fn glyph_str(glyphs: &Vec<Glyph>) -> String {
+pub fn glyph_str(glyphs: &[Glyph]) -> String {
     glyphs.iter().map(|g| g.substr.as_str()).collect::<Vec<_>>().join("")
 }
 

+ 41 - 41
bin/darkwallet/src/ui/chatedit.rs

@@ -66,6 +66,8 @@ use super::{
 // Avoid updating too much makes scrolling smoother.
 const VERT_SCROLL_UPDATE_INC: f32 = 1.;
 
+macro_rules! d { ($($arg:tt)*) => { debug!(target: "ui::chatview", $($arg)*); } }
+
 fn is_all_whitespace(glyphs: &[Glyph]) -> bool {
     for glyph in glyphs {
         if !is_whitespace(&glyph.substr) {
@@ -482,7 +484,7 @@ pub struct ChatEdit {
     select_ascent: PropertyFloat32,
     select_descent: PropertyFloat32,
     handle_descent: PropertyFloat32,
-    selected: PropertyPtr,
+    select_text: PropertyPtr,
     z_index: PropertyUint32,
     debug: PropertyBool,
 
@@ -545,7 +547,7 @@ impl ChatEdit {
             PropertyFloat32::wrap(node_ref, Role::Internal, "select_descent", 0).unwrap();
         let handle_descent =
             PropertyFloat32::wrap(node_ref, Role::Internal, "handle_descent", 0).unwrap();
-        let selected = node_ref.get_property("selected").unwrap();
+        let select_text = node_ref.get_property("select_text").unwrap();
         let cursor_blink_time =
             PropertyUint32::wrap(node_ref, Role::Internal, "cursor_blink_time", 0).unwrap();
         let cursor_idle_time =
@@ -597,7 +599,7 @@ impl ChatEdit {
             select_ascent,
             select_descent,
             handle_descent,
-            selected,
+            select_text,
             z_index,
             debug,
 
@@ -1040,6 +1042,8 @@ impl ChatEdit {
             if !text_wrap.select.is_empty() {
                 text_wrap.delete_selected();
 
+                self.update_select_text(&mut text_wrap);
+
                 self.is_phone_select.store(false, Ordering::Relaxed);
                 // Reshow cursor (if hidden)
                 self.hide_cursor.store(false, Ordering::Relaxed);
@@ -1065,6 +1069,8 @@ impl ChatEdit {
                         let select = &mut text_wrap.select;
                         select.clear();
                         select.push(Selection::new(0, end_pos));
+
+                        self.update_select_text(&mut text_wrap);
                     }
 
                     self.redraw().await;
@@ -1073,14 +1079,14 @@ impl ChatEdit {
             }
             'c' => {
                 if mods.ctrl {
-                    self.copy_highlighted().unwrap();
+                    //self.copy_highlighted().unwrap();
                     return true
                 }
             }
             'v' => {
                 if mods.ctrl {
                     if let Some(text) = window::clipboard_get() {
-                        self.paste_text(text).await;
+                        //self.paste_text(text).await;
                     }
                     return true
                 }
@@ -1152,6 +1158,7 @@ impl ChatEdit {
             text_wrap.clear_cache();
         } else {
             text_wrap.delete_selected();
+            self.update_select_text(&mut text_wrap);
         }
 
         self.is_phone_select.store(false, Ordering::Relaxed);
@@ -1181,6 +1188,8 @@ impl ChatEdit {
         } else {
             select.clear();
         }
+
+        self.update_select_text(&mut text_wrap);
     }
 
     /// This will select the entire word rather than move the cursor to that location
@@ -1199,47 +1208,31 @@ impl ChatEdit {
         let select = &mut text_wrap.select;
         select.clear();
         select.push(Selection::new(word_start, word_end));
+
         debug!(target: "ui::chatview", "Selected {select:?} from {touch_pos:?}");
+        self.update_select_text(&mut text_wrap);
 
         self.is_phone_select.store(true, Ordering::Relaxed);
         // redraw() will now hide the cursor
         self.hide_cursor.store(true, Ordering::Relaxed);
     }
 
-    fn glyphs_to_string(glyphs: &Vec<Glyph>) -> String {
-        let mut text = String::new();
-        for (i, glyph) in glyphs.iter().enumerate() {
-            text.push_str(&glyph.substr);
-        }
-        text
-    }
-
-    fn delete_highlighted(&self) {
-        assert!(!self.selected.is_null(0).unwrap());
-        assert!(!self.selected.is_null(1).unwrap());
-
-        let start = self.selected.get_u32(0).unwrap() as usize;
-        let end = self.selected.get_u32(1).unwrap() as usize;
-
-        let sel_start = std::cmp::min(start, end);
-        let sel_end = std::cmp::max(start, end);
-
-        let mut glyphs = self.glyphs.lock().clone();
-        glyphs.drain(sel_start..sel_end);
-
-        let text = Self::glyphs_to_string(&glyphs);
-        debug!(
-            target: "ui::chatedit",
-            "delete_highlighted() text=\"{}\", cursor_pos={}",
-            text, sel_start
-        );
-        self.text.set(text);
-
-        self.selected.set_null(Role::Internal, 0).unwrap();
-        self.selected.set_null(Role::Internal, 1).unwrap();
-        self.cursor_pos.set(sel_start as u32);
+    // Call this whenever the selection changes
+    fn update_select_text(&self, text_wrap: &mut TextWrap) {
+        let select = &text_wrap.select;
+        let Some(select) = select.first().cloned() else {
+            if !self.select_text.is_null(0).unwrap() {
+                self.select_text.set_null(Role::Internal, 0).unwrap();
+            }
+            return
+        };
+        let rendered = text_wrap.get_render();
+        let glyphs = &rendered.glyphs[select.start..select.end];
+        let text = text::glyph_str(glyphs);
+        self.select_text.set_str(Role::Internal, 0, text).unwrap();
     }
 
+    /*
     fn copy_highlighted(&self) -> Result<()> {
         let start = self.selected.get_u32(0)? as usize;
         let end = self.selected.get_u32(1)? as usize;
@@ -1285,6 +1278,7 @@ impl ChatEdit {
         self.apply_cursor_scrolling();
         self.redraw().await;
     }
+    */
 
     async fn handle_touch_start(&self, mut touch_pos: Point) -> bool {
         //debug!(target: "ui::chatedit", "handle_touch_start({touch_pos:?})");
@@ -1420,6 +1414,8 @@ impl ChatEdit {
                         }
                         select.end = pos;
                     }
+
+                    self.update_select_text(&mut text_wrap);
                 }
                 self.redraw().await;
             }
@@ -1473,6 +1469,7 @@ impl ChatEdit {
 
             let select = &mut text_wrap.select;
             select.clear();
+            self.update_select_text(&mut text_wrap);
         }
 
         self.is_phone_select.store(false, Ordering::Relaxed);
@@ -1716,8 +1713,7 @@ impl UIObject for ChatEdit {
         // Cursor and selection might be invalidated.
         async fn reset(self_: Arc<ChatEdit>) {
             self_.cursor_pos.set(0);
-            self_.selected.set_null(Role::Internal, 0).unwrap();
-            self_.selected.set_null(Role::Internal, 1).unwrap();
+            //self_.select_text.set_null(Role::Internal, 0).unwrap();
             self_.scroll.set(0.);
             self_.redraw();
         }
@@ -1880,7 +1876,7 @@ impl UIObject for ChatEdit {
             // begin selection
             let select = &mut text_wrap.select;
             select.clear();
-            select.push(Selection::new(cursor_pos, cursor_pos));
+            self.update_select_text(&mut text_wrap);
 
             self.mouse_btn_held.store(true, Ordering::Relaxed);
         }
@@ -1928,7 +1924,11 @@ impl UIObject for ChatEdit {
 
             // modify current selection
             let select = &mut text_wrap.select;
-            select.last_mut().unwrap().end = cursor_pos;
+            if select.is_empty() {
+                select.push(Selection::new(cursor_pos, cursor_pos));
+            }
+            select.first_mut().unwrap().end = cursor_pos;
+            self.update_select_text(&mut text_wrap);
         }
 
         self.pause_blinking();

+ 16 - 4
bin/darkwallet/src/ui/vector_art/shape.rs

@@ -92,12 +92,24 @@ impl VectorShape {
         x2: SExprCode,
         y2: SExprCode,
         color: Color,
+    ) {
+        self.add_gradient_box(x1, y1, x2, y2, [color.clone(), color.clone(), color.clone(), color])
+    }
+
+    /// Colors go clockwise from top-left
+    pub fn add_gradient_box(
+        &mut self,
+        x1: SExprCode,
+        y1: SExprCode,
+        x2: SExprCode,
+        y2: SExprCode,
+        color: [Color; 4],
     ) {
         let mut verts = vec![
-            ShapeVertex::new(x1.clone(), y1.clone(), color.clone()),
-            ShapeVertex::new(x2.clone(), y1.clone(), color.clone()),
-            ShapeVertex::new(x1.clone(), y2.clone(), color.clone()),
-            ShapeVertex::new(x2, y2, color),
+            ShapeVertex::new(x1.clone(), y1.clone(), color[0]),
+            ShapeVertex::new(x2.clone(), y1.clone(), color[1]),
+            ShapeVertex::new(x1.clone(), y2.clone(), color[3]),
+            ShapeVertex::new(x2, y2, color[2]),
         ];
         let i = self.verts.len() as u16;
         let mut indices = vec![i + 0, i + 2, i + 1, i + 1, i + 2, i + 3];