/* 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