chatview.rs 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951
  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 async_lock::Mutex as AsyncMutex;
  19. use darkfi_serial::{deserialize, Decodable, Encodable, SerialDecodable, SerialEncodable};
  20. use miniquad::TouchPhase;
  21. use rand::{rngs::OsRng, Rng};
  22. use std::{
  23. collections::BTreeMap,
  24. hash::{DefaultHasher, Hash, Hasher},
  25. io::Cursor,
  26. sync::{Arc, Mutex as SyncMutex, Weak},
  27. };
  28. use crate::{
  29. error::Result,
  30. gfx2::{
  31. DrawCall, DrawInstruction, DrawMesh, GraphicsEventPublisherPtr, Point, Rectangle,
  32. RenderApi, RenderApiPtr, Vertex,
  33. },
  34. mesh::{Color, MeshBuilder, MeshInfo, COLOR_BLUE, COLOR_GREY, COLOR_WHITE},
  35. prop::{
  36. PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr, PropertyStr, PropertyUint32,
  37. },
  38. pubsub::Subscription,
  39. scene::{Pimpl, SceneGraph, SceneGraphPtr2, SceneNodeId},
  40. text2::{self, Glyph, GlyphPositionIter, SpritePtr, TextShaper, TextShaperPtr},
  41. util::zip3,
  42. };
  43. use super::{eval_rect, get_parent_rect, read_rect, DrawUpdate, OnModify, Stoppable};
  44. const DEBUG_RENDER: bool = false;
  45. fn is_whitespace(s: &str) -> bool {
  46. s.chars().all(char::is_whitespace)
  47. }
  48. #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
  49. pub struct ChatMsg {
  50. pub nick: String,
  51. pub text: String,
  52. }
  53. type Timestamp = u32;
  54. #[derive(Clone)]
  55. struct Message {
  56. timest: Timestamp,
  57. chatmsg: ChatMsg,
  58. glyphs: Vec<Glyph>,
  59. }
  60. const LINES_PER_PAGE: usize = 10;
  61. const PRELOAD_PAGES: usize = 10;
  62. #[derive(Clone)]
  63. struct Page {
  64. msgs: Vec<Message>,
  65. atlas: text2::RenderedAtlas,
  66. }
  67. #[derive(Clone)]
  68. struct PageMeshInfo {
  69. px_height: f32,
  70. mesh: DrawMesh,
  71. }
  72. type Page2Ptr = Arc<Page2>;
  73. struct Page2 {
  74. msgs: Vec<Message>,
  75. atlas: SyncMutex<text2::RenderedAtlas>,
  76. // One draw call per page.
  77. // Resizing the canvas means we recalc wrapping and the mesh changes
  78. mesh_inf: SyncMutex<Option<PageMeshInfo>>,
  79. }
  80. impl Page2 {
  81. async fn new(msgs: Vec<Message>, render_api: &RenderApi) -> Arc<Self> {
  82. let mut atlas = text2::Atlas::new(render_api);
  83. for msg in &msgs {
  84. atlas.push(&msg.glyphs);
  85. }
  86. let Ok(atlas) = atlas.make().await else {
  87. // what else should I do here?
  88. panic!("unable to make atlas!");
  89. };
  90. Arc::new(Self { msgs, atlas: SyncMutex::new(atlas), mesh_inf: SyncMutex::new(None) })
  91. }
  92. /// Regenerates the mesh, returning the old mesh which should be freed
  93. async fn regen_mesh(
  94. &self,
  95. clip: &Rectangle,
  96. render_api: &RenderApi,
  97. font_size: f32,
  98. line_height: f32,
  99. baseline: f32,
  100. nick_colors: &[Color],
  101. timestamp_color: Color,
  102. text_color: Color,
  103. ) -> (PageMeshInfo, Option<DrawMesh>) {
  104. let mut wrapped_line_idx = 0;
  105. let mut mesh = MeshBuilder::new();
  106. let atlas = self.atlas.lock().unwrap().clone();
  107. for msg in &self.msgs {
  108. let glyphs = &msg.glyphs;
  109. let nick_color = select_nick_color(&msg.chatmsg.nick, nick_colors);
  110. // Keep track of the 'section'
  111. // Section 0 is the timestamp
  112. // Section 1 is the nickname (colorized)
  113. // Finally is just the message itself
  114. let mut section = 2;
  115. let mut lines = text2::wrap(clip.w, font_size, glyphs);
  116. // We are drawing bottom up but line wrap gives us lines in normal order
  117. lines.reverse();
  118. let last_idx = lines.len() - 1;
  119. for (i, line) in lines.into_iter().enumerate() {
  120. let off_y = (wrapped_line_idx + 1) as f32 * line_height;
  121. if i == last_idx {
  122. section = 0;
  123. }
  124. // debug draw baseline
  125. //let y = baseline - off_y;
  126. //mesh.draw_filled_box(&Rectangle { x: 0., y: y - 1., w: clip.w, h: 1. }, COLOR_BLUE);
  127. // Render line
  128. let mut glyph_pos_iter = GlyphPositionIter::new(font_size, &line, baseline);
  129. for (mut glyph_rect, glyph) in glyph_pos_iter.zip(line.iter()) {
  130. let uv_rect = atlas.fetch_uv(glyph.glyph_id).expect("missing glyph UV rect");
  131. glyph_rect.y -= off_y;
  132. let color = match section {
  133. 0 => timestamp_color,
  134. 1 => nick_color,
  135. _ => text_color,
  136. };
  137. //mesh.draw_outline(&glyph_rect, COLOR_BLUE, 2.);
  138. mesh.draw_box(&glyph_rect, color, uv_rect);
  139. if section < 2 && is_whitespace(&glyph.substr) {
  140. section += 1;
  141. }
  142. }
  143. wrapped_line_idx += 1;
  144. }
  145. }
  146. let px_height = wrapped_line_idx as f32 * line_height;
  147. let mesh = mesh.alloc(render_api).await.unwrap();
  148. let mesh = mesh.draw_with_texture(atlas.texture_id);
  149. let mesh_inf = PageMeshInfo { px_height, mesh };
  150. let old = std::mem::replace(&mut *self.mesh_inf.lock().unwrap(), Some(mesh_inf.clone()));
  151. let old = old.map(|v| v.mesh);
  152. (mesh_inf, old)
  153. }
  154. }
  155. fn select_nick_color(nick: &str, nick_colors: &[Color]) -> Color {
  156. let mut hasher = DefaultHasher::new();
  157. nick.hash(&mut hasher);
  158. let i = hasher.finish() as usize;
  159. let color = nick_colors[i % nick_colors.len()];
  160. color
  161. }
  162. struct TouchInfo {
  163. start_scroll: f32,
  164. start_y: f32,
  165. start_instant: std::time::Instant,
  166. last_y: f32,
  167. }
  168. impl TouchInfo {
  169. fn new() -> Self {
  170. Self { start_scroll: 0., start_y: 0., start_instant: std::time::Instant::now(), last_y: 0. }
  171. }
  172. }
  173. pub type ChatViewPtr = Arc<ChatView>;
  174. pub struct ChatView {
  175. node_id: SceneNodeId,
  176. tasks: Vec<smol::Task<()>>,
  177. sg: SceneGraphPtr2,
  178. render_api: RenderApiPtr,
  179. text_shaper: TextShaperPtr,
  180. tree: sled::Tree,
  181. pages: SyncMutex<Vec<Page>>,
  182. pages2: AsyncMutex<Vec<Page2Ptr>>,
  183. drawcalls: SyncMutex<Vec<DrawMesh>>,
  184. dc_key: u64,
  185. /// Used for detecting when scrolling view
  186. mouse_pos: SyncMutex<Point>,
  187. /// Touch scrolling
  188. touch_info: SyncMutex<TouchInfo>,
  189. rect: PropertyPtr,
  190. scroll: PropertyFloat32,
  191. font_size: PropertyFloat32,
  192. line_height: PropertyFloat32,
  193. baseline: PropertyFloat32,
  194. timestamp_color: PropertyColor,
  195. text_color: PropertyColor,
  196. nick_colors: PropertyPtr,
  197. z_index: PropertyUint32,
  198. }
  199. impl ChatView {
  200. pub async fn new(
  201. ex: Arc<smol::Executor<'static>>,
  202. sg: SceneGraphPtr2,
  203. node_id: SceneNodeId,
  204. render_api: RenderApiPtr,
  205. event_pub: GraphicsEventPublisherPtr,
  206. text_shaper: TextShaperPtr,
  207. tree: sled::Tree,
  208. recvr: async_channel::Receiver<Vec<u8>>,
  209. ) -> Pimpl {
  210. debug!(target: "ui::chatview", "ChatView::new()");
  211. let scene_graph = sg.lock().await;
  212. let node = scene_graph.get_node(node_id).unwrap();
  213. let node_name = node.name.clone();
  214. let rect = node.get_property("rect").expect("ChatView::rect");
  215. let scroll = PropertyFloat32::wrap(node, "scroll", 0).unwrap();
  216. let font_size = PropertyFloat32::wrap(node, "font_size", 0).unwrap();
  217. let line_height = PropertyFloat32::wrap(node, "line_height", 0).unwrap();
  218. let baseline = PropertyFloat32::wrap(node, "baseline", 0).unwrap();
  219. let timestamp_color = PropertyColor::wrap(node, "timestamp_color").unwrap();
  220. let text_color = PropertyColor::wrap(node, "text_color").unwrap();
  221. let nick_colors = node.get_property("nick_colors").expect("ChatView::nick_colors");
  222. let z_index = PropertyUint32::wrap(node, "z_index", 0).unwrap();
  223. drop(scene_graph);
  224. let self_ = Arc::new_cyclic(|me: &Weak<Self>| {
  225. let ev_sub = event_pub.subscribe_mouse_wheel();
  226. let me2 = me.clone();
  227. let mouse_wheel_task =
  228. ex.spawn(async move { while Self::process_mouse_wheel(&me2, &ev_sub).await {} });
  229. let ev_sub = event_pub.subscribe_mouse_move();
  230. let me2 = me.clone();
  231. let mouse_move_task =
  232. ex.spawn(async move { while Self::process_mouse_move(&me2, &ev_sub).await {} });
  233. let ev_sub = event_pub.subscribe_touch();
  234. let me2 = me.clone();
  235. let touch_task =
  236. ex.spawn(async move { while Self::process_touch(&me2, &ev_sub).await {} });
  237. let me2 = me.clone();
  238. let insert_line_method_task =
  239. ex.spawn(
  240. async move { while Self::process_insert_line_method(&me2, &recvr).await {} },
  241. );
  242. let mut on_modify = OnModify::new(ex, node_name, node_id, me.clone());
  243. //on_modify.when_change(scroll.prop(), Self::scrollview);
  244. async fn redraw(self_: Arc<ChatView>) {
  245. self_.redraw().await;
  246. }
  247. on_modify.when_change(rect.clone(), redraw);
  248. let mut tasks =
  249. vec![mouse_wheel_task, mouse_move_task, touch_task, insert_line_method_task];
  250. tasks.append(&mut on_modify.tasks);
  251. Self {
  252. node_id,
  253. tasks,
  254. sg,
  255. render_api,
  256. text_shaper,
  257. tree,
  258. pages: SyncMutex::new(Vec::new()),
  259. pages2: AsyncMutex::new(Vec::new()),
  260. drawcalls: SyncMutex::new(Vec::new()),
  261. dc_key: OsRng.gen(),
  262. mouse_pos: SyncMutex::new(Point::from([0., 0.])),
  263. touch_info: SyncMutex::new(TouchInfo::new()),
  264. rect,
  265. scroll,
  266. font_size,
  267. line_height,
  268. baseline,
  269. timestamp_color,
  270. text_color,
  271. nick_colors,
  272. z_index,
  273. }
  274. });
  275. let timer = std::time::Instant::now();
  276. self_.populate().await;
  277. debug!(target: "ui::chatview", "populate() took {:?}", timer.elapsed());
  278. Pimpl::ChatView(self_)
  279. }
  280. async fn process_mouse_wheel(me: &Weak<Self>, ev_sub: &Subscription<(f32, f32)>) -> bool {
  281. let Ok((wheel_x, wheel_y)) = ev_sub.receive().await else {
  282. debug!(target: "ui::chatview", "Event relayer closed");
  283. return false
  284. };
  285. let Some(self_) = me.upgrade() else {
  286. // Should not happen
  287. panic!("self destroyed before mouse_wheel_task was stopped!");
  288. };
  289. self_.handle_mouse_wheel(wheel_x, wheel_y).await;
  290. true
  291. }
  292. async fn process_mouse_move(me: &Weak<Self>, ev_sub: &Subscription<(f32, f32)>) -> bool {
  293. let Ok((mouse_x, mouse_y)) = ev_sub.receive().await else {
  294. debug!(target: "ui::chatview", "Event relayer closed");
  295. return false
  296. };
  297. let Some(self_) = me.upgrade() else {
  298. // Should not happen
  299. panic!("self destroyed before mouse_move_task was stopped!");
  300. };
  301. self_.handle_mouse_move(mouse_x, mouse_y).await;
  302. true
  303. }
  304. async fn process_touch(
  305. me: &Weak<Self>,
  306. ev_sub: &Subscription<(TouchPhase, u64, f32, f32)>,
  307. ) -> bool {
  308. let Ok((phase, id, touch_x, touch_y)) = ev_sub.receive().await else {
  309. debug!(target: "ui::chatview", "Event relayer closed");
  310. return false
  311. };
  312. let Some(self_) = me.upgrade() else {
  313. // Should not happen
  314. panic!("self destroyed before touch_task was stopped!");
  315. };
  316. self_.handle_touch(phase, id, touch_x, touch_y).await;
  317. true
  318. }
  319. async fn process_insert_line_method(
  320. me: &Weak<Self>,
  321. recvr: &async_channel::Receiver<Vec<u8>>,
  322. ) -> bool {
  323. let Ok(data) = recvr.recv().await else {
  324. debug!(target: "ui::chatview", "Event relayer closed");
  325. return false
  326. };
  327. fn decode_data(data: &[u8]) -> std::io::Result<(u32, String, String)> {
  328. let mut cur = Cursor::new(&data);
  329. let timestamp = u32::decode(&mut cur)?;
  330. let nick = String::decode(&mut cur)?;
  331. let text = String::decode(&mut cur)?;
  332. Ok((timestamp, nick, text))
  333. }
  334. let Ok((timestamp, nick, text)) = decode_data(&data) else {
  335. error!(target: "ui::chatview", "insert_line() method invalid arg data");
  336. return true
  337. };
  338. let Some(self_) = me.upgrade() else {
  339. // Should not happen
  340. panic!("self destroyed before touch_task was stopped!");
  341. };
  342. self_.handle_insert_line(timestamp, nick, text).await;
  343. true
  344. }
  345. async fn handle_mouse_wheel(&self, wheel_x: f32, wheel_y: f32) {
  346. debug!(target: "ui::chatview", "handle_mouse_wheel({wheel_x}, {wheel_y})");
  347. let Some(rect) = self.get_cached_world_rect().await else { return };
  348. let mouse_pos = self.mouse_pos.lock().unwrap().clone();
  349. if !rect.contains(&mouse_pos) {
  350. //debug!(target: "ui::chatview", "not inside rect");
  351. return
  352. }
  353. //debug!(target: "ui::chatview", "inside rect");
  354. let scroll = self.scroll.get() + wheel_y * 50.;
  355. self.scrollview(scroll).await;
  356. }
  357. async fn handle_mouse_move(&self, mouse_x: f32, mouse_y: f32) {
  358. //debug!(target: "ui::chatview", "handle_mouse_move({mouse_x}, {mouse_y})");
  359. let mut mouse_pos = self.mouse_pos.lock().unwrap();
  360. mouse_pos.x = mouse_x;
  361. mouse_pos.y = mouse_y;
  362. }
  363. async fn handle_touch(&self, phase: TouchPhase, id: u64, touch_x: f32, touch_y: f32) {
  364. // Ignore multi-touch
  365. if id != 0 {
  366. return
  367. }
  368. // Simulate mouse events
  369. match phase {
  370. TouchPhase::Started => {
  371. let mut touch_info = self.touch_info.lock().unwrap();
  372. touch_info.start_scroll = self.scroll.get();
  373. touch_info.start_y = touch_y;
  374. touch_info.start_instant = std::time::Instant::now();
  375. touch_info.last_y = touch_y;
  376. }
  377. TouchPhase::Moved => {
  378. let (start_scroll, start_y) = {
  379. let mut touch_info = self.touch_info.lock().unwrap();
  380. touch_info.last_y = touch_y;
  381. (touch_info.start_scroll, touch_info.start_y)
  382. };
  383. let dist = touch_y - start_y;
  384. // TODO the line selected should be fixed and move exactly that distance
  385. // No use of multipliers
  386. // TODO we are maybe doing too many updates so make a widget to 'slow down'
  387. // how often we move to fixed intervals.
  388. // draw a poly shape and eval each line segment.
  389. let scroll = start_scroll + dist;
  390. self.scrollview(scroll).await;
  391. }
  392. TouchPhase::Ended => {
  393. // Now calculate scroll acceleration
  394. }
  395. TouchPhase::Cancelled => {}
  396. }
  397. }
  398. async fn handle_insert_line(&self, timest: u32, nick: String, text: String) {
  399. debug!(target: "ui::chatview", "handle_insert_line({timest}, {nick}, {text})");
  400. let chatmsg = ChatMsg { nick, text };
  401. let timestr = timest.to_string();
  402. // left pad with zeros
  403. let mut timestr = format!("{:0>4}", timestr);
  404. timestr.insert(2, ':');
  405. let text = format!("{} {} {}", timestr, chatmsg.nick, chatmsg.text);
  406. let glyphs = self.text_shaper.shape(text, self.font_size.get()).await;
  407. // Now add message to page
  408. let mut pages = self.pages2.lock().await;
  409. let mut idx = None;
  410. for (i, page) in pages.iter_mut().enumerate() {
  411. let first_timest = page.msgs.first().unwrap().timest;
  412. let last_timest = page.msgs.last().unwrap().timest;
  413. if first_timest <= timest && timest <= last_timest {
  414. idx = Some(i);
  415. }
  416. }
  417. let idx = match idx {
  418. Some(idx) => idx,
  419. None => 0,
  420. };
  421. let page = &mut pages[idx];
  422. let mut msgs = page.msgs.clone();
  423. msgs.push(Message { timest, chatmsg, glyphs });
  424. msgs.sort_unstable_by_key(|msg| msg.timest);
  425. msgs.reverse();
  426. let new_page = Page2::new(msgs, &self.render_api).await;
  427. std::mem::replace(page, new_page);
  428. drop(pages);
  429. // This will refresh the view, so we just use this
  430. let mut scroll = self.scroll.get();
  431. self.scrollview(scroll).await;
  432. }
  433. /// Descent = line height - baseline
  434. fn descent(&self) -> f32 {
  435. self.line_height.get() - self.baseline.get()
  436. }
  437. /// Beware of this method. Here be dragons.
  438. /// Possibly racy so we limit it just to mouse stuff (for now).
  439. fn cached_rect(&self) -> Option<Rectangle> {
  440. let Ok(rect) = read_rect(self.rect.clone()) else {
  441. error!(target: "ui::chatview", "cached_rect is None");
  442. return None
  443. };
  444. Some(rect)
  445. }
  446. async fn get_parent_rect(&self) -> Option<Rectangle> {
  447. let sg = self.sg.lock().await;
  448. let node = sg.get_node(self.node_id).unwrap();
  449. let Some(parent_rect) = get_parent_rect(&sg, node) else {
  450. return None;
  451. };
  452. drop(sg);
  453. Some(parent_rect)
  454. }
  455. async fn get_cached_world_rect(&self) -> Option<Rectangle> {
  456. // NBD if it's slightly wrong
  457. let mut rect = self.cached_rect()?;
  458. // If layers can be nested and we use offsets for (x, y)
  459. // then this will be incorrect for nested layers.
  460. // For now we don't allow nesting of layers.
  461. let parent_rect = self.get_parent_rect().await?;
  462. // Offset rect which is now in world coords
  463. rect.x += parent_rect.x;
  464. rect.y += parent_rect.y;
  465. Some(rect)
  466. }
  467. async fn populate(&self) {
  468. let iter = self.tree.iter().rev();
  469. self.load_n_pages(iter, PRELOAD_PAGES).await;
  470. }
  471. /// Load extra pages
  472. async fn preload_pages(&self) -> usize {
  473. // Get last page
  474. let last_page = self.pages2.lock().await.last().unwrap().clone();
  475. // get the current earliest timestamp
  476. let last_timest = last_page.msgs.last().unwrap().timest;
  477. // iterate from there
  478. let key = last_timest.to_be_bytes();
  479. debug!(target: "ui::chatview", "preloading from {key:?}");
  480. let iter = self.tree.range(..key).rev();
  481. self.load_n_pages(iter, PRELOAD_PAGES).await
  482. }
  483. async fn load_n_pages<I: Iterator<Item = sled::Result<(sled::IVec, sled::IVec)>>>(
  484. &self,
  485. iter: I,
  486. n: usize,
  487. ) -> usize {
  488. let mut pages_len = 0;
  489. let mut msgs = vec![];
  490. for entry in iter {
  491. let Ok((k, v)) = entry else { break };
  492. assert_eq!(k.len(), 4);
  493. let key_bytes: [u8; 4] = k.as_ref().try_into().unwrap();
  494. let timest = Timestamp::from_be_bytes(key_bytes);
  495. let chatmsg: ChatMsg = deserialize(&v).unwrap();
  496. debug!(target: "ui::chatview", "{k:?} {chatmsg:?}");
  497. let timestr = timest.to_string();
  498. // left pad with zeros
  499. let mut timestr = format!("{:0>4}", timestr);
  500. timestr.insert(2, ':');
  501. let text = format!("{} {} {}", timestr, chatmsg.nick, chatmsg.text);
  502. let glyphs = self.text_shaper.shape(text, self.font_size.get()).await;
  503. msgs.push(Message { timest, chatmsg, glyphs });
  504. if msgs.len() >= LINES_PER_PAGE {
  505. let msgs = std::mem::take(&mut msgs);
  506. let page = Page2::new(msgs, &self.render_api).await;
  507. self.pages2.lock().await.push(page);
  508. pages_len += 1;
  509. if pages_len >= n {
  510. break
  511. }
  512. }
  513. }
  514. // Any remaining messages added to a short page
  515. if !msgs.is_empty() {
  516. let page = Page2::new(msgs, &self.render_api).await;
  517. self.pages2.lock().await.push(page);
  518. pages_len += 1;
  519. }
  520. debug!(target: "ui::chatview", "populated {} pages", pages_len);
  521. pages_len
  522. }
  523. async fn get_total_height(&self, rect: &Rectangle, pages: &Vec<Page2Ptr>) -> f32 {
  524. let font_size = self.font_size.get();
  525. let line_height = self.line_height.get();
  526. let baseline = self.baseline.get();
  527. let timest_color = self.timestamp_color.get();
  528. let text_color = self.text_color.get();
  529. let nick_colors = self.read_nick_colors();
  530. // Nudge the bottom line up slightly, otherwise chars like p will cross the bottom.
  531. let mut current_height = self.descent();
  532. for page in pages {
  533. let mesh_inf = page.mesh_inf.lock().unwrap().clone();
  534. let mesh_inf = match mesh_inf {
  535. Some(mesh_inf) => mesh_inf,
  536. None => {
  537. let (mesh_inf, old_drawmesh) = page
  538. .regen_mesh(
  539. &rect,
  540. &self.render_api,
  541. font_size,
  542. line_height,
  543. baseline,
  544. &nick_colors,
  545. timest_color.clone(),
  546. text_color.clone(),
  547. )
  548. .await;
  549. assert!(old_drawmesh.is_none());
  550. mesh_inf
  551. }
  552. };
  553. current_height += mesh_inf.px_height;
  554. }
  555. current_height
  556. }
  557. async fn draw_cached(&self, mut rect: Rectangle, scroll: &mut f32) -> Vec<DrawInstruction> {
  558. let mut instrs = vec![];
  559. if *scroll < 0. {
  560. *scroll = 0.;
  561. }
  562. // Make sure we have enough pages loaded.
  563. // If there's no more to load then adjust the scroll.
  564. let mut pages = self.pages2.lock().await.clone();
  565. while self.get_total_height(&rect, &pages).await < *scroll + rect.h {
  566. debug!(target: "ui::chatview", "draw_cached() loading more pages");
  567. if self.preload_pages().await == 0 {
  568. // No more pages available to load
  569. pages = self.pages2.lock().await.clone();
  570. let new_height = self.get_total_height(&rect, &pages).await;
  571. *scroll = new_height - rect.h;
  572. break
  573. }
  574. }
  575. let descent = self.descent();
  576. let mut current_height = 0.;
  577. for page in pages {
  578. if current_height > *scroll + rect.h {
  579. break
  580. }
  581. let mesh_inf = page.mesh_inf.lock().unwrap().clone();
  582. let mesh_inf = mesh_inf.expect("preload above should've regen_mesh()");
  583. // Apply scroll and scissor
  584. // We use the scissor for scrolling
  585. // Because we use the scissor, our actual rect is now rect instead of parent_rect
  586. let off_x = 0.;
  587. // This calc decides whether scroll is in terms of pages or pixels
  588. let off_y = (*scroll - current_height + rect.h) / rect.h;
  589. let scale_x = 1. / rect.w;
  590. let scale_y = 1. / rect.h;
  591. let model = glam::Mat4::from_translation(glam::Vec3::new(off_x, off_y, 0.)) *
  592. glam::Mat4::from_scale(glam::Vec3::new(scale_x, scale_y, 1.));
  593. instrs.push(DrawInstruction::ApplyMatrix(model));
  594. instrs.push(DrawInstruction::Draw(mesh_inf.mesh));
  595. current_height += mesh_inf.px_height;
  596. }
  597. instrs
  598. }
  599. /// Basically a version of redraw() where regen_mesh() is never called.
  600. /// Instead we use the cached version.
  601. async fn scrollview(&self, mut scroll: f32) {
  602. debug!(target: "ui::chatview", "scrollview()");
  603. let sg = self.sg.lock().await;
  604. let node = sg.get_node(self.node_id).unwrap();
  605. let Some(parent_rect) = get_parent_rect(&sg, node) else {
  606. return;
  607. };
  608. if let Err(err) = eval_rect(self.rect.clone(), &parent_rect) {
  609. panic!("Node {:?} bad rect property: {}", node, err);
  610. }
  611. let Ok(mut rect) = read_rect(self.rect.clone()) else {
  612. panic!("Node {:?} bad rect property", node);
  613. };
  614. let mut mesh_instrs = self.draw_cached(rect.clone(), &mut scroll).await;
  615. let mut instrs = vec![DrawInstruction::ApplyViewport(rect)];
  616. instrs.append(&mut mesh_instrs);
  617. let draw_calls =
  618. vec![(self.dc_key, DrawCall { instrs, dcs: vec![], z_index: self.z_index.get() })];
  619. self.render_api.replace_draw_calls(draw_calls).await;
  620. self.scroll.set(scroll);
  621. }
  622. async fn redraw(&self) {
  623. debug!(target: "ui::chatview", "redraw()");
  624. let sg = self.sg.lock().await;
  625. let node = sg.get_node(self.node_id).unwrap();
  626. let Some(parent_rect) = get_parent_rect(&sg, node) else {
  627. return;
  628. };
  629. let Some(draw_update) = self.draw(&sg, &parent_rect).await else {
  630. error!(target: "ui::chatview", "ChatView {:?} failed to draw", node);
  631. return;
  632. };
  633. self.render_api.replace_draw_calls(draw_update.draw_calls).await;
  634. debug!(target: "ui::chatview", "replace draw calls done");
  635. for buffer_id in draw_update.freed_buffers {
  636. self.render_api.delete_buffer(buffer_id);
  637. }
  638. for texture_id in draw_update.freed_textures {
  639. self.render_api.delete_texture(texture_id);
  640. }
  641. }
  642. async fn regen_mesh(&self, mut rect: Rectangle) -> (Vec<DrawInstruction>, Vec<DrawMesh>) {
  643. let font_size = self.font_size.get();
  644. let line_height = self.line_height.get();
  645. let baseline = self.baseline.get();
  646. let descent = self.descent();
  647. let mut instrs = vec![];
  648. let mut old_drawmesh = vec![];
  649. let timest_color = self.timestamp_color.get();
  650. let text_color = self.text_color.get();
  651. let nick_colors = self.read_nick_colors();
  652. let pages = self.pages2.lock().await.clone();
  653. let mut mesh_infs = vec![];
  654. // First pass is to measure the height and generate the meshes
  655. let mut current_height = 0.;
  656. for page in pages {
  657. // We should be able to count lines and perform wrapping without having to
  658. // generate the mesh and alloc buffers.
  659. // We need to separate both these ops.
  660. let (mesh_inf, old) = page
  661. .regen_mesh(
  662. &rect,
  663. &self.render_api,
  664. font_size,
  665. line_height,
  666. baseline,
  667. &nick_colors,
  668. timest_color.clone(),
  669. text_color.clone(),
  670. )
  671. .await;
  672. current_height += mesh_inf.px_height;
  673. if let Some(old) = old {
  674. old_drawmesh.push(old);
  675. }
  676. mesh_infs.push(mesh_inf);
  677. }
  678. let total_height = current_height + descent;
  679. // If lines aren't enough to fill the available buffer then start from the top
  680. let start_pos = if total_height < rect.h { total_height } else { rect.h };
  681. let mut scroll = self.scroll.get();
  682. assert!(scroll >= 0.);
  683. // For when we resize the window and scroll is no longer valid
  684. let max_allowed_scroll = total_height - rect.h;
  685. debug!(
  686. "max_allowed_scroll = {max_allowed_scroll} = total_height={total_height} - rect.h={}",
  687. rect.h
  688. );
  689. if scroll > max_allowed_scroll {
  690. scroll = max_allowed_scroll;
  691. self.scroll.set(scroll);
  692. }
  693. let mut current_height = 0.;
  694. for mesh_inf in mesh_infs {
  695. if current_height > scroll + rect.h {
  696. break
  697. }
  698. // Apply scroll and scissor
  699. // We use the scissor for scrolling
  700. // Because we use the scissor, our actual rect is now rect instead of parent_rect
  701. let off_x = 0.;
  702. // This calc decides whether scroll is in terms of pages or pixels
  703. let off_y = (scroll + start_pos - current_height) / rect.h;
  704. let scale_x = 1. / rect.w;
  705. let scale_y = 1. / rect.h;
  706. let model = glam::Mat4::from_translation(glam::Vec3::new(off_x, off_y, 0.)) *
  707. glam::Mat4::from_scale(glam::Vec3::new(scale_x, scale_y, 1.));
  708. instrs.push(DrawInstruction::ApplyMatrix(model));
  709. instrs.push(DrawInstruction::Draw(mesh_inf.mesh));
  710. current_height += mesh_inf.px_height;
  711. }
  712. (instrs, old_drawmesh)
  713. /*
  714. if DEBUG_RENDER {
  715. let mut debug_mesh = MeshBuilder::new();
  716. debug_mesh.draw_outline(
  717. &Rectangle { x: 0., y: -clip.h, w: clip.w, h: clip.h },
  718. COLOR_BLUE,
  719. 2.,
  720. );
  721. let mesh = debug_mesh.alloc(&self.render_api).await.unwrap();
  722. draws.push(DrawMesh {
  723. vertex_buffer: mesh.vertex_buffer,
  724. index_buffer: mesh.index_buffer,
  725. texture: None,
  726. num_elements: mesh.num_elements,
  727. });
  728. }
  729. draws
  730. */
  731. }
  732. fn read_nick_colors(&self) -> Vec<Color> {
  733. let mut colors = vec![];
  734. let mut color = [0f32; 4];
  735. for i in 0..self.nick_colors.get_len() {
  736. color[i % 4] = self.nick_colors.get_f32(i).expect("prop logic err");
  737. if i > 0 && i % 4 == 0 {
  738. let color = std::mem::take(&mut color);
  739. colors.push(color);
  740. }
  741. }
  742. colors
  743. }
  744. fn select_nick_color(&self, nick: &str, nick_colors: &[Color]) -> Color {
  745. let mut hasher = DefaultHasher::new();
  746. nick.hash(&mut hasher);
  747. let i = hasher.finish() as usize;
  748. let color = nick_colors[i % nick_colors.len()];
  749. color
  750. }
  751. pub async fn draw(&self, sg: &SceneGraph, parent_rect: &Rectangle) -> Option<DrawUpdate> {
  752. debug!(target: "ui::chatview", "ChatView::draw()");
  753. // Only used for debug messages
  754. let node = sg.get_node(self.node_id).unwrap();
  755. if let Err(err) = eval_rect(self.rect.clone(), parent_rect) {
  756. panic!("Node {:?} bad rect property: {}", node, err);
  757. }
  758. let Ok(mut rect) = read_rect(self.rect.clone()) else {
  759. panic!("Node {:?} bad rect property", node);
  760. };
  761. let timer = std::time::Instant::now();
  762. let (mut mesh_instrs, mut old_drawmesh) = self.regen_mesh(rect.clone()).await;
  763. debug!(target: "ui::chatview", "regen_mesh() took {:?}", timer.elapsed());
  764. let mut freed_textures = vec![];
  765. let mut freed_buffers = vec![];
  766. for old_mesh in old_drawmesh {
  767. freed_buffers.push(old_mesh.vertex_buffer);
  768. freed_buffers.push(old_mesh.index_buffer);
  769. //if let Some(texture_id) = old_dc.texture {
  770. // freed_textures.push(texture_id);
  771. //}
  772. }
  773. debug!(target: "ui::chatview", "chatview rect = {:?}", rect);
  774. let mut instrs = vec![DrawInstruction::ApplyViewport(rect)];
  775. instrs.append(&mut mesh_instrs);
  776. Some(DrawUpdate {
  777. key: self.dc_key,
  778. draw_calls: vec![(
  779. self.dc_key,
  780. DrawCall { instrs, dcs: vec![], z_index: self.z_index.get() },
  781. )],
  782. freed_textures,
  783. freed_buffers,
  784. })
  785. }
  786. }