mod.rs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879
  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 atomic_float::AtomicF32;
  20. use chrono::{Local, TimeZone};
  21. use darkfi::system::{msleep, CondVar};
  22. use darkfi_serial::{
  23. async_trait, deserialize, Decodable, Encodable, SerialDecodable, SerialEncodable,
  24. };
  25. use miniquad::{KeyCode, KeyMods, TouchPhase};
  26. use rand::{rngs::OsRng, Rng};
  27. use sled_overlay::sled;
  28. use std::{
  29. hash::{DefaultHasher, Hash, Hasher},
  30. io::Cursor,
  31. sync::{
  32. atomic::{AtomicBool, Ordering},
  33. Arc, Mutex as SyncMutex, Weak,
  34. },
  35. };
  36. mod page;
  37. use page::{FreedData, MessageBuffer};
  38. use crate::{
  39. gfx::{
  40. DrawCall, DrawInstruction, DrawMesh, GraphicsEventPublisherPtr, Point, Rectangle,
  41. RenderApi, RenderApiPtr,
  42. },
  43. mesh::{Color, MeshBuilder, COLOR_BLUE, COLOR_GREEN},
  44. prop::{PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr, PropertyUint32, Role},
  45. pubsub::Subscription,
  46. ringbuf::RingBuffer,
  47. scene::{Pimpl, SceneGraph, SceneGraphPtr2, SceneNodeId},
  48. text::{self, Glyph, GlyphPositionIter, TextShaperPtr},
  49. util::{enumerate, is_whitespace},
  50. ExecutorPtr,
  51. };
  52. use super::{eval_rect, get_parent_rect, read_rect, DrawUpdate, OnModify};
  53. const EPSILON: f32 = 0.001;
  54. const BIG_EPSILON: f32 = 0.05;
  55. fn is_zero(x: f32) -> bool {
  56. x.abs() < EPSILON
  57. }
  58. /// std::cmp::max() doesn't work on f32
  59. fn max(a: f32, b: f32) -> f32 {
  60. if a > b {
  61. a
  62. } else {
  63. b
  64. }
  65. }
  66. #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
  67. pub struct ChatMsg {
  68. pub nick: String,
  69. pub text: String,
  70. }
  71. type Timestamp = u64;
  72. type MessageId = [u8; 32];
  73. const PRELOAD_PAGES: usize = 1;
  74. #[derive(Clone)]
  75. struct TouchInfo {
  76. start_scroll: f32,
  77. start_y: f32,
  78. start_instant: std::time::Instant,
  79. /// Used for flick scrolling
  80. samples: RingBuffer<(std::time::Instant, f32), 2>,
  81. last_instant: std::time::Instant,
  82. last_y: f32,
  83. }
  84. impl TouchInfo {
  85. fn new() -> Self {
  86. Self {
  87. start_scroll: 0.,
  88. start_y: 0.,
  89. start_instant: std::time::Instant::now(),
  90. samples: RingBuffer::new(),
  91. last_instant: std::time::Instant::now(),
  92. last_y: 0.,
  93. }
  94. }
  95. }
  96. pub type ChatViewPtr = Arc<ChatView>;
  97. pub struct ChatView {
  98. node_id: SceneNodeId,
  99. #[allow(dead_code)]
  100. tasks: Vec<smol::Task<()>>,
  101. sg: SceneGraphPtr2,
  102. render_api: RenderApiPtr,
  103. text_shaper: TextShaperPtr,
  104. tree: sled::Tree,
  105. msgbuf: AsyncMutex<MessageBuffer>,
  106. dc_key: u64,
  107. /// Used for detecting when scrolling view
  108. mouse_pos: SyncMutex<Point>,
  109. /// Touch scrolling
  110. touch_info: SyncMutex<TouchInfo>,
  111. touch_is_active: AtomicBool,
  112. touch_is_updating: AtomicBool,
  113. rect: PropertyPtr,
  114. scroll: PropertyFloat32,
  115. font_size: PropertyFloat32,
  116. line_height: PropertyFloat32,
  117. baseline: PropertyFloat32,
  118. timestamp_color: PropertyColor,
  119. text_color: PropertyColor,
  120. nick_colors: PropertyPtr,
  121. z_index: PropertyUint32,
  122. debug: PropertyBool,
  123. mouse_scroll_start_accel: PropertyFloat32,
  124. mouse_scroll_decel: PropertyFloat32,
  125. mouse_scroll_resist: PropertyFloat32,
  126. // Scroll accel
  127. motion_cv: Arc<CondVar>,
  128. speed: AtomicF32,
  129. // Triggers the background loading task to wake up
  130. bgload_cv: Arc<CondVar>,
  131. /// Used for correct converting input event pos from screen to widget space.
  132. /// We also use it when we re-eval rect when its changed via property.
  133. parent_rect: SyncMutex<Option<Rectangle>>,
  134. }
  135. impl ChatView {
  136. pub async fn new(
  137. ex: ExecutorPtr,
  138. sg: SceneGraphPtr2,
  139. node_id: SceneNodeId,
  140. render_api: RenderApiPtr,
  141. event_pub: GraphicsEventPublisherPtr,
  142. text_shaper: TextShaperPtr,
  143. tree: sled::Tree,
  144. recvr: async_channel::Receiver<Vec<u8>>,
  145. ) -> Pimpl {
  146. debug!(target: "ui::chatview", "ChatView::new()");
  147. let scene_graph = sg.lock().await;
  148. let node = scene_graph.get_node(node_id).unwrap();
  149. let node_name = node.name.clone();
  150. let rect = node.get_property("rect").expect("ChatView::rect");
  151. let scroll = PropertyFloat32::wrap(node, Role::Internal, "scroll", 0).unwrap();
  152. let font_size = PropertyFloat32::wrap(node, Role::Internal, "font_size", 0).unwrap();
  153. let line_height = PropertyFloat32::wrap(node, Role::Internal, "line_height", 0).unwrap();
  154. let baseline = PropertyFloat32::wrap(node, Role::Internal, "baseline", 0).unwrap();
  155. let timestamp_color = PropertyColor::wrap(node, Role::Internal, "timestamp_color").unwrap();
  156. let text_color = PropertyColor::wrap(node, Role::Internal, "text_color").unwrap();
  157. let nick_colors = node.get_property("nick_colors").expect("ChatView::nick_colors");
  158. let z_index = PropertyUint32::wrap(node, Role::Internal, "z_index", 0).unwrap();
  159. let debug = PropertyBool::wrap(node, Role::Internal, "debug", 0).unwrap();
  160. let mouse_scroll_start_accel =
  161. PropertyFloat32::wrap(node, Role::Internal, "mouse_scroll_start_accel", 0).unwrap();
  162. let mouse_scroll_decel =
  163. PropertyFloat32::wrap(node, Role::Internal, "mouse_scroll_decel", 0).unwrap();
  164. let mouse_scroll_resist =
  165. PropertyFloat32::wrap(node, Role::Internal, "mouse_scroll_resist", 0).unwrap();
  166. drop(scene_graph);
  167. let self_ = Arc::new_cyclic(|me: &Weak<Self>| {
  168. let ev_sub = event_pub.subscribe_mouse_wheel();
  169. let me2 = me.clone();
  170. let mouse_wheel_task =
  171. ex.spawn(async move { while Self::process_mouse_wheel(&me2, &ev_sub).await {} });
  172. let ev_sub = event_pub.subscribe_mouse_move();
  173. let me2 = me.clone();
  174. let mouse_move_task =
  175. ex.spawn(async move { while Self::process_mouse_move(&me2, &ev_sub).await {} });
  176. let ev_sub = event_pub.subscribe_touch();
  177. let me2 = me.clone();
  178. let touch_task =
  179. ex.spawn(async move { while Self::process_touch(&me2, &ev_sub).await {} });
  180. let ev_sub = event_pub.subscribe_key_down();
  181. let me2 = me.clone();
  182. let key_down_task =
  183. ex.spawn(async move { while Self::process_key_down(&me2, &ev_sub).await {} });
  184. let me2 = me.clone();
  185. let insert_line_method_task =
  186. ex.spawn(
  187. async move { while Self::process_insert_line_method(&me2, &recvr).await {} },
  188. );
  189. let me2 = me.clone();
  190. let motion_cv = Arc::new(CondVar::new());
  191. let cv = motion_cv.clone();
  192. let motion_task = ex.spawn(async move {
  193. loop {
  194. cv.wait().await;
  195. let Some(self_) = me2.upgrade() else {
  196. // Should not happen
  197. panic!("self destroyed before motion_task was stopped!");
  198. };
  199. self_.handle_movement().await;
  200. cv.reset();
  201. }
  202. });
  203. let me2 = me.clone();
  204. let bgload_cv = Arc::new(CondVar::new());
  205. let cv = bgload_cv.clone();
  206. let bgload_task = ex.spawn(async move {
  207. loop {
  208. cv.wait().await;
  209. let Some(self_) = me2.upgrade() else {
  210. // Should not happen
  211. panic!("self destroyed before bgload_task was stopped!");
  212. };
  213. self_.handle_bgload().await;
  214. cv.reset();
  215. }
  216. });
  217. let mut on_modify = OnModify::new(ex, node_name, node_id, me.clone());
  218. async fn reload_view(self_: Arc<ChatView>) {
  219. self_.scrollview(self_.scroll.get()).await;
  220. }
  221. on_modify.when_change(scroll.prop(), reload_view);
  222. //async fn redraw(self_: Arc<ChatView>) {
  223. // self_.redraw().await;
  224. //}
  225. //on_modify.when_change(rect.clone(), redraw);
  226. //on_modify.when_change(debug.prop(), redraw);
  227. let mut tasks = vec![
  228. mouse_wheel_task,
  229. mouse_move_task,
  230. touch_task,
  231. key_down_task,
  232. insert_line_method_task,
  233. motion_task,
  234. bgload_task,
  235. ];
  236. tasks.append(&mut on_modify.tasks);
  237. Self {
  238. node_id,
  239. tasks,
  240. sg,
  241. render_api: render_api.clone(),
  242. text_shaper: text_shaper.clone(),
  243. tree,
  244. msgbuf: AsyncMutex::new(MessageBuffer::new(
  245. font_size.clone(),
  246. line_height.clone(),
  247. baseline.clone(),
  248. timestamp_color.clone(),
  249. text_color.clone(),
  250. nick_colors.clone(),
  251. debug.clone(),
  252. render_api,
  253. text_shaper,
  254. )),
  255. dc_key: OsRng.gen(),
  256. mouse_pos: SyncMutex::new(Point::from([0., 0.])),
  257. touch_info: SyncMutex::new(TouchInfo::new()),
  258. touch_is_active: AtomicBool::new(false),
  259. touch_is_updating: AtomicBool::new(false),
  260. rect,
  261. scroll,
  262. font_size,
  263. line_height,
  264. baseline,
  265. timestamp_color,
  266. text_color,
  267. nick_colors,
  268. z_index,
  269. debug,
  270. mouse_scroll_start_accel,
  271. mouse_scroll_decel,
  272. mouse_scroll_resist,
  273. motion_cv,
  274. speed: AtomicF32::new(0.),
  275. bgload_cv,
  276. parent_rect: SyncMutex::new(None),
  277. }
  278. });
  279. Pimpl::ChatView(self_)
  280. }
  281. async fn process_mouse_wheel(me: &Weak<Self>, ev_sub: &Subscription<(f32, f32)>) -> bool {
  282. let Ok((wheel_x, wheel_y)) = ev_sub.receive().await else {
  283. debug!(target: "ui::chatview", "Event relayer closed");
  284. return false
  285. };
  286. let Some(self_) = me.upgrade() else {
  287. // Should not happen
  288. panic!("self destroyed before mouse_wheel_task was stopped!");
  289. };
  290. self_.handle_mouse_wheel(wheel_x, wheel_y).await;
  291. true
  292. }
  293. async fn process_mouse_move(me: &Weak<Self>, ev_sub: &Subscription<(f32, f32)>) -> bool {
  294. let Ok((mouse_x, mouse_y)) = ev_sub.receive().await else {
  295. debug!(target: "ui::chatview", "Event relayer closed");
  296. return false
  297. };
  298. let Some(self_) = me.upgrade() else {
  299. // Should not happen
  300. panic!("self destroyed before mouse_move_task was stopped!");
  301. };
  302. self_.handle_mouse_move(mouse_x, mouse_y).await;
  303. true
  304. }
  305. async fn process_touch(
  306. me: &Weak<Self>,
  307. ev_sub: &Subscription<(TouchPhase, u64, f32, f32)>,
  308. ) -> bool {
  309. let Ok((phase, id, touch_x, touch_y)) = ev_sub.receive().await else {
  310. debug!(target: "ui::chatview", "Event relayer closed");
  311. return false
  312. };
  313. let Some(self_) = me.upgrade() else {
  314. // Should not happen
  315. panic!("self destroyed before touch_task was stopped!");
  316. };
  317. self_.handle_touch(phase, id, touch_x, touch_y).await;
  318. true
  319. }
  320. async fn process_key_down(
  321. me: &Weak<Self>,
  322. ev_sub: &Subscription<(KeyCode, KeyMods, bool)>,
  323. ) -> bool {
  324. let Ok((key, _mods, repeat)) = ev_sub.receive().await else {
  325. debug!(target: "ui::editbox", "Event relayer closed");
  326. return false
  327. };
  328. if repeat {
  329. return true
  330. }
  331. let Some(self_) = me.upgrade() else {
  332. // Should not happen
  333. panic!("self destroyed before char_task was stopped!");
  334. };
  335. match key {
  336. KeyCode::PageUp => {
  337. let scroll = self_.scroll.get() + 200.;
  338. self_.scrollview(scroll).await;
  339. }
  340. KeyCode::PageDown => {
  341. let scroll = self_.scroll.get() - 200.;
  342. self_.scrollview(scroll).await;
  343. }
  344. _ => {}
  345. }
  346. true
  347. }
  348. async fn process_insert_line_method(
  349. me: &Weak<Self>,
  350. recvr: &async_channel::Receiver<Vec<u8>>,
  351. ) -> bool {
  352. let Ok(data) = recvr.recv().await else {
  353. debug!(target: "ui::chatview", "Event relayer closed");
  354. return false
  355. };
  356. fn decode_data(data: &[u8]) -> std::io::Result<(Timestamp, MessageId, String, String)> {
  357. let mut cur = Cursor::new(&data);
  358. let timestamp = Timestamp::decode(&mut cur)?;
  359. let message_id = MessageId::decode(&mut cur)?;
  360. let nick = String::decode(&mut cur)?;
  361. let text = String::decode(&mut cur)?;
  362. Ok((timestamp, message_id, nick, text))
  363. }
  364. let Ok((timestamp, message_id, nick, text)) = decode_data(&data) else {
  365. error!(target: "ui::chatview", "insert_line() method invalid arg data");
  366. return true
  367. };
  368. let Some(self_) = me.upgrade() else {
  369. // Should not happen
  370. panic!("self destroyed before touch_task was stopped!");
  371. };
  372. self_.handle_insert_line(timestamp, message_id, nick, text).await;
  373. true
  374. }
  375. async fn handle_mouse_wheel(&self, wheel_x: f32, wheel_y: f32) {
  376. //debug!(target: "ui::chatview", "handle_mouse_wheel({wheel_x}, {wheel_y})");
  377. let Some(rect) = self.parent_rect.lock().unwrap().clone() else { return };
  378. let mouse_pos = self.mouse_pos.lock().unwrap().clone();
  379. if !rect.contains(&mouse_pos) {
  380. //debug!(target: "ui::chatview", "not inside rect");
  381. return
  382. }
  383. self.speed.fetch_add(wheel_y * self.mouse_scroll_start_accel.get(), Ordering::Relaxed);
  384. self.motion_cv.notify();
  385. }
  386. async fn handle_mouse_move(&self, mouse_x: f32, mouse_y: f32) {
  387. //debug!(target: "ui::chatview", "handle_mouse_move({mouse_x}, {mouse_y})");
  388. let mut mouse_pos = self.mouse_pos.lock().unwrap();
  389. mouse_pos.x = mouse_x;
  390. mouse_pos.y = mouse_y;
  391. }
  392. async fn handle_touch(&self, phase: TouchPhase, id: u64, touch_x: f32, touch_y: f32) {
  393. // Ignore multi-touch
  394. if id != 0 {
  395. return
  396. }
  397. let Some(rect) = self.parent_rect.lock().unwrap().clone() else { return };
  398. let touch_pos = Point { x: touch_x, y: touch_y };
  399. if !rect.contains(&touch_pos) {
  400. //debug!(target: "ui::chatview", "not inside rect");
  401. return
  402. }
  403. // Simulate mouse events
  404. match phase {
  405. TouchPhase::Started => {
  406. self.touch_is_active.store(true, Ordering::Relaxed);
  407. let mut touch_info = self.touch_info.lock().unwrap();
  408. touch_info.start_scroll = self.scroll.get();
  409. touch_info.start_y = touch_y;
  410. touch_info.start_instant = std::time::Instant::now();
  411. touch_info.samples = RingBuffer::new();
  412. touch_info.samples.push((std::time::Instant::now(), touch_y));
  413. touch_info.last_instant = std::time::Instant::now();
  414. touch_info.last_y = touch_y;
  415. }
  416. TouchPhase::Moved => {
  417. if !self.touch_is_updating.fetch_or(true, Ordering::Relaxed) {
  418. return
  419. }
  420. let (start_scroll, start_y, start_elapsed, do_update) = {
  421. let mut touch_info = self.touch_info.lock().unwrap();
  422. touch_info.last_y = touch_y;
  423. let start_elapsed = touch_info.start_instant.elapsed().as_millis_f32();
  424. let last_sample = touch_info.samples.head().unwrap().0.elapsed().as_millis_f32();
  425. // Sample every 40ms
  426. // Average small touch time is 80ms, sometimes 40ms
  427. // A longer sample time means a more accurate reading for the exit velocity.
  428. if last_sample > 40. {
  429. touch_info.samples.push((std::time::Instant::now(), touch_y));
  430. }
  431. let last_elapsed = touch_info.last_instant.elapsed().as_millis_f32();
  432. let do_update = last_elapsed > 20.;
  433. if do_update {
  434. touch_info.last_instant = std::time::Instant::now();
  435. }
  436. let start_scroll = touch_info.start_scroll;
  437. let start_y = touch_info.start_y;
  438. (start_scroll, start_y, start_elapsed, do_update)
  439. };
  440. if start_elapsed > 200. {
  441. debug!(target: "ui::chatview", "Stopping scroll accel");
  442. self.speed.store(0., Ordering::Relaxed);
  443. }
  444. let dist = touch_y - start_y;
  445. // TODO the line selected should be fixed and move exactly that distance
  446. // No use of multipliers
  447. // TODO we are maybe doing too many updates so make a widget to 'slow down'
  448. // how often we move to fixed intervals.
  449. // draw a poly shape and eval each line segment.
  450. let scroll = start_scroll + dist;
  451. if do_update {
  452. self.scrollview(scroll).await;
  453. }
  454. self.touch_is_updating.store(true, Ordering::Relaxed);
  455. }
  456. TouchPhase::Ended => {
  457. self.touch_is_active.store(false, Ordering::Relaxed);
  458. // Now calculate scroll acceleration
  459. let touch_info = self.touch_info.lock().unwrap().clone();
  460. let (oldest_sample, sample_y) = touch_info.samples.head().unwrap();
  461. //let Some((oldest_sample, sample_y)) = touch_info.samples.head() else { return };
  462. let time = oldest_sample.elapsed().as_millis_f32();
  463. let dist = touch_y - sample_y;
  464. // Ignore sub-ms events
  465. if time < 1. {
  466. return
  467. }
  468. //let speed = dist / time;
  469. //self.speed.fetch_add(speed, Ordering::Relaxed);
  470. //debug!(target: "ui::chatview", "speed = {dist} / {time} = {speed}");
  471. let accel = self.mouse_scroll_start_accel.get() * dist / time;
  472. let touch_time = touch_info.start_instant.elapsed();
  473. debug!(target: "ui::chatview", "accel = {dist} / {time} = {accel}, touch = {touch_time:?}");
  474. self.speed.fetch_add(accel, Ordering::Relaxed);
  475. self.motion_cv.notify();
  476. }
  477. TouchPhase::Cancelled => {}
  478. }
  479. }
  480. async fn add_line_to_db(
  481. &self,
  482. timest: Timestamp,
  483. message_id: &MessageId,
  484. nick: &str,
  485. text: &str,
  486. ) -> bool {
  487. let timest = timest.to_be_bytes();
  488. assert_eq!(timest.len(), 8);
  489. let mut key = [0u8; 8 + 32];
  490. key[..8].clone_from_slice(&timest);
  491. key[8..].clone_from_slice(message_id);
  492. // When does this return Err?
  493. let contains_key = self.tree.contains_key(&key);
  494. if contains_key.is_err() || contains_key.unwrap() {
  495. // Already exists
  496. return false
  497. }
  498. let msg = ChatMsg { nick: nick.to_string(), text: text.to_string() };
  499. let mut val = vec![];
  500. msg.encode(&mut val).unwrap();
  501. self.tree.insert(&key, val).unwrap();
  502. let _ = self.tree.flush_async().await;
  503. true
  504. }
  505. async fn handle_insert_line(
  506. &self,
  507. timest: Timestamp,
  508. message_id: MessageId,
  509. nick: String,
  510. text: String,
  511. ) {
  512. debug!(target: "ui::chatview", "handle_insert_line({timest}, {message_id:?}, {nick}, {text})");
  513. if !self.add_line_to_db(timest, &message_id, &nick, &text).await {
  514. // Already exists so bail
  515. debug!(target: "ui::chatview", "duplicate msg so bailing");
  516. return
  517. }
  518. // Add message to page
  519. let mut msgbuf = self.msgbuf.lock().await;
  520. msgbuf.insert_privmsg(timest, message_id, nick, text).await;
  521. self.redraw_cached(&mut msgbuf).await;
  522. self.bgload_cv.notify();
  523. }
  524. async fn handle_movement(&self) {
  525. // We need to fix this impl because it depends very much on the speed of the device
  526. // that it's running on.
  527. // Look into optimizing scrollview() so scrolling is smooth.
  528. // We could use skiplists to avoid looping from the very bottom.
  529. // So index 1 in the skiplist advances to 100px up... (or however much multiplier)
  530. loop {
  531. msleep(10).await;
  532. if self.touch_is_active.load(Ordering::Relaxed) {
  533. return
  534. }
  535. let mut speed = self.speed.load(Ordering::Relaxed);
  536. // Apply constant decel to speed
  537. speed *= self.mouse_scroll_resist.get();
  538. if speed.abs() < BIG_EPSILON {
  539. speed = 0.;
  540. }
  541. self.speed.store(speed, Ordering::Relaxed);
  542. // Finished
  543. if is_zero(speed) {
  544. return
  545. }
  546. let scroll = self.scroll.get() + speed;
  547. let dist = self.scrollview(scroll).await;
  548. // We reached the end so just stop
  549. if is_zero(dist) {
  550. self.speed.store(0., Ordering::Relaxed);
  551. return
  552. }
  553. }
  554. }
  555. async fn handle_bgload(&self) {
  556. //debug!(target: "ui::chatview", "ChatView::handle_bgload()");
  557. // Do we need to load some more?
  558. let scroll = self.scroll.get();
  559. let mut rect = read_rect(self.rect.clone()).expect("bad rect property");
  560. let top = scroll + rect.h;
  561. let preload_height = PRELOAD_PAGES as f32 * rect.h;
  562. let mut msgbuf = self.msgbuf.lock().await;
  563. let total_height = msgbuf.calc_total_height().await;
  564. if total_height > top + preload_height {
  565. // Nothing to do here
  566. //debug!(target: "ui::chatview", "bgloader: buffer is sufficient");
  567. return
  568. }
  569. // Keep loading until this is below 0
  570. let mut remaining_load_height = top + preload_height - total_height;
  571. //debug!(target: "ui::chatview", "bgloader: remaining px = {remaining_load_height}");
  572. let mut remaining_visible = top - total_height;
  573. // Get the current earliest timestamp
  574. let iter = match msgbuf.oldest_timestamp() {
  575. Some(oldest_timest) => {
  576. // iterate from there
  577. //debug!(target: "ui::chatview", "preloading from {oldest_timest}");
  578. let timest = (oldest_timest - 1).to_be_bytes();
  579. let mut key = [0u8; 8 + 32];
  580. key[..8].clone_from_slice(&timest);
  581. let iter = self.tree.range(..key).rev();
  582. iter
  583. }
  584. None => {
  585. //debug!(target: "ui::chatview", "initial load");
  586. self.tree.iter().rev()
  587. }
  588. };
  589. for entry in iter {
  590. let Ok((k, v)) = entry else { break };
  591. assert_eq!(k.len(), 8 + 32);
  592. let timest_bytes: [u8; 8] = k[..8].try_into().unwrap();
  593. let message_id: MessageId = k[8..].try_into().unwrap();
  594. let timest = Timestamp::from_be_bytes(timest_bytes);
  595. let chatmsg: ChatMsg = deserialize(&v).unwrap();
  596. debug!(target: "ui::chatview", "{timest:?} {chatmsg:?}");
  597. let msg_height =
  598. msgbuf.push_privmsg(timest, message_id, chatmsg.nick, chatmsg.text).await;
  599. remaining_load_height -= msg_height;
  600. if remaining_load_height <= 0. {
  601. break
  602. }
  603. if remaining_visible > 0. {
  604. self.redraw_cached(&mut msgbuf).await;
  605. }
  606. remaining_visible -= msg_height;
  607. }
  608. }
  609. /// Descent = line height - baseline
  610. fn descent(&self) -> f32 {
  611. self.line_height.get() - self.baseline.get()
  612. }
  613. async fn scrollview(&self, mut scroll: f32) -> f32 {
  614. //debug!(target: "ui::chatview", "scrollview()");
  615. let old_scroll = self.scroll.get();
  616. let rect = read_rect(self.rect.clone()).expect("bad rect property");
  617. let mut msgbuf = self.msgbuf.lock().await;
  618. // 1/3 of time spent here ~1.5ms
  619. if let Some(new_scroll) = self.adjust_scroll(&mut msgbuf, scroll, rect.h).await {
  620. scroll = new_scroll;
  621. }
  622. // 2/3 of time spent here ~3.3ms
  623. self.redraw_cached(&mut msgbuf).await;
  624. self.scroll.set(scroll);
  625. self.bgload_cv.notify();
  626. scroll - old_scroll
  627. }
  628. /// Adjusts a proposed scroll value to clamp it within range. It will load pages until we
  629. /// either run out or we have enough, then checks scroll is within range.
  630. /// Returns None if the value is within range.
  631. async fn adjust_scroll(
  632. &self,
  633. msgbuf: &mut MessageBuffer,
  634. mut scroll: f32,
  635. rect_h: f32,
  636. ) -> Option<f32> {
  637. // We still wish to preload pages to fill the screen, so we just adjust it up to 0.
  638. let nonneg_scroll = max(scroll, 0.);
  639. if scroll < 0. {
  640. return Some(0.)
  641. }
  642. let total_height = msgbuf.calc_total_height().await;
  643. let max_allowed_scroll = if total_height > rect_h { total_height - rect_h } else { 0. };
  644. if scroll > max_allowed_scroll {
  645. scroll = max_allowed_scroll;
  646. assert!(scroll >= 0.);
  647. return Some(scroll)
  648. }
  649. // Unchanged
  650. None
  651. }
  652. /// Returns draw calls for drawing
  653. async fn get_meshes(
  654. &self,
  655. msgbuf: &mut MessageBuffer,
  656. rect: &Rectangle,
  657. ) -> (Vec<DrawInstruction>, FreedData) {
  658. let scroll = self.scroll.get();
  659. let total_height = msgbuf.calc_total_height().await;
  660. // If lines aren't enough to fill the available buffer then start from the top
  661. let start_pos = if total_height < rect.h { total_height } else { rect.h };
  662. let mut instrs = vec![];
  663. //let mut old_drawmesh = vec![];
  664. let meshes = msgbuf.gen_meshes(rect, scroll).await;
  665. for (i, (y_pos, mesh)) in enumerate(meshes) {
  666. // Apply scroll and scissor
  667. // We use the scissor for scrolling
  668. // Because we use the scissor, our actual rect is now rect instead of parent_rect
  669. let off_x = 0.;
  670. // This calc decides whether scroll is in terms of pages or pixels
  671. let off_y = (scroll + start_pos - y_pos) / rect.h;
  672. let scale_x = 1. / rect.w;
  673. let scale_y = 1. / rect.h;
  674. let model = glam::Mat4::from_translation(glam::Vec3::new(off_x, off_y, 0.)) *
  675. glam::Mat4::from_scale(glam::Vec3::new(scale_x, scale_y, 1.));
  676. instrs.push(DrawInstruction::ApplyMatrix(model));
  677. instrs.push(DrawInstruction::Draw(mesh));
  678. }
  679. let freed = std::mem::take(&mut msgbuf.freed);
  680. (instrs, freed)
  681. }
  682. pub async fn draw(&self, sg: &SceneGraph, parent_rect: &Rectangle) -> Option<DrawUpdate> {
  683. debug!(target: "ui::chatview", "ChatView::draw()");
  684. *self.parent_rect.lock().unwrap() = Some(parent_rect.clone());
  685. let rect = eval_rect(self.rect.clone(), parent_rect).expect("bad rect property");
  686. let mut msgbuf = self.msgbuf.lock().await;
  687. msgbuf.adjust_width(rect.w);
  688. let mut scroll = self.scroll.get();
  689. if let Some(scroll) = self.adjust_scroll(&mut msgbuf, scroll, rect.h).await {
  690. self.scroll.set(scroll);
  691. }
  692. // We may need to load more messages since the screen size has changed.
  693. // Now we have updated all the values so it's safe to wake up here.
  694. self.bgload_cv.notify();
  695. let (mut mesh_instrs, freed) = self.get_meshes(&mut msgbuf, &rect).await;
  696. drop(msgbuf);
  697. let mut instrs = vec![DrawInstruction::ApplyViewport(rect)];
  698. instrs.append(&mut mesh_instrs);
  699. Some(DrawUpdate {
  700. key: self.dc_key,
  701. draw_calls: vec![(
  702. self.dc_key,
  703. DrawCall { instrs, dcs: vec![], z_index: self.z_index.get() },
  704. )],
  705. freed_textures: freed.textures,
  706. freed_buffers: freed.buffers,
  707. })
  708. }
  709. async fn redraw_cached(&self, msgbuf: &mut MessageBuffer) {
  710. let rect = read_rect(self.rect.clone()).expect("bad rect property");
  711. let (mut mesh_instrs, freed) = self.get_meshes(msgbuf, &rect).await;
  712. let mut instrs = vec![DrawInstruction::ApplyViewport(rect)];
  713. instrs.append(&mut mesh_instrs);
  714. let draw_calls =
  715. vec![(self.dc_key, DrawCall { instrs, dcs: vec![], z_index: self.z_index.get() })];
  716. self.render_api.replace_draw_calls(draw_calls).await;
  717. for buffer_id in freed.buffers {
  718. self.render_api.delete_buffer(buffer_id);
  719. }
  720. for texture_id in freed.textures {
  721. self.render_api.delete_texture(texture_id);
  722. }
  723. }
  724. /// Invalidates cache and redraws everything
  725. async fn redraw_all(&self) {
  726. debug!(target: "ui::chatview", "redraw()");
  727. // ... todo fin
  728. }
  729. }