/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2026 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 . */ use async_trait::async_trait; use futures::stream::{FuturesUnordered, StreamExt}; use miniquad::{KeyCode, KeyMods, MouseButton}; use std::sync::{Arc, OnceLock, Weak}; use crate::{ gfx::{DrawCall, Point, Rectangle}, prop::{BatchGuardPtr, ModifyAction, PropertyAtomicGuard, PropertyPtr, Role}, scene::{Pimpl, SceneNode as SceneNode3, SceneNodePtr, SceneNodeWeak}, util::i18n::I18nBabelFish, ExecutorPtr, }; static LONG_PRESS_TIMEOUT: OnceLock = OnceLock::new(); /// The system long-press timeout in milliseconds. Queried once from /// `ViewConfiguration.getLongPressTimeout()` on Android, defaults to 400 /// on other platforms. pub fn long_press_timeout() -> u32 { *LONG_PRESS_TIMEOUT.get_or_init(|| { #[cfg(target_os = "android")] { crate::android::get_long_press_timeout() } #[cfg(not(target_os = "android"))] { 400 } }) } mod button; pub use button::{Button, ButtonPtr}; pub mod chatview; pub use chatview::{ChatView, ChatViewPtr}; pub mod tokentable; pub use tokentable::{TokenRow, TokenTable, TokenTablePtr}; mod edit; pub use edit::{BaseEdit, BaseEditPtr, BaseEditType}; pub mod emoji_picker; pub use emoji_picker::{EmojiPicker, EmojiPickerPtr}; pub mod gesture; // The full config vocabulary is re-exported for widgets adopting // per-node recognizer configs (TapCfg/DragCfg axes + direction); the // crate is a binary so not every name has an in-crate use yet. #[allow(unused_imports)] pub use gesture::{ Axes, Direction, DragCfg, GestureAction, GestureConstants, GestureSession, GestureSessionPtr, GestureSet, GestureTarget, LongPressCfg, TapCfg, }; mod image; #[allow(unused_imports)] pub use image::{Image, ImagePtr}; mod vid; #[allow(unused_imports)] pub use vid::{Video, VideoPtr}; mod vector_art; pub use vector_art::{ shape::{ShapeVertex, VectorShape}, VectorArt, VectorArtPtr, }; mod layer; pub use layer::{Layer, LayerPtr}; mod scroll_layer; pub use scroll_layer::{ScrollLayer, ScrollLayerPtr}; mod shortcut; pub use shortcut::{Shortcut, ShortcutPtr}; mod menu; pub use menu::{Menu, MenuPtr}; mod text; pub use text::{Text, TextPtr}; mod text_scramble; pub use text_scramble::{TextScramble, TextScramblePtr}; mod win; pub use win::{Window, WindowPtr}; macro_rules! e { ($($arg:tt)*) => { error!(target: "scene::on_modify", $($arg)*); } } macro_rules! t { ($($arg:tt)*) => { trace!(target: "scene::on_modify", $($arg)*); } } /// Handle for requesting a redraw pass from the root window's draw loop. /// Cheap to clone. The underlying queue is bounded(1), so triggers sent /// while a pass is running or pending are coalesced into a single /// additional pass. Property mutations SHOULD be made through /// `make_guard()` so the trigger fires once, after the whole update /// chain has settled. State mutations must happen before calling /// `trigger()` so the resulting pass observes them. #[derive(Clone)] pub struct RedrawTrigger(async_channel::Sender<()>); impl RedrawTrigger { /// Create the trigger handle and the receiver consumed by the draw loop. pub fn new() -> (Self, async_channel::Receiver<()>) { let (tx, rx) = async_channel::bounded(1); (Self(tx), rx) } /// Request a draw pass without a batch scope. Only for mutations that /// need no `PropertyAtomicGuard` (plain fields, caches): the trigger is /// enqueued immediately, so all state must already be settled. For /// property updates use `make_guard()` instead, which defers the /// trigger to end-of-batch. /// /// Never blocks. A trigger is only dropped when another is already /// queued, which is equivalent: the queued token guarantees a pass /// that starts after this call, and since callers mutate state before /// triggering, that pass observes the mutation. /// /// Correctness relies on the draw loop draining exactly one token per /// iteration *before* drawing. Do not change the loop to recv after /// the draw or to drain multiple tokens per pass: a full channel means /// a pass is guaranteed, and that guarantee is what makes dropped /// triggers safe. Blocking here would also self-deadlock, since draws /// can trigger further passes. pub fn trigger(&self) { let _ = self.0.try_send(()); } /// Open a property-update batch bound to this trigger. Property /// notifications are deferred until the batch — including any batches /// spawned from it by property-change reactions holding the batch /// guard — completes, and then exactly one redraw trigger is enqueued. /// Use this instead of manual `trigger()` calls around property /// mutations so a pass can never observe the intermediate state of a /// multi-step update. pub fn make_guard(&self, debug_str: Option<&'static str>) -> PropertyAtomicGuard { let redraw = self.0.clone(); PropertyAtomicGuard::new(Box::new(move |_| { if let Some(tag) = debug_str { t!("Redraw batch ({tag}) ended, triggering redraw"); } let _ = redraw.try_send(()); })) } } #[async_trait] pub trait UIObject: Sync { fn priority(&self) -> u32; /// Called after schema and scenegraph is init but before miniquad starts. fn init(&self) {} /// Done after miniquad has started and the first window draw has been done. async fn start(self: Arc, _ex: ExecutorPtr) {} /// Clear all buffers and caches fn stop(&self) {} async fn draw( &self, _parent_rect: Rectangle, _atom: &mut PropertyAtomicGuard, ) -> Option { None } async fn handle_char(&self, _key: char, _mods: KeyMods, _repeat: bool) -> bool { false } async fn handle_key_down(&self, _key: KeyCode, _mods: KeyMods, _repeat: bool) -> bool { false } async fn handle_key_up(&self, _key: KeyCode, _mods: KeyMods) -> bool { false } async fn handle_mouse_btn_down(&self, _btn: MouseButton, _mouse_pos: Point) -> bool { false } async fn handle_mouse_btn_up(&self, _btn: MouseButton, _mouse_pos: Point) -> bool { false } async fn handle_mouse_move(&self, _mouse_pos: Point) -> bool { false } async fn handle_mouse_wheel(&self, _wheel_pos: Point) -> bool { false } /// The gestures this widget accepts. Non-participating widgets /// return [`GestureSet::NONE`] and are inert. fn gesture_set(&self) -> GestureSet { GestureSet::NONE } /// Whether this widget is a gesture target at `pos` (given in the /// widget's parent coordinate space, like `handle_gesture`). fn gesture_hit_test(&self, _pos: Point) -> bool { false } /// Containers: descend the gesture chain under `pos` (the /// container's parent space), translating coordinates. The default /// is a no-op for leaf widgets. fn gesture_descend(&self, _pos: Point, _offset: Point, _chain: &mut Vec) {} async fn handle_gesture(&self, _gesture: GestureAction) -> bool { false } fn set_i18n(&self, _i18n_fish: &I18nBabelFish) {} } pub struct DrawUpdate { pub key: u64, pub draw_calls: Vec<(u64, DrawCall)>, } pub struct OnModify { ex: ExecutorPtr, #[allow(dead_code)] node: SceneNodeWeak, me: Weak, pub tasks: Vec>, } impl OnModify { pub fn new(ex: ExecutorPtr, node: SceneNodeWeak, me: Weak) -> Self { Self { ex, node, me, tasks: vec![] } } pub fn when_change( &mut self, prop: PropertyPtr, f: impl Fn(Arc, BatchGuardPtr) -> F + Send + 'static, ) where F: std::future::Future + Send + 'static, { self.when_change_impl(prop, false, f) } /// Like `when_change`, but also skips `Role::Internal` modifications of /// dependencies. Draw-pass-migrated widgets want this: internal sets are /// eval echoes (typically produced by the draw pass itself), so reacting /// to them would queue a pass for every pass, forever. External mutation /// sites (handlers, resize/insets tasks) trigger passes explicitly. pub fn when_change_external( &mut self, prop: PropertyPtr, f: impl Fn(Arc, BatchGuardPtr) -> F + Send + 'static, ) where F: std::future::Future + Send + 'static, { self.when_change_impl(prop, true, f) } fn when_change_impl( &mut self, prop: PropertyPtr, skip_internal: bool, f: impl Fn(Arc, BatchGuardPtr) -> F + Send + 'static, ) where F: std::future::Future + Send + 'static, { let mut on_modify_subs = vec![(Arc::downgrade(&prop), None, prop.subscribe_modify())]; for dep in prop.get_depends() { let Some(dep_prop) = dep.prop.upgrade() else { continue }; on_modify_subs.push((dep.prop, Some(dep.i), dep_prop.subscribe_modify())); } let me = self.me.clone(); let task = self.ex.spawn(async move { loop { let mut poll_queues = FuturesUnordered::new(); for (i, (prop_weak, prop_i, on_modify_sub)) in on_modify_subs.iter().enumerate() { let recv = on_modify_sub.receive(); poll_queues.push(async move { let (role, action, batch_guard) = recv.await.ok()?; Some((i, prop_weak, prop_i, role, action, batch_guard)) }); } let Some(Some((idx, prop_weak, prop_i, role, action, batch_guard))) = poll_queues.next().await else { e!("Property {:?} on_modify pipe is broken", prop); return }; // Skip internal messages from ourselves or explicitly marked ignored. // Draw-pass widgets also skip internal dependency echoes. if (idx == 0 && role == Role::Internal) || (skip_internal && role == Role::Internal) || role == Role::Ignored { continue } if let Some(prop_i) = prop_i { match action { ModifyAction::Set(i) => if *prop_i != i { continue }, ModifyAction::SetCache(idxs) => if !idxs.contains(prop_i) { continue } _ => continue } } if idx == 0 { t!("Property {:?} modified [depend_idx={idx}, role={role:?}]", prop); } else { t!( "Property {:?} modified -> triggering {:?} [depend_idx={idx}, role={role:?}]", prop_weak.upgrade(), prop ); } let Some(self_) = me.upgrade() else { // Normally unreachable: an owner is dropped only after // stop() cleared its modify tasks, so an alive task // implies an alive owner. Runtime node removal // (netdebug rmnode) breaks that: stop() merely drops // the Task handles, and a future that is mid-poll on // a worker thread keeps running until its next yield, // which can carry it past this upgrade after the last // Arc is gone (this future holds only weak refs). // With no owner there is nothing left to notify, so // exit quietly instead of panicking. warn!( target: "scene::on_modify", "Property {:?} owner destroyed before modify_task was stopped", prop ); return }; //debug!(target: "app", "property modified"); f(self_, batch_guard).await; } }); self.tasks.push(task); } } pub fn get_ui_object_ptr(node: &SceneNode3) -> Arc { match node.pimpl() { Pimpl::Layer(obj) => obj.clone(), Pimpl::ScrollLayer(obj) => obj.clone(), Pimpl::VectorArt(obj) => obj.clone(), Pimpl::Text(obj) => obj.clone(), Pimpl::TextScramble(obj) => obj.clone(), Pimpl::Edit(obj) => obj.clone(), Pimpl::Image(obj) => obj.clone(), Pimpl::Video(obj) => obj.clone(), Pimpl::Button(obj) => obj.clone(), Pimpl::EmojiPicker(obj) => obj.clone(), Pimpl::Shortcut(obj) => obj.clone(), Pimpl::Menu(obj) => obj.clone(), Pimpl::TokenTable(obj) => obj.clone(), Pimpl::ChatView(obj) => obj.clone(), Pimpl::PrivMsgNode(obj) => obj.clone(), Pimpl::DateMsgNode(obj) => obj.clone(), Pimpl::FileMsgNode(obj) => obj.clone(), _ => panic!("unhandled type for get_ui_object: {node:?}"), } } pub fn get_ui_object3<'a>(node: &'a SceneNode3) -> &'a dyn UIObject { match node.pimpl() { Pimpl::Layer(obj) => obj.as_ref(), Pimpl::ScrollLayer(obj) => obj.as_ref(), Pimpl::VectorArt(obj) => obj.as_ref(), Pimpl::Text(obj) => obj.as_ref(), Pimpl::TextScramble(obj) => obj.as_ref(), Pimpl::Edit(obj) => obj.as_ref(), Pimpl::Image(obj) => obj.as_ref(), Pimpl::Video(obj) => obj.as_ref(), Pimpl::Button(obj) => obj.as_ref(), Pimpl::EmojiPicker(obj) => obj.as_ref(), Pimpl::Shortcut(obj) => obj.as_ref(), Pimpl::Menu(obj) => obj.as_ref(), Pimpl::TokenTable(obj) => obj.as_ref(), Pimpl::ChatView(obj) => obj.as_ref(), Pimpl::PrivMsgNode(obj) => obj.as_ref(), Pimpl::DateMsgNode(obj) => obj.as_ref(), Pimpl::FileMsgNode(obj) => obj.as_ref(), _ => panic!("unhandled type for get_ui_object: {node:?}"), } } pub fn get_children_ordered(node: &SceneNode3) -> Vec { let mut child_infs = vec![]; for child in node.get_children() { let obj = get_ui_object3(&child); let priority = obj.priority(); child_infs.push((child, priority)); } child_infs.sort_unstable_by_key(|(_, priority)| *priority); let nodes = child_infs.into_iter().rev().map(|(node, _)| node).collect(); nodes }