175 lines
5.6 KiB
Rust
175 lines
5.6 KiB
Rust
use bumpalo::boxed::Box as BumpBox;
|
|
use std::{
|
|
any::Any,
|
|
cell::{Cell, RefCell},
|
|
fmt::Debug,
|
|
rc::Rc,
|
|
};
|
|
|
|
pub struct UiEvent<T: 'static> {
|
|
pub(crate) bubble_state: Cell<bool>,
|
|
pub(crate) data: Rc<T>,
|
|
}
|
|
impl<T> Clone for UiEvent<T> {
|
|
fn clone(&self) -> Self {
|
|
Self {
|
|
bubble_state: self.bubble_state.clone(),
|
|
data: self.data.clone(),
|
|
}
|
|
}
|
|
}
|
|
impl<T> UiEvent<T> {
|
|
pub fn cancel_bubble(&self) {
|
|
self.bubble_state.set(false);
|
|
}
|
|
}
|
|
|
|
impl<T> std::ops::Deref for UiEvent<T> {
|
|
type Target = Rc<T>;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.data
|
|
}
|
|
}
|
|
|
|
impl<T: Debug> std::fmt::Debug for UiEvent<T> {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("UiEvent")
|
|
.field("bubble_state", &self.bubble_state)
|
|
.field("data", &self.data)
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
/// Priority of Event Triggers.
|
|
///
|
|
/// Internally, Dioxus will abort work that's taking too long if new, more important work arrives. Unlike React, Dioxus
|
|
/// won't be afraid to pause work or flush changes to the Real Dom. This is called "cooperative scheduling". Some Renderers
|
|
/// implement this form of scheduling internally, however Dioxus will perform its own scheduling as well.
|
|
///
|
|
/// The ultimate goal of the scheduler is to manage latency of changes, prioritizing "flashier" changes over "subtler" changes.
|
|
///
|
|
/// React has a 5-tier priority system. However, they break things into "Continuous" and "Discrete" priority. For now,
|
|
/// we keep it simple, and just use a 3-tier priority system.
|
|
///
|
|
/// - `NoPriority` = 0
|
|
/// - `LowPriority` = 1
|
|
/// - `NormalPriority` = 2
|
|
/// - `UserBlocking` = 3
|
|
/// - `HighPriority` = 4
|
|
/// - `ImmediatePriority` = 5
|
|
///
|
|
/// We still have a concept of discrete vs continuous though - discrete events won't be batched, but continuous events will.
|
|
/// This means that multiple "scroll" events will be processed in a single frame, but multiple "click" events will be
|
|
/// flushed before proceeding. Multiple discrete events is highly unlikely, though.
|
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
|
|
pub enum EventPriority {
|
|
/// Work that must be completed during the EventHandler phase.
|
|
///
|
|
/// Currently this is reserved for controlled inputs.
|
|
Immediate = 3,
|
|
|
|
/// "High Priority" work will not interrupt other high priority work, but will interrupt medium and low priority work.
|
|
///
|
|
/// This is typically reserved for things like user interaction.
|
|
///
|
|
/// React calls these "discrete" events, but with an extra category of "user-blocking" (Immediate).
|
|
High = 2,
|
|
|
|
/// "Medium priority" work is generated by page events not triggered by the user. These types of events are less important
|
|
/// than "High Priority" events and will take precedence over low priority events.
|
|
///
|
|
/// This is typically reserved for VirtualEvents that are not related to keyboard or mouse input.
|
|
///
|
|
/// React calls these "continuous" events (e.g. mouse move, mouse wheel, touch move, etc).
|
|
Medium = 1,
|
|
|
|
/// "Low Priority" work will always be preempted unless the work is significantly delayed, in which case it will be
|
|
/// advanced to the front of the work queue until completed.
|
|
///
|
|
/// The primary user of Low Priority work is the asynchronous work system (Suspense).
|
|
///
|
|
/// This is considered "idle" work or "background" work.
|
|
Low = 0,
|
|
}
|
|
|
|
// ensures a strict descendant relationship
|
|
// returns false if the paths are equal
|
|
pub fn is_path_ascendant(small: &[u8], big: &[u8]) -> bool {
|
|
if small.len() >= big.len() {
|
|
return false;
|
|
}
|
|
|
|
small == &big[..small.len()]
|
|
}
|
|
|
|
pub type InternalHandler<'bump> = &'bump RefCell<Option<InternalListenerCallback<'bump>>>;
|
|
type InternalListenerCallback<'bump> = BumpBox<'bump, dyn FnMut(&'bump dyn Any) + 'bump>;
|
|
type ExternalListenerCallback<'bump, T> = BumpBox<'bump, dyn FnMut(T) + 'bump>;
|
|
|
|
/// The callback type generated by the `rsx!` macro when an `on` field is specified for components.
|
|
///
|
|
/// This makes it possible to pass `move |evt| {}` style closures into components as property fields.
|
|
///
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```rust, ignore
|
|
///
|
|
/// rsx!{
|
|
/// MyComponent { onclick: move |evt| log::info!("clicked"), }
|
|
/// }
|
|
///
|
|
/// #[derive(Props)]
|
|
/// struct MyProps<'a> {
|
|
/// onclick: EventHandler<'a, MouseEvent>,
|
|
/// }
|
|
///
|
|
/// fn MyComponent(cx: Scope<'a, MyProps<'a>>) -> Element {
|
|
/// cx.render(rsx!{
|
|
/// button {
|
|
/// onclick: move |evt| cx.props.onclick.call(evt),
|
|
/// }
|
|
/// })
|
|
/// }
|
|
///
|
|
/// ```
|
|
pub struct EventHandler<'bump, T = ()> {
|
|
/// The (optional) callback that the user specified
|
|
/// Uses a `RefCell` to allow for interior mutability, and FnMut closures.
|
|
pub callback: RefCell<Option<ExternalListenerCallback<'bump, T>>>,
|
|
}
|
|
|
|
impl<'a, T> Default for EventHandler<'a, T> {
|
|
fn default() -> Self {
|
|
Self {
|
|
callback: RefCell::new(None),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> EventHandler<'_, T> {
|
|
/// Call this event handler with the appropriate event type
|
|
pub fn call(&self, event: T) {
|
|
if let Some(callback) = self.callback.borrow_mut().as_mut() {
|
|
callback(event);
|
|
}
|
|
}
|
|
|
|
/// Forcibly drop the internal handler callback, releasing memory
|
|
pub fn release(&self) {
|
|
self.callback.replace(None);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn matches_slice() {
|
|
let left = &[1, 2, 3];
|
|
let right = &[1, 2, 3, 4, 5];
|
|
assert!(is_path_ascendant(left, right));
|
|
assert!(!is_path_ascendant(right, left));
|
|
assert!(!is_path_ascendant(left, left));
|
|
|
|
assert!(is_path_ascendant(&[1, 2], &[1, 2, 3, 4, 5]));
|
|
}
|