| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339 |
- /* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2024 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
- use atomic_float::AtomicF32;
- use miniquad::{TextureId, UniformType};
- use std::{
- collections::HashMap,
- io::{BufRead, BufReader},
- path::Path,
- sync::{
- atomic::Ordering,
- Arc, Mutex,
- },
- };
- use crate::{
- error::Result,
- gfx::{
- FreetypeFace, Point, Rectangle, RenderContext,
- COLOR_RED, COLOR_WHITE,
- },
- prop::PropertyBool,
- scene::{Pimpl, SceneGraph, SceneNodeId},
- text::{Glyph, TextShaper},
- };
- fn read_lines<P>(filename: P) -> Vec<String>
- where
- P: AsRef<Path>,
- {
- //let file = File::open(filename).unwrap();
- //BufReader::new(file).lines().map(|l| l.unwrap()).collect()
- // Just so we can package for android easily
- // Later this will be all replaced anyway
- let file = include_bytes!("../chat.txt");
- BufReader::new(&file[..]).lines().map(|l| l.unwrap()).collect()
- }
- pub type ChatViewPtr = Arc<ChatView>;
- pub struct ChatView {
- node_name: String,
- debug: PropertyBool,
- // Used for mouse interaction
- world_rect: Mutex<Rectangle<f32>>,
- mouse_pos: Mutex<Point<f32>>,
- text_shaper: TextShaper,
- scroll: AtomicF32,
- lines: Vec<String>,
- glyph_lines: Mutex<Vec<Vec<Glyph>>>,
- atlas: Mutex<HashMap<(u32, [u8; 4]), TextureId>>,
- }
- impl ChatView {
- 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 node_name = node.name.clone();
- let debug = PropertyBool::wrap(node, "debug", 0)?;
- let text_shaper = TextShaper { font_faces };
- let lines = read_lines("chat.txt");
- let mut glyph_lines = vec![];
- glyph_lines.resize(lines.len(), vec![]);
- let self_ = Arc::new(Self {
- node_name: node_name.clone(),
- debug,
- world_rect: Mutex::new(Rectangle { x: 0., y: 0., w: 0., h: 0. }),
- mouse_pos: Mutex::new(Point { x: 0., y: 0. }),
- text_shaper,
- scroll: AtomicF32::new(0.),
- lines,
- glyph_lines: Mutex::new(glyph_lines),
- atlas: Mutex::new(HashMap::new()),
- });
- /*
- let weak_self = Arc::downgrade(&self_);
- let slot_move = Slot {
- name: format!("{}::mouse_move", node_name),
- func: Box::new(move |data| {
- let mut cur = Cursor::new(&data);
- let x = f32::decode(&mut cur).unwrap();
- let y = f32::decode(&mut cur).unwrap();
- let self_ = weak_self.upgrade();
- if let Some(self_) = self_ {
- let pos = &mut *self_.mouse_pos.lock().unwrap();
- pos.x = x;
- pos.y = y;
- }
- }),
- };
- let weak_self = Arc::downgrade(&self_);
- let slot_wheel = Slot {
- name: format!("{}::mouse_wheel", node_name),
- func: Box::new(move |data| {
- let mut cur = Cursor::new(&data);
- let x = f32::decode(&mut cur).unwrap();
- let y = f32::decode(&mut cur).unwrap();
- let self_ = weak_self.upgrade();
- if let Some(self_) = self_ {
- self_.mouse_scroll(x, y);
- }
- }),
- };
- */
- let mouse_node =
- scene_graph.lookup_node_mut("/window/input/mouse").expect("no mouse attached!");
- //mouse_node.register("wheel", slot_wheel);
- //mouse_node.register("move", slot_move);
- // Save any properties we use
- Ok(Pimpl::ChatView(self_))
- }
- pub fn render<'a>(
- &self,
- render: &mut RenderContext<'a>,
- node_id: SceneNodeId,
- layer_rect: &Rectangle<f32>,
- ) -> Result<()> {
- let debug = self.debug.get();
- let node = render.scene_graph.get_node(node_id).unwrap();
- let rect = RenderContext::get_dim(node, layer_rect)?;
- // Used for detecting mouse clicks
- let mut world_rect = rect.clone();
- world_rect.x += layer_rect.x as f32;
- world_rect.y += layer_rect.y as f32;
- *self.world_rect.lock().unwrap() = world_rect;
- let layer_w = layer_rect.w as f32;
- let layer_h = layer_rect.h as f32;
- let off_x = rect.x / layer_w;
- let off_y = rect.y / layer_h;
- // Use absolute pixel scale
- let scale_x = 1. / layer_w;
- let scale_y = 1. / layer_h;
- let model = glam::Mat4::from_translation(glam::Vec3::new(off_x, off_y, 0.)) *
- glam::Mat4::from_scale(glam::Vec3::new(scale_x, scale_y, 1.));
- let mut uniforms_data = [0u8; 128];
- let data: [u8; 64] = unsafe { std::mem::transmute_copy(&render.proj) };
- uniforms_data[0..64].copy_from_slice(&data);
- let data: [u8; 64] = unsafe { std::mem::transmute_copy(&model) };
- uniforms_data[64..].copy_from_slice(&data);
- assert_eq!(128, 2 * UniformType::Mat4.size());
- render.ctx.apply_uniforms_from_bytes(uniforms_data.as_ptr(), uniforms_data.len());
- // Used for scaling the font size
- let window = render.scene_graph.lookup_node("/window").expect("no window attached!");
- let window_scale = window.get_property_f32("scale")?;
- let font_size = window_scale * 20.;
- let bound = Rectangle { x: 0., y: 0., w: rect.w, h: rect.h };
- let glyph_lines = &mut self.glyph_lines.lock().unwrap();
- let atlas = &mut self.atlas.lock().unwrap();
- let scroll = self.scroll.load(Ordering::Relaxed);
- for (i, (line, glyph_line)) in self.lines.iter().zip(glyph_lines.iter_mut()).enumerate() {
- let line = line.replace("\t", " ");
- // Split time and nick from the line
- let mut iter = line.split_whitespace();
- let Some(time) = iter.next() else {
- error!("line missing time");
- continue
- };
- let Some(nick) = iter.next() else {
- error!("line missing nick");
- continue
- };
- let Some(line) = iter.remainder() else {
- error!("line missing remainder");
- continue
- };
- let linespacing = window_scale * 30.;
- let off_y = linespacing * i as f32 + scroll;
- if off_y + linespacing < 0. || off_y - linespacing > rect.h {
- continue;
- }
- if glyph_line.is_empty() {
- *glyph_line = self.text_shaper.shape(line.to_string(), font_size, COLOR_WHITE);
- }
- let times_color = [0.4, 0.4, 0.4, 1.];
- let times_color_u8 =
- [(255. * 0.4) as u8, (255. * 0.4) as u8, (255. * 0.4) as u8, (255. * 1.) as u8];
- let glyphs_time = self.text_shaper.shape(time.to_string(), font_size, times_color);
- let mut rhs = 0.;
- for glyph in glyphs_time {
- let mut pos = glyph.pos.clone();
- pos.y += off_y;
- rhs = pos.x + pos.w;
- assert_eq!(glyph.bmp.len() as u16, glyph.bmp_width * glyph.bmp_height * 4);
- //debug!("gly {} {}", glyph.substr, glyph.bmp.len());
- let texture = if atlas.contains_key(&(glyph.id, times_color_u8.clone())) {
- *atlas.get(&(glyph.id, times_color_u8.clone())).unwrap()
- } else {
- let texture = render.ctx.new_texture_from_rgba8(
- glyph.bmp_width,
- glyph.bmp_height,
- &glyph.bmp,
- );
- atlas.insert((glyph.id, times_color_u8.clone()), texture);
- texture
- };
- render.render_clipped_box_with_texture2(&bound, &pos, COLOR_WHITE, texture);
- //render.ctx.delete_texture(texture);
- }
- 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.],
- ];
- let nick_colors_u8 = [
- [(255. * 0.00) as u8, (255. * 0.94) as u8, (255. * 1.00) as u8, (255. * 1.) as u8],
- [(255. * 0.36) as u8, (255. * 1.00) as u8, (255. * 0.69) as u8, (255. * 1.) as u8],
- [(255. * 0.29) as u8, (255. * 1.00) as u8, (255. * 0.45) as u8, (255. * 1.) as u8],
- [(255. * 0.00) as u8, (255. * 0.73) as u8, (255. * 0.38) as u8, (255. * 1.) as u8],
- [(255. * 0.21) as u8, (255. * 0.67) as u8, (255. * 0.67) as u8, (255. * 1.) as u8],
- [(255. * 0.56) as u8, (255. * 0.61) as u8, (255. * 1.00) as u8, (255. * 1.) as u8],
- [(255. * 0.84) as u8, (255. * 0.48) as u8, (255. * 1.00) as u8, (255. * 1.) as u8],
- [(255. * 1.00) as u8, (255. * 0.61) as u8, (255. * 0.94) as u8, (255. * 1.) as u8],
- [(255. * 1.00) as u8, (255. * 0.36) as u8, (255. * 0.48) as u8, (255. * 1.) as u8],
- [(255. * 1.00) as u8, (255. * 0.30) as u8, (255. * 0.00) as u8, (255. * 1.) as u8],
- ];
- let nick_color = nick_colors[nick.len() % nick_colors.len()];
- let nick_color_u8 = nick_colors_u8[nick.len() % nick_colors.len()];
- let glyphs_nick = self.text_shaper.shape(nick.to_string(), font_size, nick_color);
- let off_x = rhs + window_scale * 20.;
- for glyph in glyphs_nick {
- let mut pos = glyph.pos.clone();
- pos.x += off_x;
- pos.y += off_y;
- rhs = pos.x + pos.w;
- assert_eq!(glyph.bmp.len() as u16, glyph.bmp_width * glyph.bmp_height * 4);
- //debug!("gly {} {}", glyph.substr, glyph.bmp.len());
- //let texture = render.ctx.new_texture_from_rgba8(glyph.bmp_width, glyph.bmp_height, &glyph.bmp);
- let texture = if atlas.contains_key(&(glyph.id, nick_color_u8.clone())) {
- *atlas.get(&(glyph.id, nick_color_u8.clone())).unwrap()
- } else {
- let texture = render.ctx.new_texture_from_rgba8(
- glyph.bmp_width,
- glyph.bmp_height,
- &glyph.bmp,
- );
- atlas.insert((glyph.id, nick_color_u8.clone()), texture);
- texture
- };
- render.render_clipped_box_with_texture2(&bound, &pos, COLOR_WHITE, texture);
- //render.ctx.delete_texture(texture);
- }
- let off_x = rhs + window_scale * 20.;
- for glyph in glyph_line {
- let mut pos = glyph.pos.clone();
- pos.x += off_x;
- pos.y += off_y;
- assert_eq!(glyph.bmp.len() as u16, glyph.bmp_width * glyph.bmp_height * 4);
- //debug!("gly {} {}", glyph.substr, glyph.bmp.len());
- //let texture = render.ctx.new_texture_from_rgba8(glyph.bmp_width, glyph.bmp_height, &glyph.bmp);
- let texture = if atlas.contains_key(&(glyph.id, [255, 255, 255, 255])) {
- *atlas.get(&(glyph.id, [255, 255, 255, 255])).unwrap()
- } else {
- let texture = render.ctx.new_texture_from_rgba8(
- glyph.bmp_width,
- glyph.bmp_height,
- &glyph.bmp,
- );
- atlas.insert((glyph.id, [255, 255, 255, 255]), texture);
- texture
- };
- render.render_clipped_box_with_texture2(&bound, &pos, COLOR_WHITE, texture);
- //render.ctx.delete_texture(texture);
- }
- }
- if debug {
- render.outline(0., 0., rect.w, rect.h, COLOR_RED, 1.);
- }
- Ok(())
- }
- fn mouse_scroll(self: Arc<Self>, _x: f32, y: f32) {
- let mouse_pos = &*self.mouse_pos.lock().unwrap();
- if !self.world_rect.lock().unwrap().contains(mouse_pos) {
- return;
- }
- drop(mouse_pos);
- self.scroll.fetch_add(y * 10., Ordering::Relaxed);
- // y = 1 for scroll up
- // y = -1 for scroll down
- //println!("{}", y);
- }
- }
|