Răsfoiți Sursa

wallet/chatedit: cleanup cursor and text drawing code

darkfi 1 an în urmă
părinte
comite
d22af6bc23

+ 1 - 1
bin/darkwallet/Makefile

@@ -15,7 +15,7 @@ SRC = \
 
 all: $(SRC) fonts
 	cargo lbuild $(FEATURES)
-	mv target/debug/darkwallet .
+	-mv target/debug/darkwallet .
 	./darkwallet
 
 darkwallet.apk: $(SRC) fonts

+ 3 - 0
bin/darkwallet/src/app/node.rs

@@ -268,6 +268,9 @@ pub fn create_chatedit(name: &str) -> SceneNode {
     let prop = Property::new("baseline", PropertyType::Float32, PropertySubType::Pixel);
     node.add_property(prop).unwrap();
 
+    let prop = Property::new("linespacing", PropertyType::Float32, PropertySubType::Pixel);
+    node.add_property(prop).unwrap();
+
     let prop = Property::new("descent", PropertyType::Float32, PropertySubType::Pixel);
     node.add_property(prop).unwrap();
 

+ 1 - 0
bin/darkwallet/src/app/schema.rs

@@ -826,6 +826,7 @@ pub(super) async fn make(app: &App, window: SceneNodePtr) {
     editbox_bg_rect_prop.add_depend(&prop, 3, "editz_h");
 
     node.set_property_f32(Role::App, "baseline", TEXTBAR_BASELINE).unwrap();
+    node.set_property_f32(Role::App, "linespacing", TEXTBAR_BASELINE).unwrap();
     node.set_property_f32(Role::App, "descent", TEXT_DESCENT).unwrap();
     node.set_property_f32(Role::App, "font_size", FONTSIZE).unwrap();
     //node.set_property_str(Role::App, "text", "hello king!😁🍆jelly 🍆1234").unwrap();

+ 9 - 0
bin/darkwallet/src/text/mod.rs

@@ -476,6 +476,15 @@ pub struct Glyph {
     pub y_advance: f32,
 }
 
+impl std::fmt::Debug for Glyph {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("Glyph")
+            .field("glyph_id", &self.glyph_id)
+            .field("substr", &self.substr)
+            .finish()
+    }
+}
+
 type FreetypeFace = ft::Face<&'static [u8]>;
 
 struct FtFaces(Vec<FreetypeFace>);

+ 24 - 8
bin/darkwallet/src/text/wrap.rs

@@ -22,6 +22,17 @@ impl Token {
     }
 }
 
+impl std::fmt::Debug for Token {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self.token_type {
+            TokenType::Null => write!(f, "Token::Null")?,
+            TokenType::Word => write!(f, "Token::Word")?,
+            TokenType::Whitespace => write!(f, "Token::Whitespace")?,
+        }
+        write!(f, "({})", self.as_str())
+    }
+}
+
 /// Get the string represented by a vec of glyphs. Useful for debugging.
 pub fn glyph_str(glyphs: &Vec<Glyph>) -> String {
     glyphs.iter().map(|g| g.substr.as_str()).collect::<Vec<_>>().join("")
@@ -87,8 +98,10 @@ fn tokenize(font_size: f32, window_scale: f32, glyphs: &Vec<Glyph>) -> Vec<Token
 }
 
 /// Given a series of words, apply wrapping.
-/// Whitespace is perserved unless the word wraps.
+/// Whitespace is completely perserved.
 fn apply_wrap(line_width: f32, tokens: Vec<Token>) -> Vec<Vec<Glyph>> {
+    //debug!(target: "text::wrap", "apply_wrap({line_width}, {tokens:?})");
+
     let mut lines = vec![];
     let mut line = vec![];
     let mut start = 0.;
@@ -98,26 +111,29 @@ fn apply_wrap(line_width: f32, tokens: Vec<Token>) -> Vec<Vec<Glyph>> {
 
         // Triggered by if below
         if start < 0. {
-            assert_eq!(token.token_type, TokenType::Word);
             start = token.lhs;
         }
 
         // Does this token cross over the end of the line?
         if token.rhs > start + line_width {
+            // Whitespace tokens that cause wrapping are prepended to the current line before
+            // making the line break.
+            if token.token_type == TokenType::Whitespace {
+                line.append(&mut token.glyphs);
+            }
+
             // Start a new line
             let line = std::mem::take(&mut line);
             //debug!(target: "text::apply_wrap", "adding line: {}", glyph_str(&line));
             lines.push(line);
 
-            // Whitespace tokens that cause wrapping are just discarded.
+            // Move to the next token if this is whitespace
             if token.token_type == TokenType::Whitespace {
                 // Load LHS from next token in loop
                 start = -1.;
-                continue
+            } else {
+                start = token.lhs;
             }
-
-            assert_eq!(token.token_type, TokenType::Word);
-            start = token.lhs;
         }
 
         line.append(&mut token.glyphs);
@@ -126,7 +142,7 @@ fn apply_wrap(line_width: f32, tokens: Vec<Token>) -> Vec<Vec<Glyph>> {
     // Handle the remainders
     if !line.is_empty() {
         let line = std::mem::take(&mut line);
-        //debug!(target: "text::apply_wrap", "adding line: {}", glyph_str(&line));
+        //debug!(target: "text::apply_wrap", "adding rem line: {}", glyph_str(&line));
         lines.push(line);
     }
 

+ 198 - 95
bin/darkwallet/src/ui/chatedit.rs

@@ -51,7 +51,7 @@ use crate::{
 
 use super::{
     editbox::{
-        editable::{Editable, Selection, TextIdx, TextPos},
+        editable::{Editable, RenderedEditable, Selection, TextIdx, TextPos},
         eol_nudge,
         repeat::{PressedKey, PressedKeysSmoothRepeat},
         ALLOWED_KEYCODES, DISALLOWED_CHARS,
@@ -59,6 +59,102 @@ use super::{
     DrawUpdate, OnModify, UIObject,
 };
 
+fn is_all_whitespace(glyphs: &[Glyph]) -> bool {
+    for glyph in glyphs {
+        if !is_whitespace(&glyph.substr) {
+            return false
+        }
+    }
+    true
+}
+
+struct TextWrap {
+    editable: Editable,
+    select: Vec<Selection>,
+    rendered: Option<RenderedEditable>,
+
+    font_size: PropertyFloat32,
+    window_scale: PropertyFloat32,
+    baseline: PropertyFloat32,
+}
+
+impl TextWrap {
+    fn new(
+        text_shaper: TextShaperPtr,
+        font_size: PropertyFloat32,
+        window_scale: PropertyFloat32,
+        baseline: PropertyFloat32,
+    ) -> Self {
+        Self {
+            editable: Editable::new(
+                text_shaper,
+                font_size.clone(),
+                window_scale.clone(),
+                baseline.clone(),
+            ),
+            select: vec![],
+            rendered: None,
+            font_size,
+            window_scale,
+            baseline,
+        }
+    }
+
+    fn clear_cache(&mut self) {
+        self.rendered = None;
+    }
+    fn get_render(&mut self) -> &RenderedEditable {
+        if self.rendered.is_none() {
+            self.rendered = Some(self.editable.render());
+        }
+        self.rendered.as_ref().unwrap()
+    }
+
+    fn wrap(&mut self, width: f32) -> Vec<WrappedLine> {
+        let font_size = self.font_size.get();
+        let window_scale = self.window_scale.get();
+        let baseline = self.baseline.get();
+
+        let rendered = self.get_render();
+        let glyphs = text::wrap(width, font_size, window_scale, &rendered.glyphs);
+        glyphs.into_iter().map(|g| WrappedLine::new(g, font_size, window_scale, baseline)).collect()
+    }
+
+    fn cursor_pos(&mut self) -> usize {
+        let rendered = self.get_render().clone();
+        self.editable.get_cursor_pos(&rendered)
+    }
+}
+
+struct WrappedLine {
+    glyphs: Vec<Glyph>,
+
+    font_size: f32,
+    window_scale: f32,
+    baseline: f32,
+}
+
+impl WrappedLine {
+    fn new(glyphs: Vec<Glyph>, font_size: f32, window_scale: f32, baseline: f32) -> Self {
+        Self { glyphs, font_size, window_scale, baseline }
+    }
+
+    fn len(&self) -> usize {
+        self.glyphs.len()
+    }
+
+    fn pos_iter(&self) -> GlyphPositionIter {
+        GlyphPositionIter::new(self.font_size, self.window_scale, &self.glyphs, self.baseline)
+    }
+
+    fn rhs(&self) -> f32 {
+        match self.pos_iter().last() {
+            None => 0.,
+            Some(rect) => rect.rhs(),
+        }
+    }
+}
+
 #[derive(Clone)]
 struct TouchStartInfo {
     pos: Point,
@@ -147,6 +243,7 @@ pub struct ChatEdit {
     max_height: PropertyFloat32,
     rect: PropertyRect,
     baseline: PropertyFloat32,
+    linespacing: PropertyFloat32,
     descent: PropertyFloat32,
     scroll: PropertyFloat32,
     scroll_speed: PropertyFloat32,
@@ -168,8 +265,8 @@ pub struct ChatEdit {
     z_index: PropertyUint32,
     debug: PropertyBool,
 
-    editable: SyncMutex<Editable>,
     select: SyncMutex<Vec<Selection>>,
+    text_wrap: SyncMutex<TextWrap>,
 
     mouse_btn_held: AtomicBool,
     cursor_is_visible: AtomicBool,
@@ -202,6 +299,8 @@ impl ChatEdit {
         let max_height = PropertyFloat32::wrap(node_ref, Role::Internal, "max_height", 0).unwrap();
         let rect = PropertyRect::wrap(node_ref, Role::Internal, "rect").unwrap();
         let baseline = PropertyFloat32::wrap(node_ref, Role::Internal, "baseline", 0).unwrap();
+        let linespacing =
+            PropertyFloat32::wrap(node_ref, Role::Internal, "linespacing", 0).unwrap();
         let descent = PropertyFloat32::wrap(node_ref, Role::Internal, "descent", 0).unwrap();
         let scroll = PropertyFloat32::wrap(node_ref, Role::Internal, "scroll", 0).unwrap();
         let scroll_speed =
@@ -254,6 +353,7 @@ impl ChatEdit {
             max_height,
             rect,
             baseline: baseline.clone(),
+            linespacing,
             descent,
             scroll: scroll.clone(),
             scroll_speed,
@@ -275,13 +375,13 @@ impl ChatEdit {
             z_index,
             debug,
 
-            editable: SyncMutex::new(Editable::new(
+            select: SyncMutex::new(vec![]),
+            text_wrap: SyncMutex::new(TextWrap::new(
                 text_shaper,
                 font_size,
                 window_scale.clone(),
                 baseline,
             )),
-            select: SyncMutex::new(vec![]),
 
             mouse_btn_held: AtomicBool::new(false),
             cursor_is_visible: AtomicBool::new(true),
@@ -312,6 +412,7 @@ impl ChatEdit {
         let window_scale = self.window_scale.get();
         let text_color = self.text_color.get();
         let text_hi_color = self.text_hi_color.get();
+        let linespacing = self.linespacing.get();
         let baseline = self.baseline.get();
         let scroll = self.scroll.get();
         let cursor_pos = self.cursor_pos.get() as usize;
@@ -320,16 +421,19 @@ impl ChatEdit {
         //debug!(target: "ui::chatedit", "Rendering text '{text}' clip={clip:?}");
         //debug!(target: "ui::chatedit", "    cursor_pos={cursor_pos}, is_focused={is_focused}");
 
-        let rendered = self.editable.lock().unwrap().render();
-        let atlas = text::make_texture_atlas(&self.render_api, &rendered.glyphs);
-
         let width = self.rect.prop().get_f32(2).unwrap();
-        let wrapped_glyphs = text::wrap(width, font_size, window_scale, &rendered.glyphs);
-        let wrapped_lines_len = std::cmp::max(wrapped_glyphs.len(), 1);
 
-        let total_height = wrapped_lines_len as f32 * baseline;
-        //let height = std::cmp::min(total_height, self.max_height.get());
-        let height = total_height + self.descent.get();
+        let (atlas, wrapped_lines) = {
+            let mut text_wrap = self.text_wrap.lock().unwrap();
+            let rendered_glyphs = &text_wrap.get_render().glyphs;
+            let atlas = text::make_texture_atlas(&self.render_api, rendered_glyphs);
+            let wrapped_lines = text_wrap.wrap(width);
+            (atlas, wrapped_lines)
+        };
+
+        let total_height = std::cmp::max(wrapped_lines.len(), 1) as f32 * linespacing;
+        let mut height = total_height + self.descent.get();
+        height = height.clamp(0., self.max_height.get());
 
         self.rect.prop().set_f32(Role::Internal, 3, height);
 
@@ -349,8 +453,10 @@ impl ChatEdit {
         clip.y = 0.;
 
         let mut mesh = MeshBuilder::with_clip(clip.clone());
+        let mut curr_y = 0.;
 
-        for (line_idx, glyphs) in wrapped_glyphs.iter().enumerate() {
+        for wrap_line in wrapped_lines {
+            /*
             let selections = self.select.lock().unwrap().clone();
             // Just an assert
             if self.is_phone_select.load(Ordering::Relaxed) {
@@ -360,6 +466,7 @@ impl ChatEdit {
                 //self.draw_selected(&mut mesh, select, &rendered.glyphs, clip.h);
             }
             let select_marks = self.mark_selected_glyphs(&glyphs, selections);
+            */
 
             /*
             if rendered.has_underline() {
@@ -373,26 +480,25 @@ impl ChatEdit {
             }
             */
 
-            let glyph_pos_iter = GlyphPositionIter::new(font_size, window_scale, &glyphs, baseline);
+            let pos_iter = wrap_line.pos_iter();
 
-            for (glyph_idx, mut glyph_rect, glyph, is_selected) in
-                zip3(glyph_pos_iter, glyphs.iter(), select_marks.iter())
-            {
+            for (mut glyph_rect, glyph) in pos_iter.zip(wrap_line.glyphs.iter()) {
                 let uv_rect = atlas.fetch_uv(glyph.glyph_id).expect("missing glyph UV rect");
 
-                glyph_rect.x -= scroll;
-                glyph_rect.y += line_idx as f32 * baseline;
+                glyph_rect.y += curr_y - scroll;
 
                 //mesh.draw_outline(&glyph_rect, COLOR_BLUE, 2.);
                 let mut color = text_color.clone();
-                if *is_selected {
-                    color = text_hi_color.clone();
-                }
+                //if *is_selected {
+                //    color = text_hi_color.clone();
+                //}
                 if glyph.sprite.has_color {
                     color = COLOR_WHITE;
                 }
                 mesh.draw_box(&glyph_rect, color, uv_rect);
             }
+
+            curr_y += linespacing;
         }
 
         mesh.alloc(&self.render_api).draw_with_texture(atlas.texture)
@@ -422,55 +528,54 @@ impl ChatEdit {
 
         let font_size = self.font_size.get();
         let window_scale = self.window_scale.get();
-        let baseline = self.baseline.get();
+        let linespacing = self.linespacing.get();
 
         let width = self.rect.prop().get_f32(2).unwrap();
 
-        let (cursor_pos, wrap_glyphs) = {
-            let editable = self.editable.lock().unwrap();
-            let rendered = editable.render();
-            let cursor_pos = editable.get_cursor_pos(&rendered);
-            let glyphs = text::wrap(width, font_size, window_scale, &rendered.glyphs);
-            (cursor_pos, glyphs)
+        let (mut cursor_pos, wrapped_lines) = {
+            let mut text_wrap = self.text_wrap.lock().unwrap();
+            (text_wrap.cursor_pos(), text_wrap.wrap(width))
         };
 
-        // Convert cursor glyph pos to a coord (x, y)
-        let mut glyph_idx = 0;
-        // Used for drawing the cursor when it's at the end of the line.
-        let mut rhs = 0.;
-        let mut y_pos = 0.;
-
         if cursor_pos == 0 {
             return Point::zero();
         }
 
-        for (line_idx, glyphs) in wrap_glyphs.iter().enumerate() {
-            debug!("glyph_idx = {glyph_idx}, y_pos = {y_pos}, rhs = {rhs}");
-            let glyph_pos_iter = GlyphPositionIter::new(font_size, window_scale, glyphs, baseline);
-
-            if line_idx > 0 {
-                // +1 for the EOL whitespace
-                glyph_idx += 1;
-                y_pos += baseline;
-            }
-
-            for (mut glyph_rect, glyph) in glyph_pos_iter.zip(glyphs.iter()) {
-                if cursor_pos == glyph_idx {
-                    let rhs = glyph_rect.rhs();
-                    debug!("retvrn {rhs}, {y_pos}");
-                    return Point::new(rhs, y_pos)
+        let mut y = 0.;
+        for wrap_line in &wrapped_lines {
+            assert!(!wrap_line.glyphs.is_empty());
+
+            if cursor_pos < wrap_line.len() {
+                // Cursor is on this line
+                let mut pos_iter = wrap_line.pos_iter();
+                pos_iter.advance_by(cursor_pos).unwrap();
+                let glyph_rect = pos_iter.next().unwrap();
+                let mut x = glyph_rect.x;
+                if x > width {
+                    x = width;
                 }
+                return Point::new(x, y)
+            }
 
-                rhs = glyph_rect.rhs();
+            cursor_pos -= wrap_line.len();
+            y += linespacing;
+        }
 
-                glyph_idx += 1;
+        let mut rhs = 0.;
+        let mut last_y = 0.;
+
+        if !wrapped_lines.is_empty() {
+            let last_line = wrapped_lines.last().unwrap();
+            rhs = last_line.rhs();
+            rhs += eol_nudge(font_size, &last_line.glyphs);
+            if rhs > width {
+                rhs = width;
             }
-        }
 
-        if !wrap_glyphs.is_empty() {
-            rhs += eol_nudge(font_size, &wrap_glyphs.last().unwrap());
+            last_y = (wrapped_lines.len() - 1) as f32 * linespacing;
         }
-        Point::new(rhs, y_pos)
+
+        Point::new(rhs, last_y)
     }
 
     fn mark_selected_glyphs(&self, glyphs: &Vec<Glyph>, selections: Vec<Selection>) -> Vec<bool> {
@@ -655,10 +760,11 @@ impl ChatEdit {
 
     async fn insert_char(&self, key: char) {
         {
-            let mut editable = self.editable.lock().unwrap();
+            let mut text_wrap = &mut self.text_wrap.lock().unwrap();
+            text_wrap.clear_cache();
             let mut tmp = [0; 4];
             let key_str = key.encode_utf8(&mut tmp);
-            editable.compose(key_str, true);
+            text_wrap.editable.compose(key_str, true);
         }
 
         self.pause_blinking();
@@ -744,18 +850,19 @@ impl ChatEdit {
     }
 
     fn delete(&self, before: usize, after: usize) {
-        let mut editable = self.editable.lock().unwrap();
+        let mut text_wrap = &mut self.text_wrap.lock().unwrap();
+        text_wrap.clear_cache();
         let selection = std::mem::take(&mut *self.select.lock().unwrap());
 
         if selection.is_empty() {
-            editable.delete(before, after);
+            text_wrap.editable.delete(before, after);
             return
         }
 
         let sel = selection.first().unwrap();
         let cursor_pos = std::cmp::min(sel.start, sel.end);
 
-        let render = editable.render();
+        let render = text_wrap.get_render();
         let mut before_text = String::new();
         let mut after_text = String::new();
         'next: for (pos, glyph) in render.glyphs.iter().enumerate() {
@@ -774,7 +881,7 @@ impl ChatEdit {
             }
         }
 
-        editable.set_text(before_text, after_text);
+        text_wrap.editable.set_text(before_text, after_text);
 
         self.is_phone_select.store(false, Ordering::Relaxed);
         // Reshow cursor (if hidden)
@@ -782,12 +889,12 @@ impl ChatEdit {
     }
 
     fn adjust_cursor(&self, has_shift: bool, move_cursor: impl Fn(&mut Editable)) {
-        let mut editable = self.editable.lock().unwrap();
-        let rendered = editable.render();
-        let prev_cursor_pos = editable.get_cursor_pos(&rendered);
-        move_cursor(&mut editable);
-        let cursor_pos = editable.get_cursor_pos(&rendered);
-        drop(editable);
+        let mut text_wrap = &mut self.text_wrap.lock().unwrap();
+        let rendered = text_wrap.get_render().clone();
+        let prev_cursor_pos = text_wrap.editable.get_cursor_pos(&rendered);
+        move_cursor(&mut text_wrap.editable);
+        let cursor_pos = text_wrap.editable.get_cursor_pos(&rendered);
+        drop(text_wrap);
 
         let mut select = self.select.lock().unwrap();
 
@@ -814,10 +921,11 @@ impl ChatEdit {
         let baseline = self.baseline.get();
 
         {
-            let mut editable = self.editable.lock().unwrap();
-            editable.end_compose();
+            let mut text_wrap = &mut self.text_wrap.lock().unwrap();
+            text_wrap.clear_cache();
+            text_wrap.editable.end_compose();
 
-            let rendered = editable.render();
+            let rendered = text_wrap.get_render();
             let x = x - rect.x + self.scroll.get();
 
             let cpos = rendered.x_to_pos(x, font_size, window_scale, baseline);
@@ -956,10 +1064,7 @@ impl ChatEdit {
         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 rendered = self.text_wrap.lock().unwrap().get_render().clone();
 
             let font_size = self.font_size.get();
             let window_scale = self.window_scale.get();
@@ -1027,10 +1132,7 @@ impl ChatEdit {
                     assert_eq!(selections.len(), 1);
                     let select = selections.first_mut().unwrap();
 
-                    let rendered = {
-                        let editable = self.editable.lock().unwrap();
-                        editable.render()
-                    };
+                    let rendered = self.text_wrap.lock().unwrap().get_render().clone();
 
                     let font_size = self.font_size.get();
                     let window_scale = self.window_scale.get();
@@ -1078,14 +1180,14 @@ impl ChatEdit {
                 let baseline = self.baseline.get();
 
                 {
-                    let mut editable = self.editable.lock().unwrap();
-                    let rendered = editable.render();
+                    let mut text_wrap = &mut self.text_wrap.lock().unwrap();
+                    let rendered = text_wrap.get_render();
                     // Adjust with scroll here too
                     let x = pos.x - rect.x;
 
                     let cpos = rendered.x_to_pos(x, font_size, window_scale, baseline);
                     let cidx = rendered.pos_to_idx(cpos);
-                    editable.set_cursor_idx(cidx);
+                    text_wrap.editable.set_cursor_idx(cidx);
 
                     // begin selection
                     let mut select = self.select.lock().unwrap();
@@ -1160,8 +1262,7 @@ impl ChatEdit {
         let window_scale = self.window_scale.get();
         let baseline = self.baseline.get();
         let rhs = {
-            let mut editable = self.editable.lock().unwrap();
-            let rendered = editable.render();
+            let rendered = self.text_wrap.lock().unwrap().get_render().clone();
 
             if rendered.glyphs.is_empty() {
                 return 0.
@@ -1458,7 +1559,7 @@ impl UIObject for ChatEdit {
         let mut x_off = 0.;
 
         let width = self.rect.prop().get_f32(2).unwrap();
-        let rendered = self.editable.lock().unwrap().render();
+        let rendered = self.text_wrap.lock().unwrap().editable.render();
         let wrapped_glyphs = text::wrap(width, font_size, window_scale, &rendered.glyphs);
         for glyphs in wrapped_glyphs {
             let glyph_pos_iter = GlyphPositionIter::new(font_size, window_scale, &glyphs, baseline);
@@ -1467,13 +1568,13 @@ impl UIObject for ChatEdit {
         }
 
         {
-            let mut editable = self.editable.lock().unwrap();
-            let rendered = editable.render();
+            let mut text_wrap = self.text_wrap.lock().unwrap();
+            let rendered = text_wrap.get_render();
             let x = x_off + mouse_pos.x - rect.x + self.scroll.get();
 
             let cpos = rendered.x_to_pos(x, font_size, window_scale, baseline);
             let cidx = rendered.pos_to_idx(cpos);
-            editable.set_cursor_idx(cidx);
+            text_wrap.editable.set_cursor_idx(cidx);
 
             // begin selection
             let mut select = self.select.lock().unwrap();
@@ -1520,13 +1621,13 @@ impl UIObject for ChatEdit {
         let baseline = self.baseline.get();
 
         {
-            let mut editable = self.editable.lock().unwrap();
-            let rendered = editable.render();
+            let mut text_wrap = self.text_wrap.lock().unwrap();
+            let rendered = text_wrap.get_render();
             let x = mouse_pos.x - rect.x + self.scroll.get();
 
             let cpos = rendered.x_to_pos(x, font_size, window_scale, baseline);
             let cidx = rendered.pos_to_idx(cpos);
-            editable.set_cursor_idx(cidx);
+            text_wrap.editable.set_cursor_idx(cidx);
 
             // begin selection
             let mut select = self.select.lock().unwrap();
@@ -1580,8 +1681,9 @@ impl UIObject for ChatEdit {
         }
 
         {
-            let mut editable = self.editable.lock().unwrap();
-            editable.compose(suggest_text, is_commit);
+            let mut text_wrap = self.text_wrap.lock().unwrap();
+            text_wrap.clear_cache();
+            text_wrap.editable.compose(suggest_text, is_commit);
         }
 
         //self.apply_cursor_scrolling();
@@ -1598,8 +1700,9 @@ impl UIObject for ChatEdit {
         }
 
         {
-            let mut editable = self.editable.lock().unwrap();
-            editable.set_compose_region(start, end);
+            let mut text_wrap = self.text_wrap.lock().unwrap();
+            text_wrap.clear_cache();
+            text_wrap.editable.set_compose_region(start, end);
         }
 
         self.redraw().await;

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

@@ -88,6 +88,7 @@ impl ComposingText {
     }
 }
 
+#[derive(Clone)]
 pub struct RenderedEditable {
     pub glyphs: Vec<Glyph>,
     pub under_start: TextPos,