chatview.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2024 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use atomic_float::AtomicF32;
  19. use miniquad::{TextureId, UniformType};
  20. use std::{
  21. collections::HashMap,
  22. io::{BufRead, BufReader},
  23. path::Path,
  24. sync::{
  25. atomic::Ordering,
  26. Arc, Mutex,
  27. },
  28. };
  29. use crate::{
  30. error::Result,
  31. gfx::{
  32. FreetypeFace, Point, Rectangle, RenderContext,
  33. COLOR_RED, COLOR_WHITE,
  34. },
  35. prop::PropertyBool,
  36. scene::{Pimpl, SceneGraph, SceneNodeId},
  37. text::{Glyph, TextShaper},
  38. };
  39. fn read_lines<P>(filename: P) -> Vec<String>
  40. where
  41. P: AsRef<Path>,
  42. {
  43. //let file = File::open(filename).unwrap();
  44. //BufReader::new(file).lines().map(|l| l.unwrap()).collect()
  45. // Just so we can package for android easily
  46. // Later this will be all replaced anyway
  47. let file = include_bytes!("../chat.txt");
  48. BufReader::new(&file[..]).lines().map(|l| l.unwrap()).collect()
  49. }
  50. pub type ChatViewPtr = Arc<ChatView>;
  51. pub struct ChatView {
  52. node_name: String,
  53. debug: PropertyBool,
  54. // Used for mouse interaction
  55. world_rect: Mutex<Rectangle<f32>>,
  56. mouse_pos: Mutex<Point<f32>>,
  57. text_shaper: TextShaper,
  58. scroll: AtomicF32,
  59. lines: Vec<String>,
  60. glyph_lines: Mutex<Vec<Vec<Glyph>>>,
  61. atlas: Mutex<HashMap<(u32, [u8; 4]), TextureId>>,
  62. }
  63. impl ChatView {
  64. pub fn new(
  65. scene_graph: &mut SceneGraph,
  66. node_id: SceneNodeId,
  67. font_faces: Vec<FreetypeFace>,
  68. ) -> Result<Pimpl> {
  69. let node = scene_graph.get_node(node_id).unwrap();
  70. let node_name = node.name.clone();
  71. let debug = PropertyBool::wrap(node, "debug", 0)?;
  72. let text_shaper = TextShaper { font_faces };
  73. let lines = read_lines("chat.txt");
  74. let mut glyph_lines = vec![];
  75. glyph_lines.resize(lines.len(), vec![]);
  76. let self_ = Arc::new(Self {
  77. node_name: node_name.clone(),
  78. debug,
  79. world_rect: Mutex::new(Rectangle { x: 0., y: 0., w: 0., h: 0. }),
  80. mouse_pos: Mutex::new(Point { x: 0., y: 0. }),
  81. text_shaper,
  82. scroll: AtomicF32::new(0.),
  83. lines,
  84. glyph_lines: Mutex::new(glyph_lines),
  85. atlas: Mutex::new(HashMap::new()),
  86. });
  87. /*
  88. let weak_self = Arc::downgrade(&self_);
  89. let slot_move = Slot {
  90. name: format!("{}::mouse_move", node_name),
  91. func: Box::new(move |data| {
  92. let mut cur = Cursor::new(&data);
  93. let x = f32::decode(&mut cur).unwrap();
  94. let y = f32::decode(&mut cur).unwrap();
  95. let self_ = weak_self.upgrade();
  96. if let Some(self_) = self_ {
  97. let pos = &mut *self_.mouse_pos.lock().unwrap();
  98. pos.x = x;
  99. pos.y = y;
  100. }
  101. }),
  102. };
  103. let weak_self = Arc::downgrade(&self_);
  104. let slot_wheel = Slot {
  105. name: format!("{}::mouse_wheel", node_name),
  106. func: Box::new(move |data| {
  107. let mut cur = Cursor::new(&data);
  108. let x = f32::decode(&mut cur).unwrap();
  109. let y = f32::decode(&mut cur).unwrap();
  110. let self_ = weak_self.upgrade();
  111. if let Some(self_) = self_ {
  112. self_.mouse_scroll(x, y);
  113. }
  114. }),
  115. };
  116. */
  117. let mouse_node =
  118. scene_graph.lookup_node_mut("/window/input/mouse").expect("no mouse attached!");
  119. //mouse_node.register("wheel", slot_wheel);
  120. //mouse_node.register("move", slot_move);
  121. // Save any properties we use
  122. Ok(Pimpl::ChatView(self_))
  123. }
  124. pub fn render<'a>(
  125. &self,
  126. render: &mut RenderContext<'a>,
  127. node_id: SceneNodeId,
  128. layer_rect: &Rectangle<f32>,
  129. ) -> Result<()> {
  130. let debug = self.debug.get();
  131. let node = render.scene_graph.get_node(node_id).unwrap();
  132. let rect = RenderContext::get_dim(node, layer_rect)?;
  133. // Used for detecting mouse clicks
  134. let mut world_rect = rect.clone();
  135. world_rect.x += layer_rect.x as f32;
  136. world_rect.y += layer_rect.y as f32;
  137. *self.world_rect.lock().unwrap() = world_rect;
  138. let layer_w = layer_rect.w as f32;
  139. let layer_h = layer_rect.h as f32;
  140. let off_x = rect.x / layer_w;
  141. let off_y = rect.y / layer_h;
  142. // Use absolute pixel scale
  143. let scale_x = 1. / layer_w;
  144. let scale_y = 1. / layer_h;
  145. let model = glam::Mat4::from_translation(glam::Vec3::new(off_x, off_y, 0.)) *
  146. glam::Mat4::from_scale(glam::Vec3::new(scale_x, scale_y, 1.));
  147. let mut uniforms_data = [0u8; 128];
  148. let data: [u8; 64] = unsafe { std::mem::transmute_copy(&render.proj) };
  149. uniforms_data[0..64].copy_from_slice(&data);
  150. let data: [u8; 64] = unsafe { std::mem::transmute_copy(&model) };
  151. uniforms_data[64..].copy_from_slice(&data);
  152. assert_eq!(128, 2 * UniformType::Mat4.size());
  153. render.ctx.apply_uniforms_from_bytes(uniforms_data.as_ptr(), uniforms_data.len());
  154. // Used for scaling the font size
  155. let window = render.scene_graph.lookup_node("/window").expect("no window attached!");
  156. let window_scale = window.get_property_f32("scale")?;
  157. let font_size = window_scale * 20.;
  158. let bound = Rectangle { x: 0., y: 0., w: rect.w, h: rect.h };
  159. let glyph_lines = &mut self.glyph_lines.lock().unwrap();
  160. let atlas = &mut self.atlas.lock().unwrap();
  161. let scroll = self.scroll.load(Ordering::Relaxed);
  162. for (i, (line, glyph_line)) in self.lines.iter().zip(glyph_lines.iter_mut()).enumerate() {
  163. let line = line.replace("\t", " ");
  164. // Split time and nick from the line
  165. let mut iter = line.split_whitespace();
  166. let Some(time) = iter.next() else {
  167. error!("line missing time");
  168. continue
  169. };
  170. let Some(nick) = iter.next() else {
  171. error!("line missing nick");
  172. continue
  173. };
  174. let Some(line) = iter.remainder() else {
  175. error!("line missing remainder");
  176. continue
  177. };
  178. let linespacing = window_scale * 30.;
  179. let off_y = linespacing * i as f32 + scroll;
  180. if off_y + linespacing < 0. || off_y - linespacing > rect.h {
  181. continue;
  182. }
  183. if glyph_line.is_empty() {
  184. *glyph_line = self.text_shaper.shape(line.to_string(), font_size, COLOR_WHITE);
  185. }
  186. let times_color = [0.4, 0.4, 0.4, 1.];
  187. let times_color_u8 =
  188. [(255. * 0.4) as u8, (255. * 0.4) as u8, (255. * 0.4) as u8, (255. * 1.) as u8];
  189. let glyphs_time = self.text_shaper.shape(time.to_string(), font_size, times_color);
  190. let mut rhs = 0.;
  191. for glyph in glyphs_time {
  192. let mut pos = glyph.pos.clone();
  193. pos.y += off_y;
  194. rhs = pos.x + pos.w;
  195. assert_eq!(glyph.bmp.len() as u16, glyph.bmp_width * glyph.bmp_height * 4);
  196. //debug!("gly {} {}", glyph.substr, glyph.bmp.len());
  197. let texture = if atlas.contains_key(&(glyph.id, times_color_u8.clone())) {
  198. *atlas.get(&(glyph.id, times_color_u8.clone())).unwrap()
  199. } else {
  200. let texture = render.ctx.new_texture_from_rgba8(
  201. glyph.bmp_width,
  202. glyph.bmp_height,
  203. &glyph.bmp,
  204. );
  205. atlas.insert((glyph.id, times_color_u8.clone()), texture);
  206. texture
  207. };
  208. render.render_clipped_box_with_texture2(&bound, &pos, COLOR_WHITE, texture);
  209. //render.ctx.delete_texture(texture);
  210. }
  211. let nick_colors = [
  212. [0.00, 0.94, 1.00, 1.],
  213. [0.36, 1.00, 0.69, 1.],
  214. [0.29, 1.00, 0.45, 1.],
  215. [0.00, 0.73, 0.38, 1.],
  216. [0.21, 0.67, 0.67, 1.],
  217. [0.56, 0.61, 1.00, 1.],
  218. [0.84, 0.48, 1.00, 1.],
  219. [1.00, 0.61, 0.94, 1.],
  220. [1.00, 0.36, 0.48, 1.],
  221. [1.00, 0.30, 0.00, 1.],
  222. ];
  223. let nick_colors_u8 = [
  224. [(255. * 0.00) as u8, (255. * 0.94) as u8, (255. * 1.00) as u8, (255. * 1.) as u8],
  225. [(255. * 0.36) as u8, (255. * 1.00) as u8, (255. * 0.69) as u8, (255. * 1.) as u8],
  226. [(255. * 0.29) as u8, (255. * 1.00) as u8, (255. * 0.45) as u8, (255. * 1.) as u8],
  227. [(255. * 0.00) as u8, (255. * 0.73) as u8, (255. * 0.38) as u8, (255. * 1.) as u8],
  228. [(255. * 0.21) as u8, (255. * 0.67) as u8, (255. * 0.67) as u8, (255. * 1.) as u8],
  229. [(255. * 0.56) as u8, (255. * 0.61) as u8, (255. * 1.00) as u8, (255. * 1.) as u8],
  230. [(255. * 0.84) as u8, (255. * 0.48) as u8, (255. * 1.00) as u8, (255. * 1.) as u8],
  231. [(255. * 1.00) as u8, (255. * 0.61) as u8, (255. * 0.94) as u8, (255. * 1.) as u8],
  232. [(255. * 1.00) as u8, (255. * 0.36) as u8, (255. * 0.48) as u8, (255. * 1.) as u8],
  233. [(255. * 1.00) as u8, (255. * 0.30) as u8, (255. * 0.00) as u8, (255. * 1.) as u8],
  234. ];
  235. let nick_color = nick_colors[nick.len() % nick_colors.len()];
  236. let nick_color_u8 = nick_colors_u8[nick.len() % nick_colors.len()];
  237. let glyphs_nick = self.text_shaper.shape(nick.to_string(), font_size, nick_color);
  238. let off_x = rhs + window_scale * 20.;
  239. for glyph in glyphs_nick {
  240. let mut pos = glyph.pos.clone();
  241. pos.x += off_x;
  242. pos.y += off_y;
  243. rhs = pos.x + pos.w;
  244. assert_eq!(glyph.bmp.len() as u16, glyph.bmp_width * glyph.bmp_height * 4);
  245. //debug!("gly {} {}", glyph.substr, glyph.bmp.len());
  246. //let texture = render.ctx.new_texture_from_rgba8(glyph.bmp_width, glyph.bmp_height, &glyph.bmp);
  247. let texture = if atlas.contains_key(&(glyph.id, nick_color_u8.clone())) {
  248. *atlas.get(&(glyph.id, nick_color_u8.clone())).unwrap()
  249. } else {
  250. let texture = render.ctx.new_texture_from_rgba8(
  251. glyph.bmp_width,
  252. glyph.bmp_height,
  253. &glyph.bmp,
  254. );
  255. atlas.insert((glyph.id, nick_color_u8.clone()), texture);
  256. texture
  257. };
  258. render.render_clipped_box_with_texture2(&bound, &pos, COLOR_WHITE, texture);
  259. //render.ctx.delete_texture(texture);
  260. }
  261. let off_x = rhs + window_scale * 20.;
  262. for glyph in glyph_line {
  263. let mut pos = glyph.pos.clone();
  264. pos.x += off_x;
  265. pos.y += off_y;
  266. assert_eq!(glyph.bmp.len() as u16, glyph.bmp_width * glyph.bmp_height * 4);
  267. //debug!("gly {} {}", glyph.substr, glyph.bmp.len());
  268. //let texture = render.ctx.new_texture_from_rgba8(glyph.bmp_width, glyph.bmp_height, &glyph.bmp);
  269. let texture = if atlas.contains_key(&(glyph.id, [255, 255, 255, 255])) {
  270. *atlas.get(&(glyph.id, [255, 255, 255, 255])).unwrap()
  271. } else {
  272. let texture = render.ctx.new_texture_from_rgba8(
  273. glyph.bmp_width,
  274. glyph.bmp_height,
  275. &glyph.bmp,
  276. );
  277. atlas.insert((glyph.id, [255, 255, 255, 255]), texture);
  278. texture
  279. };
  280. render.render_clipped_box_with_texture2(&bound, &pos, COLOR_WHITE, texture);
  281. //render.ctx.delete_texture(texture);
  282. }
  283. }
  284. if debug {
  285. render.outline(0., 0., rect.w, rect.h, COLOR_RED, 1.);
  286. }
  287. Ok(())
  288. }
  289. fn mouse_scroll(self: Arc<Self>, _x: f32, y: f32) {
  290. let mouse_pos = &*self.mouse_pos.lock().unwrap();
  291. if !self.world_rect.lock().unwrap().contains(mouse_pos) {
  292. return;
  293. }
  294. drop(mouse_pos);
  295. self.scroll.fetch_add(y * 10., Ordering::Relaxed);
  296. // y = 1 for scroll up
  297. // y = -1 for scroll down
  298. //println!("{}", y);
  299. }
  300. }