| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904 |
- /* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2024 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
- use async_lock::Mutex as AsyncMutex;
- use async_trait::async_trait;
- use atomic_float::AtomicF32;
- use chrono::{Local, TimeZone};
- use darkfi::system::{msleep, CondVar};
- use darkfi_serial::{deserialize, Decodable, Encodable, SerialDecodable, SerialEncodable};
- use miniquad::{KeyCode, KeyMods, MouseButton, TouchPhase};
- use rand::{rngs::OsRng, Rng};
- use sled_overlay::sled;
- use std::{
- collections::VecDeque,
- hash::{DefaultHasher, Hash, Hasher},
- io::Cursor,
- sync::{
- atomic::{AtomicBool, Ordering},
- Arc, Mutex as SyncMutex, Weak,
- },
- };
- mod page;
- use page::{FreedData, MessageBuffer};
- use crate::{
- gfx::{
- GfxDrawCall, GfxDrawInstruction, GfxDrawMesh, GraphicsEventPublisherPtr, Point, Rectangle,
- RenderApi, RenderApiPtr,
- },
- mesh::{Color, MeshBuilder, COLOR_BLUE, COLOR_GREEN},
- prop::{
- PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr, PropertyRect, PropertyUint32,
- Role,
- },
- pubsub::Subscription,
- scene::{Pimpl, SceneNodeWeak},
- text::{self, Glyph, GlyphPositionIter, TextShaperPtr},
- util::{enumerate, is_whitespace},
- ExecutorPtr,
- };
- use super::{DrawUpdate, OnModify, UIObject};
- const EPSILON: f32 = 0.001;
- const BIG_EPSILON: f32 = 0.05;
- fn is_zero(x: f32) -> bool {
- x.abs() < EPSILON
- }
- /// std::cmp::max() doesn't work on f32
- fn max(a: f32, b: f32) -> f32 {
- if a > b {
- a
- } else {
- b
- }
- }
- #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
- pub struct ChatMsg {
- pub nick: String,
- pub text: String,
- }
- type Timestamp = u64;
- type MessageId = [u8; 32];
- const PRELOAD_PAGES: usize = 1;
- #[derive(Clone)]
- struct TouchInfo {
- start_scroll: f32,
- start_y: f32,
- start_instant: std::time::Instant,
- /// Used for flick scrolling
- samples: VecDeque<(std::time::Instant, f32)>,
- last_instant: std::time::Instant,
- last_y: f32,
- /// Selection started?
- is_select_mode: Option<bool>,
- }
- impl TouchInfo {
- fn new(start_scroll: f32, y: f32) -> Self {
- Self {
- start_scroll,
- start_y: y,
- start_instant: std::time::Instant::now(),
- samples: VecDeque::from([(std::time::Instant::now(), y)]),
- last_instant: std::time::Instant::now(),
- last_y: y,
- is_select_mode: None,
- }
- }
- fn push_sample(&mut self, y: f32) {
- self.samples.push_back((std::time::Instant::now(), y));
- // Now drop all old samples older than 40ms
- while let Some((instant, _)) = self.samples.front() {
- if instant.elapsed().as_millis_f32() <= 40. {
- break
- }
- self.samples.pop_front().unwrap();
- }
- }
- fn first_sample(&self) -> Option<(f32, f32)> {
- self.samples.front().map(|(t, s)| (t.elapsed().as_millis_f32(), *s))
- }
- }
- pub type ChatViewPtr = Arc<ChatView>;
- pub struct ChatView {
- node: SceneNodeWeak,
- #[allow(dead_code)]
- tasks: Vec<smol::Task<()>>,
- render_api: RenderApiPtr,
- text_shaper: TextShaperPtr,
- tree: sled::Tree,
- msgbuf: AsyncMutex<MessageBuffer>,
- dc_key: u64,
- /// Used for detecting when scrolling view
- mouse_pos: SyncMutex<Point>,
- /// Touch scrolling
- touch_info: SyncMutex<Option<TouchInfo>>,
- touch_is_active: AtomicBool,
- rect: PropertyRect,
- scroll: PropertyFloat32,
- font_size: PropertyFloat32,
- line_height: PropertyFloat32,
- baseline: PropertyFloat32,
- timestamp_color: PropertyColor,
- text_color: PropertyColor,
- nick_colors: PropertyPtr,
- hi_bg_color: PropertyColor,
- z_index: PropertyUint32,
- debug: PropertyBool,
- scroll_start_accel: PropertyFloat32,
- scroll_resist: PropertyFloat32,
- select_hold_time: PropertyFloat32,
- /// Scroll accel
- motion_cv: Arc<CondVar>,
- speed: AtomicF32,
- mouse_btn_held: AtomicBool,
- /// Triggers the background loading task to wake up
- bgload_cv: Arc<CondVar>,
- /// We use it when we re-eval rect when its changed via property.
- parent_rect: SyncMutex<Option<Rectangle>>,
- }
- impl ChatView {
- pub async fn new(
- node: SceneNodeWeak,
- tree: sled::Tree,
- window_scale: PropertyFloat32,
- render_api: RenderApiPtr,
- text_shaper: TextShaperPtr,
- ex: ExecutorPtr,
- ) -> Pimpl {
- debug!(target: "ui::chatview", "ChatView::new()");
- let node_ref = &node.upgrade().unwrap();
- let rect = PropertyRect::wrap(node_ref, Role::Internal, "rect").unwrap();
- let scroll = PropertyFloat32::wrap(node_ref, Role::Internal, "scroll", 0).unwrap();
- let font_size = PropertyFloat32::wrap(node_ref, Role::Internal, "font_size", 0).unwrap();
- let line_height =
- PropertyFloat32::wrap(node_ref, Role::Internal, "line_height", 0).unwrap();
- let baseline = PropertyFloat32::wrap(node_ref, Role::Internal, "baseline", 0).unwrap();
- let timestamp_color =
- PropertyColor::wrap(node_ref, Role::Internal, "timestamp_color").unwrap();
- let text_color = PropertyColor::wrap(node_ref, Role::Internal, "text_color").unwrap();
- let nick_colors = node_ref.get_property("nick_colors").expect("ChatView::nick_colors");
- let hi_bg_color = PropertyColor::wrap(node_ref, Role::Internal, "hi_bg_color").unwrap();
- let z_index = PropertyUint32::wrap(node_ref, Role::Internal, "z_index", 0).unwrap();
- let debug = PropertyBool::wrap(node_ref, Role::Internal, "debug", 0).unwrap();
- let scroll_start_accel =
- PropertyFloat32::wrap(node_ref, Role::Internal, "scroll_start_accel", 0).unwrap();
- let scroll_resist =
- PropertyFloat32::wrap(node_ref, Role::Internal, "scroll_resist", 0).unwrap();
- let select_hold_time =
- PropertyFloat32::wrap(node_ref, Role::Internal, "select_hold_time", 0).unwrap();
- let node_name = node_ref.name.clone();
- let node_id = node_ref.id;
- let self_ = Arc::new_cyclic(|me: &Weak<Self>| {
- /*
- let me2 = me.clone();
- let insert_line_method_task =
- ex.spawn(
- async move { while Self::process_insert_line_method(&me2, &recvr).await {} },
- );
- */
- let me2 = me.clone();
- let motion_cv = Arc::new(CondVar::new());
- let cv = motion_cv.clone();
- let motion_task = ex.spawn(async move {
- loop {
- cv.wait().await;
- let Some(self_) = me2.upgrade() else {
- // Should not happen
- panic!("self destroyed before motion_task was stopped!");
- };
- self_.handle_movement().await;
- cv.reset();
- }
- });
- let me2 = me.clone();
- let bgload_cv = Arc::new(CondVar::new());
- let cv = bgload_cv.clone();
- let bgload_task = ex.spawn(async move {
- loop {
- cv.wait().await;
- let Some(self_) = me2.upgrade() else {
- // Should not happen
- panic!("self destroyed before bgload_task was stopped!");
- };
- self_.handle_bgload().await;
- cv.reset();
- }
- });
- let mut on_modify = OnModify::new(ex, node_name, node_id, me.clone());
- async fn reload_view(self_: Arc<ChatView>) {
- self_.scrollview(self_.scroll.get()).await;
- }
- on_modify.when_change(scroll.prop(), reload_view);
- //async fn redraw(self_: Arc<ChatView>) {
- // self_.redraw().await;
- //}
- //on_modify.when_change(rect.clone(), redraw);
- //on_modify.when_change(debug.prop(), redraw);
- let mut tasks = vec![/*insert_line_method_task,*/ motion_task, bgload_task];
- tasks.append(&mut on_modify.tasks);
- Self {
- node,
- tasks,
- render_api: render_api.clone(),
- text_shaper: text_shaper.clone(),
- tree,
- msgbuf: AsyncMutex::new(MessageBuffer::new(
- font_size.clone(),
- line_height.clone(),
- baseline.clone(),
- timestamp_color.clone(),
- text_color.clone(),
- nick_colors.clone(),
- hi_bg_color.clone(),
- debug.clone(),
- window_scale,
- render_api,
- text_shaper,
- )),
- dc_key: OsRng.gen(),
- mouse_pos: SyncMutex::new(Point::from([0., 0.])),
- touch_info: SyncMutex::new(None),
- touch_is_active: AtomicBool::new(false),
- rect,
- scroll,
- font_size,
- line_height,
- baseline,
- timestamp_color,
- text_color,
- nick_colors,
- hi_bg_color,
- z_index,
- debug,
- scroll_start_accel,
- scroll_resist,
- select_hold_time,
- motion_cv,
- speed: AtomicF32::new(0.),
- mouse_btn_held: AtomicBool::new(false),
- bgload_cv,
- parent_rect: SyncMutex::new(None),
- }
- });
- Pimpl::ChatView(self_)
- }
- async fn process_insert_line_method(
- me: &Weak<Self>,
- recvr: &async_channel::Receiver<Vec<u8>>,
- ) -> bool {
- let Ok(data) = recvr.recv().await else {
- debug!(target: "ui::chatview", "Event relayer closed");
- return false
- };
- fn decode_data(data: &[u8]) -> std::io::Result<(Timestamp, MessageId, String, String)> {
- let mut cur = Cursor::new(&data);
- let timestamp = Timestamp::decode(&mut cur)?;
- let message_id = MessageId::decode(&mut cur)?;
- let nick = String::decode(&mut cur)?;
- let text = String::decode(&mut cur)?;
- Ok((timestamp, message_id, nick, text))
- }
- let Ok((timestamp, message_id, nick, text)) = decode_data(&data) else {
- error!(target: "ui::chatview", "insert_line() method invalid arg data");
- return true
- };
- let Some(self_) = me.upgrade() else {
- // Should not happen
- panic!("self destroyed before touch_task was stopped!");
- };
- self_.handle_insert_line(timestamp, message_id, nick, text).await;
- true
- }
- /// Mark line as selected
- async fn select_line(&self, mut y: f32) {
- // The cursor is inside the rect. We just have to find which line it clicked.
- let scroll = self.scroll.get();
- let rect = self.rect.get();
- let bottom = scroll + rect.y + rect.h;
- assert!(bottom >= y);
- y = bottom - y;
- let mut msgbuf = self.msgbuf.lock().await;
- msgbuf.select_line(y).await;
- self.redraw_cached(&mut msgbuf).await;
- }
- fn end_touch_phase(&self, touch_y: f32) {
- // Now calculate scroll acceleration
- let touch_info = std::mem::replace(&mut *self.touch_info.lock().unwrap(), None);
- let Some(touch_info) = &touch_info else { return };
- self.touch_is_active.store(false, Ordering::Relaxed);
- // No scroll accel with selection mode
- if touch_info.is_select_mode.is_some() {
- return
- }
- let Some((time, sample_y)) = touch_info.first_sample() else { return };
- let dist = touch_y - sample_y;
- // Ignore sub-ms events
- if time < 1. {
- error!(target: "ui::chatview", "Received a sub-ms touch event!");
- return
- }
- //let speed = dist / time;
- //self.speed.fetch_add(speed, Ordering::Relaxed);
- //debug!(target: "ui::chatview", "speed = {dist} / {time} = {speed}");
- let accel = self.scroll_start_accel.get() * dist / time;
- let touch_time = touch_info.start_instant.elapsed();
- debug!(target: "ui::chatview", "accel = {dist} / {time} = {accel}, touch = {touch_time:?}");
- self.speed.fetch_add(accel, Ordering::Relaxed);
- self.motion_cv.notify();
- }
- async fn add_line_to_db(
- &self,
- timest: Timestamp,
- message_id: &MessageId,
- nick: &str,
- text: &str,
- ) -> bool {
- let timest = timest.to_be_bytes();
- assert_eq!(timest.len(), 8);
- let mut key = [0u8; 8 + 32];
- key[..8].clone_from_slice(×t);
- key[8..].clone_from_slice(message_id);
- // When does this return Err?
- let contains_key = self.tree.contains_key(&key);
- if contains_key.is_err() || contains_key.unwrap() {
- // Already exists
- return false
- }
- let msg = ChatMsg { nick: nick.to_string(), text: text.to_string() };
- let mut val = vec![];
- msg.encode(&mut val).unwrap();
- self.tree.insert(&key, val).unwrap();
- let _ = self.tree.flush_async().await;
- true
- }
- async fn handle_insert_line(
- &self,
- timest: Timestamp,
- message_id: MessageId,
- nick: String,
- text: String,
- ) {
- debug!(target: "ui::chatview", "handle_insert_line({timest}, {message_id:?}, {nick}, {text})");
- if !self.add_line_to_db(timest, &message_id, &nick, &text).await {
- // Already exists so bail
- debug!(target: "ui::chatview", "duplicate msg so bailing");
- return
- }
- // Add message to page
- let mut msgbuf = self.msgbuf.lock().await;
- msgbuf.insert_privmsg(timest, message_id, nick, text).await;
- self.redraw_cached(&mut msgbuf).await;
- self.bgload_cv.notify();
- }
- async fn handle_movement(&self) {
- // We need to fix this impl because it depends very much on the speed of the device
- // that it's running on.
- // Look into optimizing scrollview() so scrolling is smooth.
- // We could use skiplists to avoid looping from the very bottom.
- // So index 1 in the skiplist advances to 100px up... (or however much multiplier)
- loop {
- msleep(10).await;
- if self.touch_is_active.load(Ordering::Relaxed) {
- return
- }
- let mut speed = self.speed.load(Ordering::Relaxed);
- // Apply constant decel to speed
- speed *= self.scroll_resist.get();
- if speed.abs() < BIG_EPSILON {
- speed = 0.;
- }
- self.speed.store(speed, Ordering::Relaxed);
- // Finished
- if is_zero(speed) {
- return
- }
- let scroll = self.scroll.get() + speed;
- let dist = self.scrollview(scroll).await;
- // We reached the end so just stop
- if is_zero(dist) {
- self.speed.store(0., Ordering::Relaxed);
- return
- }
- }
- }
- async fn handle_bgload(&self) {
- //debug!(target: "ui::chatview", "ChatView::handle_bgload()");
- // Do we need to load some more?
- let scroll = self.scroll.get();
- let rect = self.rect.get();
- let top = scroll + rect.h;
- let preload_height = PRELOAD_PAGES as f32 * rect.h;
- let mut msgbuf = self.msgbuf.lock().await;
- let total_height = msgbuf.calc_total_height().await;
- if total_height > top + preload_height {
- // Nothing to do here
- //debug!(target: "ui::chatview", "bgloader: buffer is sufficient");
- return
- }
- // Keep loading until this is below 0
- let mut remaining_load_height = top + preload_height - total_height;
- //debug!(target: "ui::chatview", "bgloader: remaining px = {remaining_load_height}");
- let mut remaining_visible = top - total_height;
- // Get the current earliest timestamp
- let iter = match msgbuf.oldest_timestamp() {
- Some(oldest_timest) => {
- // iterate from there
- //debug!(target: "ui::chatview", "preloading from {oldest_timest}");
- let timest = (oldest_timest - 1).to_be_bytes();
- let mut key = [0u8; 8 + 32];
- key[..8].clone_from_slice(×t);
- let iter = self.tree.range(..key).rev();
- iter
- }
- None => {
- //debug!(target: "ui::chatview", "initial load");
- self.tree.iter().rev()
- }
- };
- for entry in iter {
- let Ok((k, v)) = entry else { break };
- assert_eq!(k.len(), 8 + 32);
- let timest_bytes: [u8; 8] = k[..8].try_into().unwrap();
- let message_id: MessageId = k[8..].try_into().unwrap();
- let timest = Timestamp::from_be_bytes(timest_bytes);
- let chatmsg: ChatMsg = deserialize(&v).unwrap();
- debug!(target: "ui::chatview", "{timest:?} {chatmsg:?}");
- let msg_height =
- msgbuf.push_privmsg(timest, message_id, chatmsg.nick, chatmsg.text).await;
- remaining_load_height -= msg_height;
- if remaining_load_height <= 0. {
- break
- }
- if remaining_visible > 0. {
- self.redraw_cached(&mut msgbuf).await;
- }
- remaining_visible -= msg_height;
- }
- }
- /// Descent = line height - baseline
- fn descent(&self) -> f32 {
- self.line_height.get() - self.baseline.get()
- }
- async fn scrollview(&self, mut scroll: f32) -> f32 {
- //debug!(target: "ui::chatview", "scrollview()");
- let old_scroll = self.scroll.get();
- let rect = self.rect.get();
- let mut msgbuf = self.msgbuf.lock().await;
- // 1/3 of time spent here ~1.5ms
- if let Some(new_scroll) = self.adjust_scroll(&mut msgbuf, scroll, rect.h).await {
- scroll = new_scroll;
- }
- // 2/3 of time spent here ~3.3ms
- self.redraw_cached(&mut msgbuf).await;
- self.scroll.set(scroll);
- self.bgload_cv.notify();
- scroll - old_scroll
- }
- /// Adjusts a proposed scroll value to clamp it within range. It will load pages until we
- /// either run out or we have enough, then checks scroll is within range.
- /// Returns None if the value is within range.
- async fn adjust_scroll(
- &self,
- msgbuf: &mut MessageBuffer,
- mut scroll: f32,
- rect_h: f32,
- ) -> Option<f32> {
- // We still wish to preload pages to fill the screen, so we just adjust it up to 0.
- let nonneg_scroll = max(scroll, 0.);
- if scroll < 0. {
- return Some(0.)
- }
- let total_height = msgbuf.calc_total_height().await;
- let max_allowed_scroll = if total_height > rect_h { total_height - rect_h } else { 0. };
- if scroll > max_allowed_scroll {
- scroll = max_allowed_scroll;
- assert!(scroll >= 0.);
- return Some(scroll)
- }
- // Unchanged
- None
- }
- /// Returns draw calls for drawing
- async fn get_meshes(
- &self,
- msgbuf: &mut MessageBuffer,
- rect: &Rectangle,
- ) -> (Vec<GfxDrawInstruction>, FreedData) {
- let scroll = self.scroll.get();
- let total_height = msgbuf.calc_total_height().await;
- // Use this to start from the top
- //let start_pos = if total_height < rect.h { total_height } else { rect.h };
- // We start from the bottom though
- let start_pos = rect.h;
- let mut instrs = vec![];
- //let mut old_drawmesh = vec![];
- let meshes = msgbuf.gen_meshes(rect, scroll).await;
- for (i, (y_pos, mesh)) in enumerate(meshes) {
- // Apply scroll and scissor
- // We use the scissor for scrolling
- // Because we use the scissor, our actual rect is now rect instead of parent_rect
- let off_x = 0.;
- // This calc decides whether scroll is in terms of pages or pixels
- let off_y = (scroll + start_pos - y_pos);
- let pos = Point::from([0., off_y]);
- instrs.push(GfxDrawInstruction::Move(pos));
- instrs.push(GfxDrawInstruction::Draw(mesh));
- }
- let freed = std::mem::take(&mut msgbuf.freed);
- (instrs, freed)
- }
- async fn redraw_cached(&self, msgbuf: &mut MessageBuffer) {
- let rect = self.rect.get();
- let (mut mesh_instrs, freed) = self.get_meshes(msgbuf, &rect).await;
- let mut instrs = vec![GfxDrawInstruction::ApplyView(rect)];
- instrs.append(&mut mesh_instrs);
- let draw_calls =
- vec![(self.dc_key, GfxDrawCall { instrs, dcs: vec![], z_index: self.z_index.get() })];
- self.render_api.replace_draw_calls(draw_calls);
- for buffer_id in freed.buffers {
- self.render_api.delete_buffer(buffer_id);
- }
- for texture_id in freed.textures {
- self.render_api.delete_texture(texture_id);
- }
- }
- /// Invalidates cache and redraws everything
- async fn redraw_all(&self) {
- debug!(target: "ui::chatview", "redraw()");
- // ... todo fin
- }
- }
- #[async_trait]
- impl UIObject for ChatView {
- fn z_index(&self) -> u32 {
- self.z_index.get()
- }
- async fn draw(&self, parent_rect: Rectangle) -> Option<DrawUpdate> {
- debug!(target: "ui::chatview", "ChatView::draw()");
- *self.parent_rect.lock().unwrap() = Some(parent_rect.clone());
- self.rect.eval(&parent_rect).ok()?;
- let rect = self.rect.get();
- let mut msgbuf = self.msgbuf.lock().await;
- msgbuf.adjust_window_scale().await;
- msgbuf.adjust_width(rect.w);
- msgbuf.clear_meshes();
- let mut scroll = self.scroll.get();
- if let Some(scroll) = self.adjust_scroll(&mut msgbuf, scroll, rect.h).await {
- self.scroll.set(scroll);
- }
- // We may need to load more messages since the screen size has changed.
- // Now we have updated all the values so it's safe to wake up here.
- self.bgload_cv.notify();
- let (mut mesh_instrs, freed) = self.get_meshes(&mut msgbuf, &rect).await;
- drop(msgbuf);
- let mut instrs = vec![GfxDrawInstruction::ApplyView(rect)];
- instrs.append(&mut mesh_instrs);
- Some(DrawUpdate {
- key: self.dc_key,
- draw_calls: vec![(
- self.dc_key,
- GfxDrawCall { instrs, dcs: vec![], z_index: self.z_index.get() },
- )],
- freed_textures: freed.textures,
- freed_buffers: freed.buffers,
- })
- }
- async fn handle_key_down(&self, key: KeyCode, mods: KeyMods, repeat: bool) -> bool {
- if repeat {
- return false
- }
- match key {
- KeyCode::PageUp => {
- let scroll = self.scroll.get() + 200.;
- self.scrollview(scroll).await;
- }
- KeyCode::PageDown => {
- let scroll = self.scroll.get() - 200.;
- self.scrollview(scroll).await;
- }
- _ => {}
- }
- true
- }
- async fn handle_mouse_btn_down(&self, btn: MouseButton, mouse_pos: Point) -> bool {
- if btn != MouseButton::Left {
- return false
- }
- let rect = self.rect.get();
- if !rect.contains(mouse_pos) {
- return false
- }
- self.select_line(mouse_pos.y).await;
- self.mouse_btn_held.store(true, Ordering::Relaxed);
- true
- }
- async fn handle_mouse_btn_up(&self, btn: MouseButton, mouse_pos: Point) -> bool {
- if btn != MouseButton::Left {
- return false
- }
- self.mouse_btn_held.store(false, Ordering::Relaxed);
- false
- }
- async fn handle_mouse_move(&self, mouse_pos: Point) -> bool {
- //debug!(target: "ui::chatview", "handle_mouse_move({mouse_x}, {mouse_y})");
- // We store the mouse pos for use in handle_mouse_wheel()
- *self.mouse_pos.lock().unwrap() = mouse_pos.clone();
- if !self.mouse_btn_held.load(Ordering::Relaxed) {
- return false
- }
- let rect = self.rect.get();
- if !rect.contains(mouse_pos) {
- return false
- }
- self.select_line(mouse_pos.y).await;
- false
- }
- async fn handle_mouse_wheel(&self, wheel_pos: Point) -> bool {
- //debug!(target: "ui::chatview", "handle_mouse_wheel({wheel_x}, {wheel_y})");
- let rect = self.rect.get();
- let mouse_pos = self.mouse_pos.lock().unwrap().clone();
- if !rect.contains(mouse_pos) {
- //debug!(target: "ui::chatview", "not inside rect");
- return false
- }
- self.speed.fetch_add(wheel_pos.y * self.scroll_start_accel.get(), Ordering::Relaxed);
- self.motion_cv.notify();
- true
- }
- async fn handle_touch(&self, phase: TouchPhase, id: u64, touch_pos: Point) -> bool {
- // Ignore multi-touch
- if id != 0 {
- return false
- }
- let rect = self.rect.get();
- //debug!(target: "ui::chatview", "handle_touch({phase:?}, {touch_x}, {touch_y})");
- let touch_y = touch_pos.y;
- if !rect.contains(touch_pos) {
- match phase {
- TouchPhase::Started => *self.touch_info.lock().unwrap() = None,
- _ => self.end_touch_phase(touch_y),
- }
- return false
- }
- let select_hold_time = self.select_hold_time.get();
- // Simulate mouse events
- match phase {
- TouchPhase::Started => {
- self.touch_is_active.store(true, Ordering::Relaxed);
- let mut touch_info = self.touch_info.lock().unwrap();
- *touch_info = Some(TouchInfo::new(self.scroll.get(), touch_y));
- }
- TouchPhase::Moved => {
- let (start_scroll, start_y, start_elapsed, do_update, is_select_mode) = {
- let mut touch_info = self.touch_info.lock().unwrap();
- let Some(touch_info) = &mut *touch_info else { return false };
- touch_info.last_y = touch_y;
- let start_scroll = touch_info.start_scroll;
- let start_y = touch_info.start_y;
- let start_elapsed = touch_info.start_instant.elapsed().as_millis_f32();
- if start_elapsed > select_hold_time && touch_info.is_select_mode.is_none() {
- // Did we move?
- if (touch_y - start_y).abs() < BIG_EPSILON {
- touch_info.is_select_mode = Some(true);
- } else {
- touch_info.is_select_mode = Some(false);
- }
- }
- let is_select_mode = touch_info.is_select_mode.clone();
- touch_info.push_sample(touch_y);
- // Only update screen every 20ms. Avoid wasting cycles.
- let last_elapsed = touch_info.last_instant.elapsed().as_millis_f32();
- let do_update = last_elapsed > 20.;
- if do_update {
- touch_info.last_instant = std::time::Instant::now();
- }
- (start_scroll, start_y, start_elapsed, do_update, is_select_mode)
- };
- debug!(target: "ui::chatview", "touch phase moved, is_select_mode={is_select_mode:?}");
- // When scrolling if we suddenly grab the screen for more than a brief period
- // of time then stop the scrolling completely.
- if start_elapsed > 200. {
- //debug!(target: "ui::chatview", "Stopping scroll accel");
- self.speed.store(0., Ordering::Relaxed);
- }
- // Only update every so often to prevent wasting resources.
- if !do_update {
- return true
- }
- // We are in selection mode so don't scroll the screen until touch phase ends.
- if let Some(is_select_mode) = is_select_mode &&
- is_select_mode
- {
- self.select_line(touch_y).await;
- return true
- }
- let dist = touch_y - start_y;
- // No movement so just return
- if dist.abs() < BIG_EPSILON {
- return true
- }
- let scroll = start_scroll + dist;
- // Redraws the screen from the cache
- self.scrollview(scroll).await;
- }
- TouchPhase::Ended | TouchPhase::Cancelled => {
- self.end_touch_phase(touch_y);
- }
- }
- true
- }
- }
|