mod.rs 35 KB

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