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

wallet/chatedit: when typing commands like /nick, highlight them with special coloring

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

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

@@ -313,6 +313,11 @@ pub fn create_chatedit(name: &str) -> SceneNode {
     prop.set_range_f32(0., 1.);
     node.add_property(prop).unwrap();
 
+    let mut prop = Property::new("text_cmd_color", PropertyType::Float32, PropertySubType::Color);
+    prop.set_array_len(4);
+    prop.set_range_f32(0., 1.);
+    node.add_property(prop).unwrap();
+
     let mut prop = Property::new("cursor_color", PropertyType::Float32, PropertySubType::Color);
     prop.set_array_len(4);
     prop.set_range_f32(0., 1.);
@@ -350,6 +355,11 @@ pub fn create_chatedit(name: &str) -> SceneNode {
     prop.set_range_f32(0., 1.);
     node.add_property(prop).unwrap();
 
+    let mut prop = Property::new("cmd_bg_color", PropertyType::Float32, PropertySubType::Color);
+    prop.set_array_len(4);
+    prop.set_range_f32(0., 1.);
+    node.add_property(prop).unwrap();
+
     let mut prop = Property::new("select_text", PropertyType::Str, PropertySubType::Null);
     prop.allow_null_values();
     prop.set_defaults_null().unwrap();

+ 18 - 1
bin/darkwallet/src/app/schema/chat.rs

@@ -688,6 +688,11 @@ pub async fn make(
     prop.set_f32(Role::App, 1, 0.96).unwrap();
     prop.set_f32(Role::App, 2, 1.).unwrap();
     prop.set_f32(Role::App, 3, 1.).unwrap();
+    let prop = node.get_property("text_cmd_color").unwrap();
+    prop.set_f32(Role::App, 0, 0.64).unwrap();
+    prop.set_f32(Role::App, 1, 1.).unwrap();
+    prop.set_f32(Role::App, 2, 0.83).unwrap();
+    prop.set_f32(Role::App, 3, 1.).unwrap();
     let prop = node.get_property("cursor_color").unwrap();
     prop.set_f32(Role::App, 0, 0.816).unwrap();
     prop.set_f32(Role::App, 1, 0.627).unwrap();
@@ -695,10 +700,10 @@ pub async fn make(
     prop.set_f32(Role::App, 3, 1.).unwrap();
     node.set_property_f32(Role::App, "cursor_ascent", CHATEDIT_CURSOR_ASCENT).unwrap();
     node.set_property_f32(Role::App, "cursor_descent", CHATEDIT_CURSOR_DESCENT).unwrap();
-    let prop = node.get_property("hi_bg_color").unwrap();
     node.set_property_f32(Role::App, "select_ascent", CHATEDIT_SELECT_ASCENT).unwrap();
     node.set_property_f32(Role::App, "select_descent", CHATEDIT_SELECT_DESCENT).unwrap();
     node.set_property_f32(Role::App, "handle_descent", CHATEDIT_HANDLE_DESCENT).unwrap();
+    let prop = node.get_property("hi_bg_color").unwrap();
     if LIGHTMODE {
         prop.set_f32(Role::App, 0, 0.5).unwrap();
         prop.set_f32(Role::App, 1, 0.5).unwrap();
@@ -710,6 +715,18 @@ 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("cmd_bg_color").unwrap();
+    if LIGHTMODE {
+        prop.set_f32(Role::App, 0, 0.5).unwrap();
+        prop.set_f32(Role::App, 1, 0.5).unwrap();
+        prop.set_f32(Role::App, 2, 0.5).unwrap();
+        prop.set_f32(Role::App, 3, 1.).unwrap();
+    } else {
+        prop.set_f32(Role::App, 0, 0.).unwrap();
+        prop.set_f32(Role::App, 1, 0.30).unwrap();
+        prop.set_f32(Role::App, 2, 0.25).unwrap();
+        prop.set_f32(Role::App, 3, 1.).unwrap();
+    }
     node.set_property_u32(Role::App, "z_index", 3).unwrap();
     //node.set_property_bool(Role::App, "debug", true).unwrap();
 

+ 70 - 13
bin/darkwallet/src/ui/chatedit.rs

@@ -40,7 +40,7 @@ use crate::{
         GfxDrawCall, GfxDrawInstruction, GfxDrawMesh, GfxTextureId, GraphicsEventPublisherPtr,
         Point, Rectangle, RenderApi, Vertex,
     },
-    mesh::{MeshBuilder, MeshInfo, COLOR_BLUE, COLOR_RED, COLOR_WHITE},
+    mesh::{Color, MeshBuilder, MeshInfo, COLOR_BLUE, COLOR_RED, COLOR_WHITE},
     prop::{
         PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr, PropertyRect, PropertyStr,
         PropertyUint32, Role,
@@ -48,7 +48,7 @@ use crate::{
     pubsub::Subscription,
     scene::{MethodCallSub, Pimpl, SceneNodePtr, SceneNodeWeak},
     text::{self, Glyph, GlyphPositionIter, TextShaperPtr},
-    util::{enumerate_ref, is_whitespace, min_f32, zip3},
+    util::{enumerate_ref, is_whitespace, min_f32, zip4},
     ExecutorPtr,
 };
 
@@ -442,6 +442,12 @@ impl TouchInfo {
     }
 }
 
+enum ColoringState {
+    Start,
+    IsCommand,
+    Normal,
+}
+
 pub type ChatEditPtr = Arc<ChatEdit>;
 
 pub struct ChatEdit {
@@ -474,6 +480,7 @@ pub struct ChatEdit {
     text: PropertyStr,
     text_color: PropertyColor,
     text_hi_color: PropertyColor,
+    text_cmd_color: PropertyColor,
     cursor_color: PropertyColor,
     cursor_width: PropertyFloat32,
     cursor_ascent: PropertyFloat32,
@@ -481,6 +488,7 @@ pub struct ChatEdit {
     cursor_blink_time: PropertyUint32,
     cursor_idle_time: PropertyUint32,
     hi_bg_color: PropertyColor,
+    cmd_bg_color: PropertyColor,
     select_ascent: PropertyFloat32,
     select_descent: PropertyFloat32,
     handle_descent: PropertyFloat32,
@@ -533,6 +541,8 @@ impl ChatEdit {
         let text = PropertyStr::wrap(node_ref, Role::Internal, "text", 0).unwrap();
         let text_color = PropertyColor::wrap(node_ref, Role::Internal, "text_color").unwrap();
         let text_hi_color = PropertyColor::wrap(node_ref, Role::Internal, "text_hi_color").unwrap();
+        let text_cmd_color =
+            PropertyColor::wrap(node_ref, Role::Internal, "text_cmd_color").unwrap();
         let cursor_color = PropertyColor::wrap(node_ref, Role::Internal, "cursor_color").unwrap();
         let cursor_width =
             PropertyFloat32::wrap(node_ref, Role::Internal, "cursor_width", 0).unwrap();
@@ -541,6 +551,7 @@ impl ChatEdit {
         let cursor_descent =
             PropertyFloat32::wrap(node_ref, Role::Internal, "cursor_descent", 0).unwrap();
         let hi_bg_color = PropertyColor::wrap(node_ref, Role::Internal, "hi_bg_color").unwrap();
+        let cmd_bg_color = PropertyColor::wrap(node_ref, Role::Internal, "cmd_bg_color").unwrap();
         let select_ascent =
             PropertyFloat32::wrap(node_ref, Role::Internal, "select_ascent", 0).unwrap();
         let select_descent =
@@ -589,6 +600,7 @@ impl ChatEdit {
             text,
             text_color,
             text_hi_color,
+            text_cmd_color,
             cursor_color,
             cursor_width,
             cursor_ascent,
@@ -596,6 +608,7 @@ impl ChatEdit {
             cursor_blink_time,
             cursor_idle_time,
             hi_bg_color,
+            cmd_bg_color,
             select_ascent,
             select_descent,
             handle_descent,
@@ -630,10 +643,10 @@ impl ChatEdit {
         //    .lock()
         //    .editable
         //    .set_text("".to_string(), "king!😁🍆jelly 🍆1234".to_string());
-        self_.text_wrap.lock().editable.set_text(
-            "".to_string(),
-            "A berry is a small, pulpy, and often edible fruit. Typically, berries are juicy, rounded, brightly colored, sweet, sour or tart, and do not have a stone or pit, although many pips or seeds may be present. Common examples of berries in the culinary sense are strawberries, raspberries, blueberries, blackberries, white currants, blackcurrants, and redcurrants. In Britain, soft fruit is a horticultural term for such fruits. The common usage of the term berry is different from the scientific or botanical definition of a berry, which refers to a fruit produced from the ovary of a single flower where the outer layer of the ovary wall develops into an edible fleshy portion (pericarp). The botanical definition includes many fruits that are not commonly known or referred to as berries, such as grapes, tomatoes, cucumbers, eggplants, bananas, and chili peppers.".to_string()
-        );
+        //self_.text_wrap.lock().editable.set_text(
+        //    "".to_string(),
+        //    "A berry is a small, pulpy, and often edible fruit. Typically, berries are juicy, rounded, brightly colored, sweet, sour or tart, and do not have a stone or pit, although many pips or seeds may be present. Common examples of berries in the culinary sense are strawberries, raspberries, blueberries, blackberries, white currants, blackcurrants, and redcurrants. In Britain, soft fruit is a horticultural term for such fruits. The common usage of the term berry is different from the scientific or botanical definition of a berry, which refers to a fruit produced from the ovary of a single flower where the outer layer of the ovary wall develops into an edible fleshy portion (pericarp). The botanical definition includes many fruits that are not commonly known or referred to as berries, such as grapes, tomatoes, cucumbers, eggplants, bananas, and chili peppers.".to_string()
+        //);
         //self_
         //    .text_wrap
         //    .lock()
@@ -669,6 +682,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 text_cmd_color = self.text_cmd_color.get();
         let linespacing = self.linespacing.get();
         let baseline = self.baseline.get();
         let scroll = self.scroll.get();
@@ -725,7 +739,16 @@ impl ChatEdit {
 
         for (line_idx, wrap_line) in wrapped_lines.lines.iter().enumerate() {
             let select_marks = self.mark_selected_glyphs(&wrap_line, &selections);
-            self.draw_selected(&mut mesh, &select_marks, &wrap_line, curr_y);
+            let hi_bg_color = self.hi_bg_color.get();
+            self.draw_text_bg_box(&mut mesh, &select_marks, &wrap_line, curr_y, hi_bg_color);
+
+            let cmd_marks = if line_idx == 0 {
+                self.mark_command_glyphs(&wrap_line)
+            } else {
+                vec![false; wrap_line.len()]
+            };
+            let cmd_bg_color = self.cmd_bg_color.get();
+            self.draw_text_bg_box(&mut mesh, &cmd_marks, &wrap_line, curr_y, cmd_bg_color);
 
             /*
             if rendered.has_underline() {
@@ -741,9 +764,12 @@ impl ChatEdit {
 
             let pos_iter = wrap_line.pos_iter();
 
-            for (_, mut glyph_rect, glyph, is_selected) in
-                zip3(pos_iter, wrap_line.glyphs.iter(), select_marks.into_iter())
-            {
+            for (_, mut glyph_rect, glyph, is_selected, is_cmd) in zip4(
+                pos_iter,
+                wrap_line.glyphs.iter(),
+                select_marks.into_iter(),
+                cmd_marks.into_iter(),
+            ) {
                 let uv_rect = atlas.fetch_uv(glyph.glyph_id).expect("missing glyph UV rect");
 
                 glyph_rect.y += curr_y;
@@ -752,7 +778,10 @@ impl ChatEdit {
                 let mut color = text_color.clone();
                 if is_selected {
                     color = text_hi_color.clone();
+                } else if is_cmd {
+                    color = text_cmd_color.clone();
                 }
+
                 if glyph.sprite.has_color {
                     color = COLOR_WHITE;
                 }
@@ -849,18 +878,46 @@ impl ChatEdit {
         marks
     }
 
-    fn draw_selected(
+    fn mark_command_glyphs(&self, wrap_line: &WrappedLine) -> Vec<bool> {
+        let mut state = ColoringState::Start;
+        let mut marks = vec![false; wrap_line.len()];
+        for (idx, glyph) in wrap_line.glyphs.iter().enumerate() {
+            match state {
+                ColoringState::Start => {
+                    if glyph.substr == "/" {
+                        state = ColoringState::IsCommand
+                    } else {
+                        state = ColoringState::Normal
+                    }
+                }
+                ColoringState::IsCommand => {
+                    if is_whitespace(&glyph.substr) {
+                        state = ColoringState::Normal
+                    }
+                }
+                _ => {}
+            }
+
+            match state {
+                ColoringState::IsCommand => marks[idx] = true,
+                _ => {}
+            }
+        }
+        marks
+    }
+
+    fn draw_text_bg_box(
         &self,
         mesh: &mut MeshBuilder,
         select_marks: &Vec<bool>,
         wrap_line: &WrappedLine,
         y_off: f32,
+        color: Color,
     ) {
         let font_size = self.font_size.get();
         let baseline = self.baseline.get();
         let select_ascent = self.select_ascent.get();
         let select_descent = self.select_descent.get();
-        let hi_bg_color = self.hi_bg_color.get();
 
         if select_marks.iter().all(|b| !b) {
             return
@@ -901,7 +958,7 @@ impl ChatEdit {
             w: end_x - start_x,
             h: select_ascent + select_descent,
         };
-        mesh.draw_box(&select_rect, hi_bg_color, &Rectangle::zero());
+        mesh.draw_box(&select_rect, color, &Rectangle::zero());
     }
 
     fn draw_phone_select_handle(

+ 46 - 1
bin/darkwallet/src/util.rs

@@ -117,7 +117,6 @@ where
     }
 }
 
-#[allow(dead_code)]
 pub fn zip3<X1, X2, X3, I1, I2, I3>(i1: I1, i2: I2, i3: I3) -> TupleIterStruct3<I1, I2, I3>
 where
     I1: Iterator<Item = X1>,
@@ -127,6 +126,52 @@ where
     TupleIterStruct3 { idx: 0, i1, i2, i3 }
 }
 
+pub struct TupleIterStruct4<I1, I2, I3, I4> {
+    idx: usize,
+    i1: I1,
+    i2: I2,
+    i3: I3,
+    i4: I4,
+}
+
+impl<I1, I2, I3, I4> Iterator for TupleIterStruct4<I1, I2, I3, I4>
+where
+    I1: Iterator,
+    I2: Iterator,
+    I3: Iterator,
+    I4: Iterator,
+{
+    type Item = (usize, I1::Item, I2::Item, I3::Item, I4::Item);
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let Some(x1) = self.i1.next() else { return None };
+        let Some(x2) = self.i2.next() else { return None };
+        let Some(x3) = self.i3.next() else { return None };
+        let Some(x4) = self.i4.next() else { return None };
+
+        let res = (self.idx, x1, x2, x3, x4);
+        self.idx += 1;
+
+        Some(res)
+    }
+}
+
+#[allow(dead_code)]
+pub fn zip4<X1, X2, X3, X4, I1, I2, I3, I4>(
+    i1: I1,
+    i2: I2,
+    i3: I3,
+    i4: I4,
+) -> TupleIterStruct4<I1, I2, I3, I4>
+where
+    I1: Iterator<Item = X1>,
+    I2: Iterator<Item = X2>,
+    I3: Iterator<Item = X3>,
+    I4: Iterator<Item = X4>,
+{
+    TupleIterStruct4 { idx: 0, i1, i2, i3, i4 }
+}
+
 pub fn enumerate<X>(v: Vec<X>) -> impl Iterator<Item = (usize, X)> {
     v.into_iter().enumerate()
 }