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

wallet: editing text, support backspace/delete

darkfi 2 лет назад
Родитель
Сommit
ed85b9a955
4 измененных файлов с 133 добавлено и 32 удалено
  1. 2 2
      bin/darkwallet/gui/__init__.py
  2. 95 12
      bin/darkwallet/src/editbox.rs
  3. 9 7
      bin/darkwallet/src/gfx.rs
  4. 27 11
      bin/darkwallet/src/text.rs

+ 2 - 2
bin/darkwallet/gui/__init__.py

@@ -459,7 +459,7 @@ def draw():
         False, False, 1, None, None, []
     )
     api.add_property(node_id, prop)
-    api.set_property_str(node_id, "text", 0, "hello!😁jelly 🍆1234")
+    api.set_property_str(node_id, "text", 0, "hello!😁🍆jelly 🍆1234")
 
     prop = Property(
         "color", PropertyType.FLOAT32, PropertySubType.COLOR,
@@ -560,7 +560,7 @@ def draw():
         False, False, 1, None, None, []
     )
     api.add_property(node_id, prop)
-    api.set_property_str(node_id, "text", 0, "hello!😁jelly 🍆1234")
+    api.set_property_str(node_id, "text", 0, "hello king!😁🍆jelly 🍆1234")
 
     prop = Property(
         "color", PropertyType.FLOAT32, PropertySubType.COLOR,

+ 95 - 12
bin/darkwallet/src/editbox.rs

@@ -1,24 +1,48 @@
 use miniquad::{KeyMods, UniformType};
-use std::{io::Cursor, sync::Arc};
+use std::{io::Cursor, sync::{Arc, Mutex}};
 use darkfi_serial::Decodable;
 use freetype as ft;
 
-use crate::{error::{Error, Result}, prop::Property, scene::{SceneGraph, SceneNodeId, Pimpl, Slot}, gfx::{Rectangle, RenderContext, COLOR_WHITE, COLOR_BLUE, COLOR_RED, COLOR_GREEN}, text::TextShaper};
+use crate::{error::{Error, Result}, prop::Property, scene::{SceneGraph, SceneNodeId, Pimpl, Slot}, gfx::{Rectangle, RenderContext, COLOR_WHITE, COLOR_BLUE, COLOR_RED, COLOR_GREEN, FreetypeFace}, text::{Glyph, TextShaper}};
 
 pub type EditBoxPtr = Arc<EditBox>;
 
 pub struct EditBox {
     scroll: Arc<Property>,
+    cursor_pos: Arc<Property>,
+    text: Arc<Property>,
+    font_size: Arc<Property>,
+    color: Arc<Property>,
+    glyphs: Mutex<Vec<Glyph>>,
+    text_shaper: TextShaper,
 }
 
 impl EditBox {
-    pub fn new(scene_graph: &mut SceneGraph, node_id: SceneNodeId) -> Result<Pimpl> {
+    pub fn new(scene_graph: &mut SceneGraph, node_id: SceneNodeId, font_faces: Vec<FreetypeFace>) -> Result<Pimpl> {
         let node = scene_graph.get_node(node_id).unwrap();
         let scroll = node.get_property("scroll").ok_or(Error::PropertyNotFound)?;
+        let cursor_pos = node.get_property("cursor_pos").ok_or(Error::PropertyNotFound)?;
+        let text = node.get_property("text").ok_or(Error::PropertyNotFound)?;
+        let font_size = node.get_property("font_size").ok_or(Error::PropertyNotFound)?;
+        let color = node.get_property("color").ok_or(Error::PropertyNotFound)?;
+
+        let text_shaper = TextShaper {
+            font_faces
+        };
+
+        let glyphs = text_shaper.shape(text.get_str(0)?, font_size.get_f32(0)?, 
+                [color.get_f32(0)?, color.get_f32(1)?,
+                 color.get_f32(2)?, color.get_f32(3)?]);
 
         println!("EditBox::new()");
         let self_ = Arc::new(Self{
-            scroll
+            scroll,
+            cursor_pos,
+            text,
+            font_size,
+            color,
+            glyphs: Mutex::new(glyphs),
+            text_shaper,
         });
         let weak_self = Arc::downgrade(&self_);
 
@@ -91,13 +115,13 @@ impl EditBox {
         render.ctx.apply_uniforms_from_bytes(uniforms_data.as_ptr(), uniforms_data.len());
 
         let shaper = TextShaper {
-            font_faces: render.font_faces
+            font_faces: render.font_faces.clone()
         };
 
         let mut glyph_idx = 0;
         let mut rhs = 0.;
 
-        for glyph in shaper.shape(text, font_size, text_color) {
+        for glyph in &*self.glyphs.lock().unwrap() {
             let texture = render.ctx.new_texture_from_rgba8(glyph.bmp_width, glyph.bmp_height, &glyph.bmp);
 
             let x1 = glyph.pos.x + scroll;
@@ -140,17 +164,76 @@ impl EditBox {
         Ok(())
     }
 
+    fn regen_glyphs(&self) -> Result<()> {
+        let glyphs = self.text_shaper.shape(self.text.get_str(0)?, self.font_size.get_f32(0)?, 
+                [self.color.get_f32(0)?, self.color.get_f32(1)?,
+                 self.color.get_f32(2)?, self.color.get_f32(3)?]);
+        *self.glyphs.lock().unwrap() = glyphs;
+        Ok(())
+    }
+
     fn key_press(self: Arc<Self>, key: String, mods: KeyMods, repeat: bool) {
         if repeat {
             return;
         }
-        if key == "PageUp" {
-            println!("pageup!");
-        }
-        else if key == "PageDown" {
-            println!("pagedown!");
+        match key.as_str() {
+            "PageUp" => {
+                println!("pageup!");
+            }
+            "PageDown" => {
+                println!("pagedown!");
+            }
+            "Left" => {
+                let cursor_pos = self.cursor_pos.get_u32(0).unwrap();
+                if cursor_pos > 0 {
+                    self.cursor_pos.set_u32(0, cursor_pos - 1).unwrap();
+                }
+            }
+            "Right" => {
+                let cursor_pos = self.cursor_pos.get_u32(0).unwrap();
+                let glyphs_len = self.glyphs.lock().unwrap().len() as u32;
+                if cursor_pos < glyphs_len {
+                    self.cursor_pos.set_u32(0, cursor_pos + 1).unwrap();
+                }
+            }
+            "Delete" => {
+                let cursor_pos = self.cursor_pos.get_u32(0).unwrap();
+                if cursor_pos == 0 {
+                    return;
+                }
+                let mut text = String::new();
+                for (i, glyph) in self.glyphs.lock().unwrap().iter().enumerate() {
+                    let mut substr = glyph.substr.clone();
+                    if cursor_pos as usize == i {
+                        // Lmk if anyone knows a better way to do substr.pop_front()
+                        let mut chars = substr.chars();
+                        chars.next();
+                        substr = chars.as_str().to_string();
+                    }
+                    text.push_str(&substr);
+                }
+                self.text.set_str(0, text).unwrap();
+                self.regen_glyphs().unwrap();
+            }
+            "Backspace" => {
+                let cursor_pos = self.cursor_pos.get_u32(0).unwrap();
+                if cursor_pos == 0 {
+                    return;
+                }
+                let mut text = String::new();
+                for (i, glyph) in self.glyphs.lock().unwrap().iter().enumerate() {
+                    let mut substr = glyph.substr.clone();
+                    if cursor_pos as usize - 1 == i {
+                        substr.pop().unwrap();
+                    }
+                    text.push_str(&substr);
+                }
+                self.cursor_pos.set_u32(0, cursor_pos - 1).unwrap();
+                self.text.set_str(0, text).unwrap();
+                self.regen_glyphs().unwrap();
+            }
+            _ => {}
         }
-        // Ability to move cursor
     }
 }
 

+ 9 - 7
bin/darkwallet/src/gfx.rs

@@ -121,6 +121,8 @@ impl<T: Copy + std::ops::Add<Output=T> + std::ops::Sub<Output=T> + std::cmp::Par
     }
 }
 
+pub type FreetypeFace = ft::Face<&'static [u8]>;
+
 #[derive(Debug)]
 enum GraphicsMethodEvent {
     LoadTexture,
@@ -128,14 +130,14 @@ enum GraphicsMethodEvent {
     CreateEditBox,
 }
 
-struct Stage<'a> {
+struct Stage {
     ctx: Box<dyn RenderingBackend>,
     pipeline: Pipeline,
 
     scene_graph: SceneGraphPtr,
 
     textures: ResourceManager<TextureId>,
-    font_faces: Vec<ft::Face<&'a [u8]>>,
+    font_faces: Vec<FreetypeFace>,
 
     method_recvr: mpsc::Receiver<(GraphicsMethodEvent, SceneNodeId, Vec<u8>, MethodResponseFn)>,
     method_sender: mpsc::SyncSender<(GraphicsMethodEvent, SceneNodeId, Vec<u8>, MethodResponseFn)>,
@@ -143,10 +145,10 @@ struct Stage<'a> {
     last_draw_time: Option<Instant>,
 }
 
-impl<'a> Stage<'a> {
+impl Stage {
     const WHITE_TEXTURE_ID: ResourceId = 0;
 
-    pub fn new(scene_graph: SceneGraphPtr) -> Stage<'a> {
+    pub fn new(scene_graph: SceneGraphPtr) -> Self {
         let mut ctx: Box<dyn RenderingBackend> = window::new_rendering_backend();
 
         let white_texture = ctx.new_texture_from_rgba8(1, 1, &[255, 255, 255, 255]);
@@ -390,7 +392,7 @@ impl<'a> Stage<'a> {
         let node_id = SceneNodeId::decode(&mut cur).unwrap();
 
         let mut scene_graph = self.scene_graph.lock().unwrap();
-        let editbox = editbox::EditBox::new(&mut scene_graph, node_id)?;
+        let editbox = editbox::EditBox::new(&mut scene_graph, node_id, self.font_faces.clone())?;
 
         let node = scene_graph.get_node_mut(node_id).ok_or(Error::NodeNotFound)?;
         node.pimpl = editbox;
@@ -406,7 +408,7 @@ pub struct RenderContext<'a> {
     pub pipeline: &'a Pipeline,
     pub proj: glam::Mat4,
     pub textures: &'a ResourceManager<TextureId>,
-    pub font_faces: &'a Vec<ft::Face<&'a [u8]>>,
+    pub font_faces: &'a Vec<FreetypeFace>,
 }
 
 impl<'a> RenderContext<'a> {
@@ -965,7 +967,7 @@ impl<'a> RenderContext<'a> {
     }
 }
 
-impl<'a> EventHandler for Stage<'a> {
+impl EventHandler for Stage {
     fn update(&mut self) {
         if self.last_draw_time.is_none() {
             return

+ 27 - 11
bin/darkwallet/src/text.rs

@@ -1,6 +1,6 @@
 use freetype as ft;
 
-use crate::gfx::Rectangle;
+use crate::gfx::{Rectangle, FreetypeFace};
 
 pub struct Glyph {
     // Substring this glyph corresponds to
@@ -14,11 +14,14 @@ pub struct Glyph {
     pub pos: Rectangle<f32>,
 }
 
-pub struct TextShaper<'a> {
-    pub font_faces: &'a Vec<ft::Face<&'a [u8]>>,
+pub struct TextShaper {
+    pub font_faces: Vec<FreetypeFace>,
 }
 
-impl<'a> TextShaper<'a> {
+unsafe impl Send for TextShaper {}
+unsafe impl Sync for TextShaper {}
+
+impl TextShaper {
     fn split_into_substrs(&self, text: String) -> Vec<(usize, String)> {
         let mut current_idx = 0;
         let mut current_str = String::new();
@@ -57,7 +60,7 @@ impl<'a> TextShaper<'a> {
     pub fn shape(&self, text: String, font_size: f32, text_color: [f32; 4]) -> Vec<Glyph> {
         let substrs = self.split_into_substrs(text.clone());
 
-        let mut glyphs = vec![];
+        let mut glyphs: Vec<Glyph> = vec![];
 
         let mut current_x = 0.;
         let mut current_y = 0.;
@@ -73,16 +76,28 @@ impl<'a> TextShaper<'a> {
             }
 
             let hb_font = harfbuzz_rs::Font::from_freetype_face(face.clone());
-            let buffer = harfbuzz_rs::UnicodeBuffer::new().add_str(&text);
+            let buffer = harfbuzz_rs::UnicodeBuffer::new()
+                .set_cluster_level(harfbuzz_rs::ClusterLevel::MonotoneCharacters)
+                .add_str(&text);
             let output = harfbuzz_rs::shape(&hb_font, buffer, &[]);
 
             let positions = output.get_glyph_positions();
             let infos = output.get_glyph_infos();
 
-            for (position, info) in positions.iter().zip(infos) {
+            let mut prev_cluster = 0;
+
+            for (i, (position, info)) in positions.iter().zip(infos).enumerate() {
                 let gid = info.codepoint;
                 // Index within this substr
-                // let cluster = info.cluster;
+                let curr_cluster = info.cluster as usize;
+
+                // Skip first time
+                if i != 0 {
+                    let substr = text[prev_cluster..curr_cluster].to_string();
+                    glyphs.last_mut().unwrap().substr = substr;
+                }
+
+                prev_cluster = curr_cluster;
 
                 let mut flags = ft::face::LoadFlag::DEFAULT;
                 if face.has_color() {
@@ -168,10 +183,8 @@ impl<'a> TextShaper<'a> {
                     }
                 };
 
-                let substr = "hello".to_string();
-
                 let glyph = Glyph {
-                    substr,
+                    substr: String::new(),
                     bmp,
                     bmp_width: bmp_width as u16,
                     bmp_height: bmp_height as u16,
@@ -180,6 +193,9 @@ impl<'a> TextShaper<'a> {
 
                 glyphs.push(glyph);
             }
+
+            let substr = text[prev_cluster..].to_string();
+            glyphs.last_mut().unwrap().substr = substr;
         }
 
         glyphs