1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
//! Internal and external event system
//!
//!
//! This is all kinda WIP, but the bones are there.

use crate::{ElementId, ScopeId};
use std::{any::Any, cell::Cell, rc::Rc, sync::Arc};

pub(crate) struct BubbleState {
    pub canceled: Cell<bool>,
}

impl BubbleState {
    pub fn new() -> Self {
        Self {
            canceled: Cell::new(false),
        }
    }
}

/// User Events are events that are shuttled from the renderer into the VirtualDom trhough the scheduler channel.
///
/// These events will be passed to the appropriate Element given by `mounted_dom_id` and then bubbled up through the tree
/// where each listener is checked and fired if the event name matches.
///
/// It is the expectation that the event name matches the corresponding event listener, otherwise Dioxus will panic in
/// attempting to downcast the event data.
///
/// Because Event Data is sent across threads, it must be `Send + Sync`. We are hoping to lift the `Sync` restriction but
/// `Send` will not be lifted. The entire `UserEvent` must also be `Send + Sync` due to its use in the scheduler channel.
///
/// # Example
/// ```rust, ignore
/// fn App(cx: Scope) -> Element {
///     rsx!(cx, div {
///         onclick: move |_| println!("Clicked!")
///     })
/// }
///
/// let mut dom = VirtualDom::new(App);
/// let mut scheduler = dom.get_scheduler_channel();
/// scheduler.unbounded_send(SchedulerMsg::UiEvent(
///     UserEvent {
///         scope_id: None,
///         priority: EventPriority::Medium,
///         name: "click",
///         element: Some(ElementId(0)),
///         data: Arc::new(ClickEvent { .. })
///     }
/// )).unwrap();
/// ```
#[derive(Debug)]
pub struct UserEvent {
    /// The originator of the event trigger if available
    pub scope_id: Option<ScopeId>,

    /// The priority of the event to be scheduled around ongoing work
    pub priority: EventPriority,

    /// The optional real node associated with the trigger
    pub element: Option<ElementId>,

    /// The event type IE "onclick" or "onmouseover"
    pub name: &'static str,

    /// The event data to be passed onto the event handler
    pub data: Arc<dyn Any + Send + Sync>,
}

/// 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 RealDOM. 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,
}

pub struct AnyEvent {
    pub(crate) bubble_state: Rc<BubbleState>,
    pub(crate) data: Arc<dyn Any + Send + Sync>,
}

impl AnyEvent {
    pub fn downcast<T: Send + Sync + 'static>(self) -> Option<UiEvent<T>> {
        let AnyEvent { data, bubble_state } = self;

        if let Ok(data) = data.downcast::<T>() {
            Some(UiEvent { bubble_state, data })
        } else {
            None
        }
    }
}

pub struct UiEvent<T> {
    pub data: Arc<T>,

    #[allow(unused)]
    bubble_state: Rc<BubbleState>,
}

impl<T> std::ops::Deref for UiEvent<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        self.data.as_ref()
    }
}

impl<T> UiEvent<T> {
    /// Prevent this event from bubbling up the tree.
    pub fn cancel_bubble(&self) {
        self.bubble_state.canceled.set(true);
    }
}