mod.rs 35 KB

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