chatview.rs 13 KB

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