mod.rs 34 KB

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