mod.rs 34 KB

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