mod.rs 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2025 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 async_trait::async_trait;
  20. use atomic_float::AtomicF32;
  21. use chrono::{Local, TimeZone};
  22. use darkfi::system::{msleep, CondVar};
  23. use darkfi_serial::{deserialize, Decodable, Encodable, SerialDecodable, SerialEncodable};
  24. use miniquad::{KeyCode, KeyMods, MouseButton, TouchPhase};
  25. use parking_lot::Mutex as SyncMutex;
  26. use rand::{rngs::OsRng, Rng};
  27. use sled_overlay::sled;
  28. use std::{
  29. collections::VecDeque,
  30. hash::{DefaultHasher, Hash, Hasher},
  31. io::Cursor,
  32. sync::{
  33. atomic::{AtomicBool, Ordering},
  34. Arc, Weak,
  35. },
  36. };
  37. mod page;
  38. use page::MessageBuffer;
  39. use crate::{
  40. gfx::{
  41. GfxDrawCall, GfxDrawInstruction, GfxDrawMesh, GraphicsEventPublisherPtr, Point, Rectangle,
  42. RenderApi,
  43. },
  44. mesh::{Color, MeshBuilder, COLOR_BLUE, COLOR_GREEN},
  45. prop::{
  46. PropertyAtomicGuard, PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr,
  47. PropertyRect, PropertyUint32, Role,
  48. },
  49. pubsub::Subscription,
  50. scene::{MethodCallSub, Pimpl, SceneNodeWeak},
  51. text::{self, Glyph, GlyphPositionIter, TextShaperPtr},
  52. util::{enumerate, is_whitespace, unixtime},
  53. ExecutorPtr,
  54. };
  55. use super::{DrawUpdate, OnModify, UIObject};
  56. macro_rules! d { ($($arg:tt)*) => { debug!(target: "ui::chatview", $($arg)*); } }
  57. macro_rules! t { ($($arg:tt)*) => { trace!(target: "ui::chatview", $($arg)*); } }
  58. const EPSILON: f32 = 0.001;
  59. const BIG_EPSILON: f32 = 0.05;
  60. // Disable selecting lines for this release.
  61. const ENABLE_SELECT: bool = false;
  62. fn is_zero(x: f32) -> bool {
  63. x.abs() < EPSILON
  64. }
  65. /// std::cmp::max() doesn't work on f32
  66. fn max(a: f32, b: f32) -> f32 {
  67. if a > b {
  68. a
  69. } else {
  70. b
  71. }
  72. }
  73. #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
  74. pub struct ChatMsg {
  75. pub nick: String,
  76. pub text: String,
  77. }
  78. pub type Timestamp = u64;
  79. #[derive(Clone, SerialEncodable, SerialDecodable, PartialEq)]
  80. pub struct MessageId(pub [u8; 32]);
  81. impl std::fmt::Display for MessageId {
  82. fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
  83. for b in &self.0 {
  84. write!(f, "{b:02x}")?
  85. }
  86. Ok(())
  87. }
  88. }
  89. const PRELOAD_PAGES: usize = 1;
  90. #[derive(Clone)]
  91. struct TouchInfo {
  92. start_scroll: f32,
  93. start_y: f32,
  94. start_instant: std::time::Instant,
  95. /// Used for flick scrolling
  96. samples: VecDeque<(std::time::Instant, f32)>,
  97. last_instant: std::time::Instant,
  98. last_y: f32,
  99. /// Selection started?
  100. is_select_mode: Option<bool>,
  101. }
  102. impl TouchInfo {
  103. fn new(start_scroll: f32, y: f32) -> Self {
  104. Self {
  105. start_scroll,
  106. start_y: y,
  107. start_instant: std::time::Instant::now(),
  108. samples: VecDeque::from([(std::time::Instant::now(), y)]),
  109. last_instant: std::time::Instant::now(),
  110. last_y: y,
  111. is_select_mode: None,
  112. }
  113. }
  114. fn push_sample(&mut self, y: f32) {
  115. self.samples.push_back((std::time::Instant::now(), y));
  116. // Now drop all old samples older than 40ms
  117. while let Some((instant, _)) = self.samples.front() {
  118. if instant.elapsed().as_micros() <= 40_000 {
  119. break
  120. }
  121. self.samples.pop_front().unwrap();
  122. }
  123. }
  124. fn first_sample(&self) -> Option<(f32, f32)> {
  125. self.samples.front().map(|(t, s)| (t.elapsed().as_micros() as f32 / 1000., *s))
  126. }
  127. }
  128. pub type ChatViewPtr = Arc<ChatView>;
  129. pub struct ChatView {
  130. node: SceneNodeWeak,
  131. tasks: SyncMutex<Vec<smol::Task<()>>>,
  132. render_api: RenderApi,
  133. text_shaper: TextShaperPtr,
  134. tree: sled::Tree,
  135. msgbuf: AsyncMutex<MessageBuffer>,
  136. dc_key: u64,
  137. /// Used for detecting when scrolling view
  138. mouse_pos: SyncMutex<Point>,
  139. /// Touch scrolling
  140. touch_info: SyncMutex<Option<TouchInfo>>,
  141. touch_is_active: AtomicBool,
  142. rect: PropertyRect,
  143. scroll: PropertyFloat32,
  144. z_index: PropertyUint32,
  145. priority: PropertyUint32,
  146. scroll_start_accel: PropertyFloat32,
  147. scroll_resist: PropertyFloat32,
  148. select_hold_time: PropertyFloat32,
  149. key_scroll_speed: PropertyFloat32,
  150. /// Scroll accel
  151. motion_cv: Arc<CondVar>,
  152. speed: AtomicF32,
  153. mouse_btn_held: AtomicBool,
  154. /// Triggers the background loading task to wake up.
  155. /// We use this since there should only ever be a single bg task loading.
  156. bgload_cv: Arc<CondVar>,
  157. /// We use it when we re-eval rect when its changed via property.
  158. parent_rect: SyncMutex<Option<Rectangle>>,
  159. }
  160. impl ChatView {
  161. pub async fn new(
  162. node: SceneNodeWeak,
  163. tree: sled::Tree,
  164. window_scale: PropertyFloat32,
  165. render_api: RenderApi,
  166. text_shaper: TextShaperPtr,
  167. ex: ExecutorPtr,
  168. ) -> Pimpl {
  169. t!("ChatView::new()");
  170. let node_ref = &node.upgrade().unwrap();
  171. let rect = PropertyRect::wrap(node_ref, Role::Internal, "rect").unwrap();
  172. let scroll = PropertyFloat32::wrap(node_ref, Role::Internal, "scroll", 0).unwrap();
  173. let font_size = PropertyFloat32::wrap(node_ref, Role::Internal, "font_size", 0).unwrap();
  174. let timestamp_font_size =
  175. PropertyFloat32::wrap(node_ref, Role::Internal, "timestamp_font_size", 0).unwrap();
  176. let timestamp_width =
  177. PropertyFloat32::wrap(node_ref, Role::Internal, "timestamp_width", 0).unwrap();
  178. let line_height =
  179. PropertyFloat32::wrap(node_ref, Role::Internal, "line_height", 0).unwrap();
  180. let message_spacing =
  181. PropertyFloat32::wrap(node_ref, Role::Internal, "message_spacing", 0).unwrap();
  182. let baseline = PropertyFloat32::wrap(node_ref, Role::Internal, "baseline", 0).unwrap();
  183. let timestamp_color =
  184. PropertyColor::wrap(node_ref, Role::Internal, "timestamp_color").unwrap();
  185. let text_color = PropertyColor::wrap(node_ref, Role::Internal, "text_color").unwrap();
  186. let nick_colors = node_ref.get_property("nick_colors").expect("ChatView::nick_colors");
  187. let hi_bg_color = PropertyColor::wrap(node_ref, Role::Internal, "hi_bg_color").unwrap();
  188. let z_index = PropertyUint32::wrap(node_ref, Role::Internal, "z_index", 0).unwrap();
  189. let priority = PropertyUint32::wrap(node_ref, Role::Internal, "priority", 0).unwrap();
  190. let debug = PropertyBool::wrap(node_ref, Role::Internal, "debug", 0).unwrap();
  191. let scroll_start_accel =
  192. PropertyFloat32::wrap(node_ref, Role::Internal, "scroll_start_accel", 0).unwrap();
  193. let scroll_resist =
  194. PropertyFloat32::wrap(node_ref, Role::Internal, "scroll_resist", 0).unwrap();
  195. let select_hold_time =
  196. PropertyFloat32::wrap(node_ref, Role::Internal, "select_hold_time", 0).unwrap();
  197. let key_scroll_speed =
  198. PropertyFloat32::wrap(node_ref, Role::Internal, "key_scroll_speed", 0).unwrap();
  199. let motion_cv = Arc::new(CondVar::new());
  200. let bgload_cv = Arc::new(CondVar::new());
  201. let self_ = Arc::new(Self {
  202. node: node.clone(),
  203. tasks: SyncMutex::new(vec![]),
  204. render_api: render_api.clone(),
  205. text_shaper: text_shaper.clone(),
  206. tree,
  207. msgbuf: AsyncMutex::new(MessageBuffer::new(
  208. node,
  209. font_size,
  210. timestamp_font_size,
  211. timestamp_width,
  212. line_height,
  213. message_spacing,
  214. baseline,
  215. timestamp_color,
  216. text_color,
  217. nick_colors,
  218. hi_bg_color,
  219. debug,
  220. window_scale,
  221. render_api,
  222. text_shaper,
  223. )),
  224. dc_key: OsRng.gen(),
  225. mouse_pos: SyncMutex::new(Point::from([0., 0.])),
  226. touch_info: SyncMutex::new(None),
  227. touch_is_active: AtomicBool::new(false),
  228. rect,
  229. scroll,
  230. z_index,
  231. priority,
  232. scroll_start_accel,
  233. scroll_resist,
  234. select_hold_time,
  235. key_scroll_speed,
  236. motion_cv,
  237. speed: AtomicF32::new(0.),
  238. mouse_btn_held: AtomicBool::new(false),
  239. bgload_cv,
  240. parent_rect: SyncMutex::new(None),
  241. });
  242. Pimpl::ChatView(self_)
  243. }
  244. async fn process_insert_line_method(me: &Weak<Self>, sub: &MethodCallSub) -> bool {
  245. let Ok(method_call) = sub.receive().await else {
  246. d!("Event relayer closed");
  247. return false
  248. };
  249. t!("method called: insert_line({method_call:?})");
  250. assert!(method_call.send_res.is_none());
  251. fn decode_data(data: &[u8]) -> std::io::Result<(Timestamp, MessageId, String, String)> {
  252. let mut cur = Cursor::new(&data);
  253. let timestamp = Timestamp::decode(&mut cur)?;
  254. let msg_id = MessageId::decode(&mut cur)?;
  255. let nick = String::decode(&mut cur)?;
  256. let text = String::decode(&mut cur)?;
  257. Ok((timestamp, msg_id, nick, text))
  258. }
  259. let Ok((timestamp, msg_id, nick, text)) = decode_data(&method_call.data) else {
  260. error!(target: "ui::chatview", "insert_line() method invalid arg data");
  261. return true
  262. };
  263. let Some(self_) = me.upgrade() else {
  264. // Should not happen
  265. panic!("self destroyed before insert_line_method_task was stopped!");
  266. };
  267. self_.handle_insert_line(timestamp, msg_id, nick, text).await;
  268. true
  269. }
  270. async fn process_insert_unconf_line_method(me: &Weak<Self>, sub: &MethodCallSub) -> bool {
  271. let Ok(method_call) = sub.receive().await else {
  272. d!("Event relayer closed");
  273. return false
  274. };
  275. t!("method called: insert_unconf_line({method_call:?})");
  276. assert!(method_call.send_res.is_none());
  277. fn decode_data(data: &[u8]) -> std::io::Result<(Timestamp, MessageId, String, String)> {
  278. let mut cur = Cursor::new(&data);
  279. let timestamp = Timestamp::decode(&mut cur)?;
  280. let msg_id = MessageId::decode(&mut cur)?;
  281. let nick = String::decode(&mut cur)?;
  282. let text = String::decode(&mut cur)?;
  283. Ok((timestamp, msg_id, nick, text))
  284. }
  285. let Ok((timestamp, msg_id, nick, text)) = decode_data(&method_call.data) else {
  286. error!(target: "ui::chatview", "insert_unconf_line() method invalid arg data");
  287. return true
  288. };
  289. let Some(self_) = me.upgrade() else {
  290. // Should not happen
  291. panic!("self destroyed before touch_task was stopped!");
  292. };
  293. self_.handle_insert_unconf_line(timestamp, msg_id, nick, text).await;
  294. true
  295. }
  296. /// Mark line as selected
  297. async fn select_line(&self, mut y: f32) {
  298. let trace_id = rand::random();
  299. t!("select_line({y}) [trace_id={trace_id}]");
  300. // The cursor is inside the rect. We just have to find which line it clicked.
  301. let rect = self.rect.get();
  302. // y coord within widget's screen rect
  303. y -= rect.y;
  304. // The scroll is the position of the bottom of the rect on screen
  305. let scroll = self.scroll.get();
  306. // Now what is its distance from the absolute bottom
  307. y = rect.h - y + scroll;
  308. let mut msgbuf = self.msgbuf.lock().await;
  309. msgbuf.select_line(y).await;
  310. self.redraw_cached(&mut msgbuf, trace_id).await;
  311. }
  312. fn end_touch_phase(&self, touch_y: f32) {
  313. // Now calculate scroll acceleration
  314. let touch_info = std::mem::replace(&mut *self.touch_info.lock(), None);
  315. let Some(touch_info) = &touch_info else { return };
  316. self.touch_is_active.store(false, Ordering::Relaxed);
  317. // No scroll accel with selection mode
  318. if touch_info.is_select_mode.is_some() {
  319. return
  320. }
  321. let Some((time, sample_y)) = touch_info.first_sample() else { return };
  322. let dist = touch_y - sample_y;
  323. // Ignore sub-ms events
  324. if time < 1. {
  325. error!(target: "ui::chatview", "Received a sub-ms touch event!");
  326. return
  327. }
  328. //let speed = dist / time;
  329. //self.speed.fetch_add(speed, Ordering::Relaxed);
  330. //debug!(target: "ui::chatview", "speed = {dist} / {time} = {speed}");
  331. let accel = self.scroll_start_accel.get() * dist / time;
  332. let touch_time = touch_info.start_instant.elapsed();
  333. t!("accel = {dist} / {time} = {accel}, touch = {touch_time:?}");
  334. self.speed.fetch_add(accel, Ordering::Relaxed);
  335. self.motion_cv.notify();
  336. }
  337. async fn add_line_to_db(
  338. &self,
  339. timest: Timestamp,
  340. msg_id: &MessageId,
  341. nick: &str,
  342. text: &str,
  343. ) -> bool {
  344. assert!(timest > 6047051717);
  345. let timest = timest.to_be_bytes();
  346. assert_eq!(timest.len(), 8);
  347. let mut key = [0u8; 8 + 32];
  348. key[..8].clone_from_slice(&timest);
  349. key[8..].clone_from_slice(&msg_id.0);
  350. // When does this return Err?
  351. let contains_key = self.tree.contains_key(&key);
  352. if contains_key.is_err() || contains_key.unwrap() {
  353. // Already exists
  354. return false
  355. }
  356. let msg = ChatMsg { nick: nick.to_string(), text: text.to_string() };
  357. let mut val = vec![];
  358. msg.encode(&mut val).unwrap();
  359. self.tree.insert(&key, val).unwrap();
  360. let _ = self.tree.flush_async().await;
  361. true
  362. }
  363. pub async fn handle_insert_line(
  364. &self,
  365. timest: Timestamp,
  366. msg_id: MessageId,
  367. nick: String,
  368. text: String,
  369. ) {
  370. let trace_id = rand::random();
  371. t!("handle_insert_line({timest}, {msg_id}, {nick}, {text}) [trace_id={trace_id}]");
  372. // Lock message buffer so background loader doesn't load the message as soon as it's
  373. // inserted into the DB.
  374. let mut msgbuf = self.msgbuf.lock().await;
  375. if !self.add_line_to_db(timest, &msg_id, &nick, &text).await {
  376. // Already exists so bail
  377. t!("duplicate msg so bailing");
  378. return
  379. }
  380. // Add message to page
  381. if msgbuf.mark_confirmed(&msg_id) {
  382. // Message already exists. Which means it must be an unconfirmed sent message.
  383. // Mark it as confirmed.
  384. t!("Mark sent message as confirmed");
  385. } else {
  386. t!("Inserting new message");
  387. // Insert the privmsg since it doesn't already exist
  388. if msgbuf.insert_privmsg(timest, msg_id, nick, text).is_none() {
  389. // Not visible so no need to redraw
  390. return
  391. }
  392. }
  393. self.redraw_cached(&mut msgbuf, trace_id).await;
  394. self.bgload_cv.notify();
  395. }
  396. async fn handle_insert_unconf_line(
  397. &self,
  398. timest: Timestamp,
  399. msg_id: MessageId,
  400. nick: String,
  401. text: String,
  402. ) {
  403. let trace_id = rand::random();
  404. t!("handle_insert_unconf_line({timest}, {msg_id}, {nick}, {text}) [trace_id={trace_id}]");
  405. // We don't add unconfirmed lines to the db. Maybe we should?
  406. // Add message to page
  407. let mut msgbuf = self.msgbuf.lock().await;
  408. let Some(privmsg) = msgbuf.insert_privmsg(timest, msg_id, nick, text) else { return };
  409. privmsg.confirmed = false;
  410. self.redraw_cached(&mut msgbuf, trace_id).await;
  411. self.bgload_cv.notify();
  412. }
  413. /// Signal to begin scrolling
  414. fn start_scroll(&self, y: f32) {
  415. self.speed.fetch_add(y * self.scroll_start_accel.get(), Ordering::Relaxed);
  416. self.motion_cv.notify();
  417. }
  418. async fn handle_movement(&self, atom: &mut PropertyAtomicGuard) {
  419. // We need to fix this impl because it depends very much on the speed of the device
  420. // that it's running on.
  421. // Look into optimizing scrollview() so scrolling is smooth.
  422. // We could use skiplists to avoid looping from the very bottom.
  423. // So index 1 in the skiplist advances to 100px up... (or however much multiplier)
  424. loop {
  425. msleep(10).await;
  426. if self.touch_is_active.load(Ordering::Relaxed) {
  427. return
  428. }
  429. let mut speed = self.speed.load(Ordering::Relaxed);
  430. // Apply constant decel to speed
  431. speed *= self.scroll_resist.get();
  432. if speed.abs() < BIG_EPSILON {
  433. speed = 0.;
  434. }
  435. self.speed.store(speed, Ordering::Relaxed);
  436. // Finished
  437. if is_zero(speed) {
  438. return
  439. }
  440. let scroll = self.scroll.get() + speed;
  441. let dist = self.scrollview(scroll, atom).await;
  442. // We reached the end so just stop
  443. if is_zero(dist) {
  444. self.speed.store(0., Ordering::Relaxed);
  445. return
  446. }
  447. }
  448. }
  449. async fn handle_bgload(&self) {
  450. let trace_id = rand::random();
  451. t!("ChatView::handle_bgload() [trace_id={trace_id}]");
  452. // Do we need to load some more?
  453. let scroll = self.scroll.get();
  454. let rect = self.rect.get();
  455. let top = scroll + rect.h;
  456. let preload_height = PRELOAD_PAGES as f32 * rect.h;
  457. let mut msgbuf = self.msgbuf.lock().await;
  458. let total_height = msgbuf.calc_total_height().await;
  459. if total_height > top + preload_height {
  460. // Nothing to do here
  461. t!("bgloader: buffer is sufficient [trace_id={trace_id}]");
  462. return
  463. }
  464. // Keep loading until this is below 0
  465. let mut remaining_load_height = top + preload_height - total_height;
  466. let mut remaining_visible = top - total_height;
  467. t!("bgloader: remaining px = {remaining_load_height}, remaining_visible={remaining_visible} [trace_id={trace_id}]");
  468. // Get the current earliest timestamp
  469. let iter = match msgbuf.oldest_timestamp() {
  470. Some(oldest_timest) => {
  471. // iterate from there
  472. t!("preloading from {oldest_timest} [trace_id={trace_id}]");
  473. let timest = (oldest_timest - 1).to_be_bytes();
  474. let mut key = [0u8; 8 + 32];
  475. key[..8].clone_from_slice(&timest);
  476. let iter = self.tree.range(..key).rev();
  477. iter
  478. }
  479. None => {
  480. t!("initial load [trace_id={trace_id}]");
  481. self.tree.iter().rev()
  482. }
  483. };
  484. let mut do_redraw = false;
  485. for entry in iter {
  486. let Ok((k, v)) = entry else { break };
  487. assert_eq!(k.len(), 8 + 32);
  488. let timest_bytes: [u8; 8] = k[..8].try_into().unwrap();
  489. let msg_id = MessageId(k[8..].try_into().unwrap());
  490. let timest = Timestamp::from_be_bytes(timest_bytes);
  491. let chatmsg: ChatMsg = deserialize(&v).unwrap();
  492. t!("{timest:?} {chatmsg:?} [trace_id={trace_id}]");
  493. let msg_height = msgbuf.push_privmsg(timest, msg_id, chatmsg.nick, chatmsg.text);
  494. remaining_load_height -= msg_height;
  495. if remaining_load_height <= 0. {
  496. break
  497. }
  498. // Do this once at the end rather than continuously redrawing
  499. if remaining_visible > 0. {
  500. do_redraw = true;
  501. }
  502. remaining_visible -= msg_height;
  503. }
  504. t!("do_redraw = {do_redraw} [trace_id={trace_id}]");
  505. if do_redraw {
  506. self.redraw_cached(&mut msgbuf, trace_id).await;
  507. }
  508. }
  509. async fn scrollview(&self, mut scroll: f32, atom: &mut PropertyAtomicGuard) -> f32 {
  510. let trace_id = rand::random();
  511. t!("scrollview({scroll}) [trace_id={trace_id}]");
  512. let old_scroll = self.scroll.get();
  513. let rect = self.rect.get();
  514. let mut msgbuf = self.msgbuf.lock().await;
  515. // 1/3 of time spent here ~1.5ms
  516. if let Some(new_scroll) = self.adjust_scroll(&mut msgbuf, scroll, rect.h).await {
  517. scroll = new_scroll;
  518. }
  519. // 2/3 of time spent here ~3.3ms
  520. self.redraw_cached(&mut msgbuf, trace_id).await;
  521. self.scroll.set(atom, scroll);
  522. self.bgload_cv.notify();
  523. scroll - old_scroll
  524. }
  525. /// Adjusts a proposed scroll value to clamp it within range. It will load pages until we
  526. /// either run out or we have enough, then checks scroll is within range.
  527. /// Returns None if the value is within range.
  528. async fn adjust_scroll(
  529. &self,
  530. msgbuf: &mut MessageBuffer,
  531. mut scroll: f32,
  532. rect_h: f32,
  533. ) -> Option<f32> {
  534. // We still wish to preload pages to fill the screen, so we just adjust it up to 0.
  535. let nonneg_scroll = max(scroll, 0.);
  536. if scroll < 0. {
  537. return Some(0.)
  538. }
  539. let total_height = msgbuf.calc_total_height().await;
  540. let max_allowed_scroll = if total_height > rect_h { total_height - rect_h } else { 0. };
  541. if scroll > max_allowed_scroll {
  542. scroll = max_allowed_scroll;
  543. assert!(scroll >= 0.);
  544. return Some(scroll)
  545. }
  546. // Unchanged
  547. None
  548. }
  549. /// Returns draw calls for drawing
  550. async fn get_meshes(
  551. &self,
  552. msgbuf: &mut MessageBuffer,
  553. rect: &Rectangle,
  554. ) -> Vec<GfxDrawInstruction> {
  555. let scroll = self.scroll.get();
  556. let total_height = msgbuf.calc_total_height().await;
  557. // Use this to start from the top
  558. //let start_pos = if total_height < rect.h { total_height } else { rect.h };
  559. // We start from the bottom though
  560. let start_pos = rect.h;
  561. let mut instrs = vec![];
  562. //let mut old_drawmesh = vec![];
  563. let meshes = msgbuf.gen_meshes(rect, scroll).await;
  564. for (i, (y_pos, mesh)) in enumerate(meshes) {
  565. // Apply scroll and scissor
  566. // We use the scissor for scrolling
  567. // Because we use the scissor, our actual rect is now rect instead of parent_rect
  568. let off_x = 0.;
  569. // This calc decides whether scroll is in terms of pages or pixels
  570. let off_y = (scroll + start_pos - y_pos);
  571. let pos = Point::from([0., off_y]);
  572. instrs.push(GfxDrawInstruction::SetPos(pos));
  573. instrs.push(GfxDrawInstruction::Draw(mesh));
  574. }
  575. instrs
  576. }
  577. async fn redraw_cached(&self, msgbuf: &mut MessageBuffer, trace_id: u32) {
  578. t!("ChatView::redraw_cached() [trace_id={trace_id}]");
  579. let timest = unixtime();
  580. let rect = self.rect.get();
  581. let mut mesh_instrs = self.get_meshes(msgbuf, &rect).await;
  582. let mut instrs = vec![GfxDrawInstruction::ApplyView(rect)];
  583. instrs.append(&mut mesh_instrs);
  584. let draw_calls =
  585. vec![(self.dc_key, GfxDrawCall { instrs, dcs: vec![], z_index: self.z_index.get() })];
  586. self.render_api.replace_draw_calls(timest, draw_calls);
  587. t!("ChatView::redraw_cached() DONE [trace_id={trace_id}]");
  588. }
  589. /// Invalidates cache and redraws everything
  590. async fn redraw_all(&self) {
  591. let trace_id = rand::random();
  592. t!("ChatView::redraw_all() [trace_id={trace_id}]");
  593. let parent_rect = self.parent_rect.lock().unwrap().clone();
  594. self.rect.eval(&parent_rect).expect("unable to eval rect");
  595. let mut msgbuf = self.msgbuf.lock().await;
  596. msgbuf.adjust_params();
  597. msgbuf.clear_meshes();
  598. self.redraw_cached(&mut msgbuf, trace_id).await;
  599. t!("ChatView::redraw_all() DONE [trace_id={trace_id}]");
  600. }
  601. }
  602. #[async_trait]
  603. impl UIObject for ChatView {
  604. fn priority(&self) -> u32 {
  605. self.priority.get()
  606. }
  607. async fn start(self: Arc<Self>, ex: ExecutorPtr) {
  608. let me = Arc::downgrade(&self);
  609. let node_ref = &self.node.upgrade().unwrap();
  610. let method_sub = node_ref.subscribe_method_call("insert_line").unwrap();
  611. let me2 = me.clone();
  612. let insert_line_method_task =
  613. ex.spawn(
  614. async move { while Self::process_insert_line_method(&me2, &method_sub).await {} },
  615. );
  616. let method_sub = node_ref.subscribe_method_call("insert_unconf_line").unwrap();
  617. let me2 = me.clone();
  618. let insert_unconf_line_method_task = ex.spawn(async move {
  619. while Self::process_insert_unconf_line_method(&me2, &method_sub).await {}
  620. });
  621. let me2 = me.clone();
  622. let cv = self.motion_cv.clone();
  623. let motion_task = ex.spawn(async move {
  624. loop {
  625. cv.wait().await;
  626. let Some(self_) = me2.upgrade() else {
  627. // Should not happen
  628. panic!("self destroyed before motion_task was stopped!");
  629. };
  630. let atom = &mut PropertyAtomicGuard::new();
  631. self_.handle_movement(atom).await;
  632. cv.reset();
  633. }
  634. });
  635. let me2 = me.clone();
  636. let cv = self.bgload_cv.clone();
  637. let bgload_task = ex.spawn(async move {
  638. loop {
  639. cv.wait().await;
  640. let Some(self_) = me2.upgrade() else {
  641. // Should not happen
  642. panic!("self destroyed before bgload_task was stopped!");
  643. };
  644. self_.handle_bgload().await;
  645. cv.reset();
  646. }
  647. });
  648. let mut on_modify = OnModify::new(ex, self.node.clone(), me.clone());
  649. async fn reload_view(self_: Arc<ChatView>) {
  650. let atom = &mut PropertyAtomicGuard::new();
  651. self_.scrollview(self_.scroll.get(), atom).await;
  652. }
  653. on_modify.when_change(self.scroll.prop(), reload_view);
  654. async fn redraw(self_: Arc<ChatView>) {
  655. if !self_.rect.has_cached() {
  656. return
  657. }
  658. self_.redraw_all().await;
  659. }
  660. //on_modify.when_change(self.baseline.prop(), redraw);
  661. //on_modify.when_change(self.font_size.prop(), redraw);
  662. //on_modify.when_change(self.timestamp_font_size.prop(), redraw);
  663. //on_modify.when_change(self.timestamp_color.prop(), redraw);
  664. //on_modify.when_change(self.timestamp_width.prop(), redraw);
  665. //on_modify.when_change(self.line_height.prop(), redraw);
  666. //on_modify.when_change(self.message_spacing.prop(), redraw);
  667. //on_modify.when_change(self.text_color.prop(), redraw);
  668. //on_modify.when_change(self.nick_colors.clone(), redraw);
  669. //on_modify.when_change(self.hi_bg_color.prop(), redraw);
  670. on_modify.when_change(self.rect.prop(), redraw);
  671. //on_modify.when_change(self.debug.prop(), redraw);
  672. let mut tasks =
  673. vec![insert_line_method_task, insert_unconf_line_method_task, motion_task, bgload_task];
  674. tasks.append(&mut on_modify.tasks);
  675. *self.tasks.lock() = tasks;
  676. }
  677. fn stop(&self) {
  678. self.tasks.lock().clear();
  679. *self.parent_rect.lock() = None;
  680. // Clear mesh caches
  681. self.msgbuf.lock_blocking().clear();
  682. }
  683. async fn draw(
  684. &self,
  685. parent_rect: Rectangle,
  686. trace_id: u32,
  687. atom: &mut PropertyAtomicGuard,
  688. ) -> Option<DrawUpdate> {
  689. t!("ChatView::draw({:?}, {trace_id})", self.node.upgrade().unwrap());
  690. *self.parent_rect.lock() = Some(parent_rect.clone());
  691. self.rect.eval(&parent_rect).ok()?;
  692. let rect = self.rect.get();
  693. let mut msgbuf = self.msgbuf.lock().await;
  694. msgbuf.adjust_window_scale();
  695. msgbuf.adjust_width(rect.w);
  696. msgbuf.clear_meshes();
  697. let mut scroll = self.scroll.get();
  698. if let Some(scroll) = self.adjust_scroll(&mut msgbuf, scroll, rect.h).await {
  699. self.scroll.set(atom, scroll);
  700. }
  701. // We may need to load more messages since the screen size has changed.
  702. // Now we have updated all the values so it's safe to wake up here.
  703. self.bgload_cv.notify();
  704. let mut mesh_instrs = self.get_meshes(&mut msgbuf, &rect).await;
  705. drop(msgbuf);
  706. let mut instrs = vec![GfxDrawInstruction::ApplyView(rect)];
  707. instrs.append(&mut mesh_instrs);
  708. Some(DrawUpdate {
  709. key: self.dc_key,
  710. draw_calls: vec![(
  711. self.dc_key,
  712. GfxDrawCall { instrs, dcs: vec![], z_index: self.z_index.get() },
  713. )],
  714. })
  715. }
  716. async fn handle_key_down(&self, key: KeyCode, mods: KeyMods, repeat: bool) -> bool {
  717. if repeat {
  718. return false
  719. }
  720. match key {
  721. KeyCode::PageUp => {
  722. self.start_scroll(1. * self.key_scroll_speed.get());
  723. return true
  724. }
  725. KeyCode::PageDown => {
  726. self.start_scroll(-1. * self.key_scroll_speed.get());
  727. return true
  728. }
  729. _ => {}
  730. }
  731. false
  732. }
  733. async fn handle_mouse_btn_down(&self, btn: MouseButton, mouse_pos: Point) -> bool {
  734. if btn != MouseButton::Left {
  735. return false
  736. }
  737. let rect = self.rect.get();
  738. if !rect.contains(mouse_pos) {
  739. return false
  740. }
  741. if ENABLE_SELECT {
  742. self.select_line(mouse_pos.y).await;
  743. }
  744. self.mouse_btn_held.store(true, Ordering::Relaxed);
  745. true
  746. }
  747. async fn handle_mouse_btn_up(&self, btn: MouseButton, mouse_pos: Point) -> bool {
  748. t!("handle_mouse_btn_up({btn:?}, {mouse_pos:?})");
  749. if btn != MouseButton::Left {
  750. return false
  751. }
  752. self.mouse_btn_held.store(false, Ordering::Relaxed);
  753. false
  754. }
  755. async fn handle_mouse_move(&self, mouse_pos: Point) -> bool {
  756. t!("handle_mouse_move({mouse_pos:?})");
  757. // We store the mouse pos for use in handle_mouse_wheel()
  758. *self.mouse_pos.lock() = mouse_pos.clone();
  759. if !self.mouse_btn_held.load(Ordering::Relaxed) {
  760. return false
  761. }
  762. let rect = self.rect.get();
  763. if !rect.contains(mouse_pos) {
  764. return false
  765. }
  766. if ENABLE_SELECT {
  767. self.select_line(mouse_pos.y).await;
  768. }
  769. false
  770. }
  771. async fn handle_mouse_wheel(&self, wheel_pos: Point) -> bool {
  772. t!("handle_mouse_wheel({wheel_pos:?})");
  773. let rect = self.rect.get();
  774. let mouse_pos = self.mouse_pos.lock().clone();
  775. if !rect.contains(mouse_pos) {
  776. t!("not inside rect");
  777. return false
  778. }
  779. self.start_scroll(wheel_pos.y);
  780. true
  781. }
  782. async fn handle_touch(&self, phase: TouchPhase, id: u64, touch_pos: Point) -> bool {
  783. // Ignore multi-touch
  784. if id != 0 {
  785. return false
  786. }
  787. let rect = self.rect.get();
  788. t!("handle_touch({phase:?}, {id},{id}, {touch_pos:?})");
  789. let atom = &mut PropertyAtomicGuard::new();
  790. let touch_y = touch_pos.y;
  791. if !rect.contains(touch_pos) {
  792. match phase {
  793. TouchPhase::Started => *self.touch_info.lock() = None,
  794. _ => self.end_touch_phase(touch_y),
  795. }
  796. return false
  797. }
  798. let select_hold_time = self.select_hold_time.get();
  799. // Simulate mouse events
  800. match phase {
  801. TouchPhase::Started => {
  802. self.touch_is_active.store(true, Ordering::Relaxed);
  803. let mut touch_info = self.touch_info.lock();
  804. *touch_info = Some(TouchInfo::new(self.scroll.get(), touch_y));
  805. }
  806. TouchPhase::Moved => {
  807. let (start_scroll, start_y, start_elapsed, do_update, is_select_mode) = {
  808. let mut touch_info = self.touch_info.lock();
  809. let Some(touch_info) = &mut *touch_info else { return false };
  810. touch_info.last_y = touch_y;
  811. let start_scroll = touch_info.start_scroll;
  812. let start_y = touch_info.start_y;
  813. let start_elapsed =
  814. touch_info.start_instant.elapsed().as_micros() as f32 / 1000.;
  815. if start_elapsed > select_hold_time && touch_info.is_select_mode.is_none() {
  816. // Did we move?
  817. if (touch_y - start_y).abs() < BIG_EPSILON {
  818. touch_info.is_select_mode = Some(true);
  819. } else {
  820. touch_info.is_select_mode = Some(false);
  821. }
  822. }
  823. let is_select_mode = touch_info.is_select_mode.clone();
  824. touch_info.push_sample(touch_y);
  825. // Only update screen every 20ms. Avoid wasting cycles.
  826. let last_elapsed = touch_info.last_instant.elapsed().as_micros();
  827. let do_update = last_elapsed > 20_000;
  828. if do_update {
  829. touch_info.last_instant = std::time::Instant::now();
  830. }
  831. (start_scroll, start_y, start_elapsed, do_update, is_select_mode)
  832. };
  833. t!("touch phase moved, is_select_mode={is_select_mode:?}");
  834. // When scrolling if we suddenly grab the screen for more than a brief period
  835. // of time then stop the scrolling completely.
  836. if start_elapsed > 200. {
  837. t!("Stopping scroll accel");
  838. self.speed.store(0., Ordering::Relaxed);
  839. }
  840. // Only update every so often to prevent wasting resources.
  841. if !do_update {
  842. return true
  843. }
  844. // We are in selection mode so don't scroll the screen until touch phase ends.
  845. if is_select_mode == Some(true) {
  846. if ENABLE_SELECT {
  847. self.select_line(touch_y).await;
  848. }
  849. return true
  850. }
  851. let dist = touch_y - start_y;
  852. // No movement so just return
  853. if dist.abs() < BIG_EPSILON {
  854. return true
  855. }
  856. let scroll = start_scroll + dist;
  857. // Redraws the screen from the cache
  858. self.scrollview(scroll, atom).await;
  859. }
  860. TouchPhase::Ended | TouchPhase::Cancelled => {
  861. self.end_touch_phase(touch_y);
  862. }
  863. }
  864. true
  865. }
  866. }
  867. impl Drop for ChatView {
  868. fn drop(&mut self) {
  869. self.render_api.replace_draw_calls(unixtime(), vec![(self.dc_key, Default::default())]);
  870. }
  871. }