diff --git a/bin/darkwallet/src/app.rs b/bin/darkwallet/src/app.rs index 1353f7340..1df14d91f 100644 --- a/bin/darkwallet/src/app.rs +++ b/bin/darkwallet/src/app.rs @@ -569,6 +569,35 @@ impl App { node.set_property_f32("baseline", 10.).unwrap(); node.set_property_u32("z_index", 1).unwrap(); + let prop = node.get_property("timestamp_color").unwrap(); + prop.set_f32(0, 0.5).unwrap(); + prop.set_f32(1, 0.5).unwrap(); + prop.set_f32(2, 0.5).unwrap(); + prop.set_f32(3, 0.5).unwrap(); + let prop = node.get_property("text_color").unwrap(); + prop.set_f32(0, 1.).unwrap(); + prop.set_f32(1, 1.).unwrap(); + prop.set_f32(2, 1.).unwrap(); + prop.set_f32(3, 1.).unwrap(); + + let prop = node.get_property("nick_colors").unwrap(); + #[rustfmt::skip] + let nick_colors = [ + 0.00, 0.94, 1.00, 1., + 0.36, 1.00, 0.69, 1., + 0.29, 1.00, 0.45, 1., + 0.00, 0.73, 0.38, 1., + 0.21, 0.67, 0.67, 1., + 0.56, 0.61, 1.00, 1., + 0.84, 0.48, 1.00, 1., + 1.00, 0.61, 0.94, 1., + 1.00, 0.36, 0.48, 1., + 1.00, 0.30, 0.00, 1. + ]; + for c in nick_colors { + prop.push_f32(c).unwrap(); + } + drop(sg); let db = sled::open(CHATDB_PATH).expect("cannot open sleddb"); let chat_tree = db.open_tree(b"chat").unwrap(); @@ -793,6 +822,21 @@ fn create_chatview(sg: &mut SceneGraph, name: &str) -> SceneNodeId { let prop = Property::new("line_height", PropertyType::Float32, PropertySubType::Pixel); node.add_property(prop).unwrap(); + let mut prop = Property::new("timestamp_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("text_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("nick_colors", PropertyType::Float32, PropertySubType::Pixel); + prop.set_unbounded(); + prop.set_range_f32(0., 1.); + node.add_property(prop).unwrap(); + let prop = Property::new("baseline", PropertyType::Float32, PropertySubType::Pixel); node.add_property(prop).unwrap(); diff --git a/bin/darkwallet/src/mesh.rs b/bin/darkwallet/src/mesh.rs index 6251a5a55..1821a5a08 100644 --- a/bin/darkwallet/src/mesh.rs +++ b/bin/darkwallet/src/mesh.rs @@ -26,9 +26,12 @@ pub type Color = [f32; 4]; pub const COLOR_RED: Color = [1., 0., 0., 1.]; pub const COLOR_DARKGREY: Color = [0.2, 0.2, 0.2, 1.]; +pub const COLOR_LIGHTGREY: Color = [0.7, 0.7, 0.7, 1.]; pub const COLOR_GREEN: Color = [0., 1., 0., 1.]; pub const COLOR_BLUE: Color = [0., 0., 1., 1.]; pub const COLOR_WHITE: Color = [1., 1., 1., 1.]; +pub const COLOR_BLACK: Color = [1., 1., 1., 1.]; +pub const COLOR_GREY: Color = [0.5, 0.5, 0.5, 1.]; #[derive(Clone)] pub struct MeshInfo { diff --git a/bin/darkwallet/src/ui/chatview.rs b/bin/darkwallet/src/ui/chatview.rs index c46977a64..3c24ec020 100644 --- a/bin/darkwallet/src/ui/chatview.rs +++ b/bin/darkwallet/src/ui/chatview.rs @@ -21,6 +21,7 @@ use miniquad::TouchPhase; use rand::{rngs::OsRng, Rng}; use std::{ collections::BTreeMap, + hash::{DefaultHasher, Hash, Hasher}, sync::{Arc, Mutex as SyncMutex, Weak}, }; @@ -30,7 +31,7 @@ use crate::{ DrawCall, DrawInstruction, DrawMesh, GraphicsEventPublisherPtr, Point, Rectangle, RenderApi, RenderApiPtr, Vertex, }, - mesh::{Color, MeshBuilder, MeshInfo, COLOR_BLUE, COLOR_WHITE}, + mesh::{Color, MeshBuilder, MeshInfo, COLOR_BLUE, COLOR_GREY, COLOR_WHITE}, prop::{ PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr, PropertyStr, PropertyUint32, }, @@ -44,6 +45,10 @@ use super::{eval_rect, get_parent_rect, read_rect, DrawUpdate, OnModify, Stoppab const DEBUG_RENDER: bool = false; +fn is_whitespace(s: &str) -> bool { + s.chars().all(char::is_whitespace) +} + #[derive(Clone, Debug, SerialEncodable, SerialDecodable)] pub struct ChatMsg { pub nick: String, @@ -104,6 +109,9 @@ pub struct ChatView { font_size: PropertyFloat32, line_height: PropertyFloat32, baseline: PropertyFloat32, + timestamp_color: PropertyColor, + text_color: PropertyColor, + nick_colors: PropertyPtr, z_index: PropertyUint32, } @@ -126,7 +134,10 @@ impl ChatView { let scroll = PropertyFloat32::wrap(node, "scroll", 0).unwrap(); let font_size = PropertyFloat32::wrap(node, "font_size", 0).unwrap(); let line_height = PropertyFloat32::wrap(node, "line_height", 0).unwrap(); - let baseline = PropertyFloat32::wrap(node, "line_height", 0).unwrap(); + let baseline = PropertyFloat32::wrap(node, "baseline", 0).unwrap(); + let timestamp_color = PropertyColor::wrap(node, "timestamp_color").unwrap(); + let text_color = PropertyColor::wrap(node, "text_color").unwrap(); + let nick_colors = node.get_property("nick_colors").expect("ChatView::nick_colors"); let z_index = PropertyUint32::wrap(node, "z_index", 0).unwrap(); drop(scene_graph); @@ -188,6 +199,9 @@ impl ChatView { font_size, line_height, baseline, + timestamp_color, + text_color, + nick_colors, z_index, } }); @@ -252,7 +266,7 @@ impl ChatView { debug!(target: "ui::chatview", "inside rect"); let scroll = self.scroll.get(); self.scroll.set(scroll + wheel_y * 50.); - // Render will auto update from on_modify hook + self.scrollview().await; } async fn handle_mouse_move(&self, mouse_x: f32, mouse_y: f32) { @@ -460,35 +474,63 @@ impl ChatView { let mut draws = vec![]; let color = COLOR_WHITE; + let timestamp_color = self.timestamp_color.get(); + let text_color = self.text_color.get(); + let nick_colors = self.read_nick_colors(); + // This is a little hack to nudge the bottom line up slightly, otherwise // chars like p will cross the bottom. - let descent = line_height / 2.; + let descent = baseline / 2.; // Pages start at the bottom. - let mut current_idx = 1; + let mut current_idx = 0; 'pageloop: for page in pages { let mut mesh = MeshBuilder::new(); for msg in page.msgs { let glyphs = msg.glyphs; + let nick_color = self.select_nick_color(&msg.chatmsg.nick, &nick_colors); + + // Keep track of the 'section' + // Section 0 is the timestamp + // Section 1 is the nickname (colorized) + // Finally is just the message itself + let mut section = 2; + let mut lines = text2::wrap(clip.w, font_size, &glyphs); // We are drawing bottom up but line wrap gives us lines in normal order lines.reverse(); - for line in lines { - let off_y = descent + current_idx as f32 * line_height; + let last_idx = lines.len() - 1; + for (i, line) in lines.into_iter().enumerate() { + let off_y = descent + baseline + current_idx as f32 * line_height; //if px_height > clip.h { // break 'pageloop; //} + if i == last_idx { + section = 0; + } + // Render line let mut glyph_pos_iter = GlyphPositionIter::new(font_size, &line, baseline); for (mut glyph_rect, glyph) in glyph_pos_iter.zip(line.iter()) { let uv_rect = page.atlas.fetch_uv(glyph.glyph_id).expect("missing glyph UV rect"); glyph_rect.y -= off_y; + + let color = match section { + 0 => timestamp_color, + 1 => nick_color, + _ => text_color, + }; + mesh.draw_box(&glyph_rect, color, uv_rect); + + if section < 2 && is_whitespace(&glyph.substr) { + section += 1; + } } current_idx += 1; @@ -524,6 +566,28 @@ impl ChatView { draws } + fn read_nick_colors(&self) -> Vec { + let mut colors = vec![]; + let mut color = [0f32; 4]; + for i in 0..self.nick_colors.get_len() { + color[i % 4] = self.nick_colors.get_f32(i).expect("prop logic err"); + + if i > 0 && i % 4 == 0 { + let color = std::mem::take(&mut color); + colors.push(color); + } + } + colors + } + + fn select_nick_color(&self, nick: &str, nick_colors: &[Color]) -> Color { + let mut hasher = DefaultHasher::new(); + nick.hash(&mut hasher); + let i = hasher.finish() as usize; + let color = nick_colors[i % nick_colors.len()]; + color + } + pub async fn draw(&self, sg: &SceneGraph, parent_rect: &Rectangle) -> Option { debug!(target: "ui::chatview", "ChatView::draw()"); // Only used for debug messages