chatview.rs 12 KB

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