From 4a0bb8cf84da7960d7090de3d5f95248318540a3 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 8 Oct 2021 16:01:13 -0400 Subject: [PATCH] wip: portals --- docs/main-concepts/13-subtrees.md | 54 ++ packages/core/src/context.rs | 40 ++ packages/core/src/diff.rs | 15 +- packages/core/src/diff_stack.rs | 7 + packages/core/src/events.old.rs | 1023 ----------------------------- packages/core/src/mutations.rs | 1 - packages/core/src/scheduler.rs | 3 + packages/core/src/scope.rs | 37 +- packages/core/src/virtual_dom.rs | 1 + 9 files changed, 154 insertions(+), 1027 deletions(-) create mode 100644 docs/main-concepts/13-subtrees.md delete mode 100644 packages/core/src/events.old.rs diff --git a/docs/main-concepts/13-subtrees.md b/docs/main-concepts/13-subtrees.md new file mode 100644 index 000000000..e6dbc03e4 --- /dev/null +++ b/docs/main-concepts/13-subtrees.md @@ -0,0 +1,54 @@ +# Subtrees + +One way of extending the Dioxus VirtualDom is through the use of "Subtrees." Subtrees are chunks of the VirtualDom tree distinct from the rest of the tree. They still participate in event bubbling, diffing, etc, but will have a separate set of edits generated during the diff phase. + +For a VirtualDom that has a root tree with two subtrees, the edits follow a pattern of: + +Root +-> Tree 1 +-> Tree 2 +-> Original root tree + +- Root edits +- Tree 1 Edits +- Tree 2 Edits +- Root Edits + +The goal of this functionality is to enable things like Portals, Windows, and inline alternative renderers without needing to spin up a new VirtualDom. + +With the right renderer plugins, a subtree could be rendered as anything - a 3D scene, SVG, or even as the contents of a new window or modal. This functionality is similar to "Portals" in React, but much more "renderer agnostic." Portals, by nature, are not necessarily cross-platform and rely on renderer functionality, so it makes sense to abstract their purpose into the subtree concept. + +The desktop renderer comes pre-loaded with the window and notification subtree plugins, making it possible to render subtrees into entirely different windows. + +Subtrees also solve the "bridging" issues in React where two different renderers need two different VirtualDoms to work properly. In Dioxus, you only ever need one VirtualDom and the right renderer plugins. + + +## API + +Due to their importance in the hierarchy, Components - not nodes - are treated as subtree roots. + + +```rust + +fn Subtree

(cx: Context, props: P) -> DomTree { + +} + +fn Window() -> DomTree { + Subtree { + onassign: move |e| { + // create window + } + children() + } +} + +fn 3dRenderer -> DomTree { + Subtree { + onassign: move |e| { + // initialize bevy + } + } +} + +``` diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index b3df1af3f..dd35ce4b9 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -239,6 +239,46 @@ impl<'src> Context<'src> { ) } + /// Create a new subtree with this scope as the root of the subtree. + /// + /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route + /// the mutations to the correct window/portal/subtree. + /// + /// This method + /// + /// # Example + /// + /// ```rust + /// static App: FC<()> = |cx, props| { + /// let id = cx.get_current_subtree(); + /// let id = cx.use_create_subtree(); + /// subtree { + /// + /// } + /// rsx!(cx, div { "Subtree {id}"}) + /// }; + /// ``` + pub fn use_create_subtree(self) -> Option { + self.scope.new_subtree() + } + + /// Get the subtree ID that this scope belongs to. + /// + /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route + /// the mutations to the correct window/portal/subtree. + /// + /// # Example + /// + /// ```rust + /// static App: FC<()> = |cx, props| { + /// let id = cx.get_current_subtree(); + /// rsx!(cx, div { "Subtree {id}"}) + /// }; + /// ``` + pub fn get_current_subtree(self) -> u32 { + self.scope.subtree() + } + /// Store a value between renders /// /// This is *the* foundational hook for all other hooks. diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 6c6be4dd3..bb959fe45 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -352,12 +352,12 @@ impl<'bump> DiffMachine<'bump> { let parent_scope = self.vdom.get_scope(parent_idx).unwrap(); let new_idx = self.vdom.insert_scope_with_key(|new_idx| { - let height = parent_scope.height + 1; Scope::new( caller, new_idx, Some(parent_idx), - height, + parent_scope.height + 1, + parent_scope.subtree(), ScopeChildren(vcomponent.children), shared, ) @@ -384,6 +384,17 @@ impl<'bump> DiffMachine<'bump> { // Take the node that was just generated from running the component let nextnode = new_component.frames.fin_head(); self.stack.create_component(new_idx, nextnode); + + // + /* + tree_item { + + } + + */ + if new_component.is_subtree_root.get() { + self.stack.push_subtree(); + } } // Finally, insert this scope as a seen node. diff --git a/packages/core/src/diff_stack.rs b/packages/core/src/diff_stack.rs index 3843bb069..3d8d6e81f 100644 --- a/packages/core/src/diff_stack.rs +++ b/packages/core/src/diff_stack.rs @@ -76,6 +76,13 @@ impl<'bump> DiffStack<'bump> { } } + pub fn push_subtree(&mut self) { + self.nodes_created_stack.push(0); + self.instructions.push(DiffInstruction::Mount { + and: MountType::Append, + }); + } + pub fn push_nodes_created(&mut self, count: usize) { self.nodes_created_stack.push(count); } diff --git a/packages/core/src/events.old.rs b/packages/core/src/events.old.rs deleted file mode 100644 index a0c97bec9..000000000 --- a/packages/core/src/events.old.rs +++ /dev/null @@ -1,1023 +0,0 @@ -//! This module provides a set of common events for all Dioxus apps to target, regardless of host platform. -//! ------------------------------------------------------------------------------------------------------- -//! -//! 3rd party renderers are responsible for converting their native events into these virtual event types. Events might -//! be heavy or need to interact through FFI, so the events themselves are designed to be lazy. -use crate::{ - innerlude::Listener, - innerlude::{ElementId, NodeFactory, ScopeId}, -}; -use bumpalo::boxed::Box as BumpBox; -use std::{ - any::Any, - cell::{Cell, RefCell}, - fmt::Debug, - ops::Deref, - rc::Rc, - sync::Arc, -}; - -#[derive(Debug)] -pub struct UserEvent { - /// The originator of the event trigger - pub scope: ScopeId, - - /// The optional real node associated with the trigger - pub mounted_dom_id: Option, - - /// The event type IE "onclick" or "onmouseover" - /// - /// The name that the renderer will use to mount the listener. - pub name: &'static str, - - /// The type of event - pub event: SyntheticEvent, -} - -pub enum SyntheticEvent { - AnimationEvent(on::AnimationEvent), - ClipboardEvent(on::ClipboardEvent), - CompositionEvent(on::CompositionEvent), - FocusEvent(on::FocusEvent), - FormEvent(on::FormEvent), - KeyboardEvent(on::KeyboardEvent), - GenericEvent(on::GenericEvent), - TouchEvent(on::TouchEvent), - ToggleEvent(on::ToggleEvent), - MediaEvent(on::MediaEvent), - MouseEvent(on::MouseEvent), - WheelEvent(on::WheelEvent), - SelectionEvent(on::SelectionEvent), - TransitionEvent(on::TransitionEvent), - PointerEvent(on::PointerEvent), -} -// ImageEvent(event_data::ImageEvent), - -impl std::fmt::Debug for SyntheticEvent { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let name = match self { - SyntheticEvent::ClipboardEvent(_) => "ClipboardEvent", - SyntheticEvent::CompositionEvent(_) => "CompositionEvent", - SyntheticEvent::KeyboardEvent(_) => "KeyboardEvent", - SyntheticEvent::FocusEvent(_) => "FocusEvent", - SyntheticEvent::FormEvent(_) => "FormEvent", - SyntheticEvent::SelectionEvent(_) => "SelectionEvent", - SyntheticEvent::TouchEvent(_) => "TouchEvent", - SyntheticEvent::WheelEvent(_) => "WheelEvent", - SyntheticEvent::MediaEvent(_) => "MediaEvent", - SyntheticEvent::AnimationEvent(_) => "AnimationEvent", - SyntheticEvent::TransitionEvent(_) => "TransitionEvent", - SyntheticEvent::ToggleEvent(_) => "ToggleEvent", - SyntheticEvent::MouseEvent(_) => "MouseEvent", - SyntheticEvent::PointerEvent(_) => "PointerEvent", - SyntheticEvent::GenericEvent(_) => "GenericEvent", - }; - - f.debug_struct("VirtualEvent").field("type", &name).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 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 presedence 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 pre-empted 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(crate) fn event_meta(event: &UserEvent) -> (bool, EventPriority) { - use EventPriority::*; - - match event.name { - // clipboard - "copy" | "cut" | "paste" => (true, Medium), - - // Composition - "compositionend" | "compositionstart" | "compositionupdate" => (true, Low), - - // Keyboard - "keydown" | "keypress" | "keyup" => (true, High), - - // Focus - "focus" | "blur" => (true, Low), - - // Form - "change" | "input" | "invalid" | "reset" | "submit" => (true, Medium), - - // Mouse - "click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit" - | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter" - | "mouseleave" | "mouseout" | "mouseover" | "mouseup" => (true, High), - - "mousemove" => (false, Medium), - - // Pointer - "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture" - | "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => { - (true, Medium) - } - - // Selection - "select" | "touchcancel" | "touchend" => (true, Medium), - - // Touch - "touchmove" | "touchstart" => (true, Medium), - - // Wheel - "scroll" | "wheel" => (false, Medium), - - // Media - "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted" - | "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play" - | "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend" - | "timeupdate" | "volumechange" | "waiting" => (true, Medium), - - // Animation - "animationstart" | "animationend" | "animationiteration" => (true, Medium), - - // Transition - "transitionend" => (true, Medium), - - // Toggle - "toggle" => (true, Medium), - - _ => (true, Low), - } -} - -pub use on::{ - AnimationEvent, ClipboardEvent, CompositionEvent, FocusEvent, FormEvent, GenericEvent, KeyCode, - KeyboardEvent, MediaEvent, MouseEvent, PointerEvent, SelectionEvent, ToggleEvent, TouchEvent, - TransitionEvent, WheelEvent, -}; - -pub mod on { - //! This module defines the synthetic events that all Dioxus apps enable. No matter the platform, every dioxus renderer - //! will implement the same events and same behavior (bubbling, cancelation, etc). - //! - //! Synthetic events are immutable and wrapped in Arc. It is the intention for Dioxus renderers to re-use the underyling - //! Arc allocation through "get_mut" - //! - //! React recently dropped support for re-using event allocation and just passes the real event along. - use super::*; - - macro_rules! event_directory { - ( $( - $( #[$attr:meta] )* - $eventdata:ident($wrapper:ident): [ - $( - $( #[$method_attr:meta] )* - $name:ident - )* - ]; - )* ) => { - $( - $(#[$attr])* - pub struct $wrapper(pub Arc); - - // todo: derefing to the event is fine (and easy) but breaks some IDE stuff like (go to source) - // going to source in fact takes you to the source of Rc which is... less than useful - // Either we ask for this to be changed in Rust-analyzer or manually impkement the trait - impl Deref for $wrapper { - type Target = Arc; - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - $( - $(#[$method_attr])* - pub fn $name<'a, F>( - c: NodeFactory<'a>, - mut callback: F, - ) -> Listener<'a> - where F: FnMut($wrapper) + 'a - { - let bump = &c.bump(); - - let cb: &mut dyn FnMut(SyntheticEvent) = bump.alloc(move |evt: SyntheticEvent| match evt { - SyntheticEvent::$wrapper(event) => callback(event), - _ => unreachable!("Downcasted SyntheticEvent to wrong event type - this is an internal bug!") - }); - - let callback: BumpBox = unsafe { BumpBox::from_raw(cb) }; - - - // ie oncopy - let event_name = stringify!($name); - - // ie copy - let shortname: &'static str = &event_name[2..]; - - Listener { - event: shortname, - mounted_node: Cell::new(None), - callback: RefCell::new(Some(callback)), - } - } - )* - )* - }; - } - - // The Dioxus Synthetic event system - event_directory! { - ClipboardEventInner(ClipboardEvent): [ - /// Called when "copy" - oncopy - - /// oncut - oncut - - /// onpaste - onpaste - ]; - - CompositionEventInner(CompositionEvent): [ - /// oncompositionend - oncompositionend - - /// oncompositionstart - oncompositionstart - - /// oncompositionupdate - oncompositionupdate - ]; - - KeyboardEventInner(KeyboardEvent): [ - /// onkeydown - onkeydown - - /// onkeypress - onkeypress - - /// onkeyup - onkeyup - ]; - - FocusEventInner(FocusEvent): [ - /// onfocus - onfocus - - /// onblur - onblur - ]; - - - FormEventInner(FormEvent): [ - /// onchange - onchange - - /// oninput handler - oninput - - /// oninvalid - oninvalid - - /// onreset - onreset - - /// onsubmit - onsubmit - ]; - - - /// A synthetic event that wraps a web-style [`MouseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent) - /// - /// - /// The MouseEvent interface represents events that occur due to the user interacting with a pointing device (such as a mouse). - /// - /// ## Trait implementation: - /// ```rust - /// fn alt_key(&self) -> bool; - /// fn button(&self) -> i16; - /// fn buttons(&self) -> u16; - /// fn client_x(&self) -> i32; - /// fn client_y(&self) -> i32; - /// fn ctrl_key(&self) -> bool; - /// fn meta_key(&self) -> bool; - /// fn page_x(&self) -> i32; - /// fn page_y(&self) -> i32; - /// fn screen_x(&self) -> i32; - /// fn screen_y(&self) -> i32; - /// fn shift_key(&self) -> bool; - /// fn get_modifier_state(&self, key_code: &str) -> bool; - /// ``` - /// - /// ## Event Handlers - /// - [`onclick`] - /// - [`oncontextmenu`] - /// - [`ondoubleclick`] - /// - [`ondrag`] - /// - [`ondragend`] - /// - [`ondragenter`] - /// - [`ondragexit`] - /// - [`ondragleave`] - /// - [`ondragover`] - /// - [`ondragstart`] - /// - [`ondrop`] - /// - [`onmousedown`] - /// - [`onmouseenter`] - /// - [`onmouseleave`] - /// - [`onmousemove`] - /// - [`onmouseout`] - /// - [`onmouseover`] - /// - [`onmouseup`] - MouseEventInner(MouseEvent): [ - /// Execute a callback when a button is clicked. - /// - /// ## Description - /// - /// An element receives a click event when a pointing device button (such as a mouse's primary mouse button) - /// is both pressed and released while the pointer is located inside the element. - /// - /// - Bubbles: Yes - /// - Cancelable: Yes - /// - Interface: [`MouseEvent`] - /// - /// If the button is pressed on one element and the pointer is moved outside the element before the button - /// is released, the event is fired on the most specific ancestor element that contained both elements. - /// `click` fires after both the `mousedown` and `mouseup` events have fired, in that order. - /// - /// ## Example - /// ``` - /// rsx!( button { "click me", onclick: move |_| log::info!("Clicked!`") } ) - /// ``` - /// - /// ## Reference - /// - https://www.w3schools.com/tags/ev_onclick.asp - /// - https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event - onclick - - /// oncontextmenu - oncontextmenu - - /// ondoubleclick - ondoubleclick - - /// ondrag - ondrag - - /// ondragend - ondragend - - /// ondragenter - ondragenter - - /// ondragexit - ondragexit - - /// ondragleave - ondragleave - - /// ondragover - ondragover - - /// ondragstart - ondragstart - - /// ondrop - ondrop - - /// onmousedown - onmousedown - - /// onmouseenter - onmouseenter - - /// onmouseleave - onmouseleave - - /// onmousemove - onmousemove - - /// onmouseout - onmouseout - - /// - onscroll - - /// onmouseover - /// - /// Triggered when the users's mouse hovers over an element. - onmouseover - - /// onmouseup - onmouseup - ]; - - PointerEventInner(PointerEvent): [ - /// pointerdown - onpointerdown - - /// pointermove - onpointermove - - /// pointerup - onpointerup - - /// pointercancel - onpointercancel - - /// gotpointercapture - ongotpointercapture - - /// lostpointercapture - onlostpointercapture - - /// pointerenter - onpointerenter - - /// pointerleave - onpointerleave - - /// pointerover - onpointerover - - /// pointerout - onpointerout - ]; - - SelectionEventInner(SelectionEvent): [ - /// onselect - onselect - ]; - - TouchEventInner(TouchEvent): [ - /// ontouchcancel - ontouchcancel - - /// ontouchend - ontouchend - - /// ontouchmove - ontouchmove - - /// ontouchstart - ontouchstart - ]; - - WheelEventInner(WheelEvent): [ - /// - onwheel - ]; - - MediaEventInner(MediaEvent): [ - ///abort - onabort - - ///canplay - oncanplay - - ///canplaythrough - oncanplaythrough - - ///durationchange - ondurationchange - - ///emptied - onemptied - - ///encrypted - onencrypted - - ///ended - onended - - ///error - onerror - - ///loadeddata - onloadeddata - - ///loadedmetadata - onloadedmetadata - - ///loadstart - onloadstart - - ///pause - onpause - - ///play - onplay - - ///playing - onplaying - - ///progress - onprogress - - ///ratechange - onratechange - - ///seeked - onseeked - - ///seeking - onseeking - - ///stalled - onstalled - - ///suspend - onsuspend - - ///timeupdate - ontimeupdate - - ///volumechange - onvolumechange - - ///waiting - onwaiting - ]; - - AnimationEventInner(AnimationEvent): [ - /// onanimationstart - onanimationstart - - /// onanimationend - onanimationend - - /// onanimationiteration - onanimationiteration - ]; - - TransitionEventInner(TransitionEvent): [ - /// - ontransitionend - ]; - - ToggleEventInner(ToggleEvent): [ - /// - ontoggle - ]; - } - - pub struct GenericEvent(pub Arc); - - pub trait GenericEventInner: Send + Sync { - /// Return a reference to the raw event. User will need to downcast the event to the right platform-specific type. - fn raw_event(&self) -> &dyn Any; - /// Returns whether or not a specific event is a bubbling event - fn bubbles(&self) -> bool; - /// Sets or returns whether the event should propagate up the hierarchy or not - fn cancel_bubble(&self); - /// Returns whether or not an event can have its default action prevented - fn cancelable(&self) -> bool; - /// Returns whether the event is composed or not - fn composed(&self) -> bool; - - // Currently not supported because those no way we could possibly support it - // just cast the event to the right platform-specific type and return it - // /// Returns the event's path - // fn composed_path(&self) -> String; - - /// Returns the element whose event listeners triggered the event - fn current_target(&self); - /// Returns whether or not the preventDefault method was called for the event - fn default_prevented(&self) -> bool; - /// Returns which phase of the event flow is currently being evaluated - fn event_phase(&self) -> u16; - /// Returns whether or not an event is trusted - fn is_trusted(&self) -> bool; - /// Cancels the event if it is cancelable, meaning that the default action that belongs to the event will - fn prevent_default(&self); - /// Prevents other listeners of the same event from being called - fn stop_immediate_propagation(&self); - /// Prevents further propagation of an event during event flow - fn stop_propagation(&self); - /// Returns the element that triggered the event - fn target(&self); - /// Returns the time (in milliseconds relative to the epoch) at which the event was created - fn time_stamp(&self) -> f64; - } - - pub trait ClipboardEventInner: Send + Sync { - // DOMDataTransfer clipboardData - } - - pub trait CompositionEventInner: Send + Sync { - fn data(&self) -> String; - } - - pub trait KeyboardEventInner: Send + Sync { - fn alt_key(&self) -> bool; - - fn char_code(&self) -> u32; - - /// Identify which "key" was entered. - /// - /// This is the best method to use for all languages. They key gets mapped to a String sequence which you can match on. - /// The key isn't an enum because there are just so many context-dependent keys. - /// - /// A full list on which keys to use is available at: - /// - /// - /// # Example - /// - /// ```rust - /// match event.key().as_str() { - /// "Esc" | "Escape" => {} - /// "ArrowDown" => {} - /// "ArrowLeft" => {} - /// _ => {} - /// } - /// ``` - /// - fn key(&self) -> String; - - /// Get the key code as an enum Variant. - /// - /// This is intended for things like arrow keys, escape keys, function keys, and other non-international keys. - /// To match on unicode sequences, use the [`key`] method - this will return a string identifier instead of a limited enum. - /// - /// - /// ## Example - /// - /// ```rust - /// use dioxus::KeyCode; - /// match event.key_code() { - /// KeyCode::Escape => {} - /// KeyCode::LeftArrow => {} - /// KeyCode::RightArrow => {} - /// _ => {} - /// } - /// ``` - /// - fn key_code(&self) -> KeyCode; - - /// Check if the ctrl key was pressed down - fn ctrl_key(&self) -> bool; - - fn get_modifier_state(&self, key_code: &str) -> bool; - - fn locale(&self) -> String; - fn location(&self) -> usize; - fn meta_key(&self) -> bool; - fn repeat(&self) -> bool; - fn shift_key(&self) -> bool; - fn which(&self) -> usize; - } - - pub trait FocusEventInner: Send + Sync { - /* DOMEventInner: Send + SyncTarget relatedTarget */ - } - - pub trait FormEventInner: Send + Sync { - fn value(&self) -> String; - } - - pub trait MouseEventInner: Send + Sync { - fn alt_key(&self) -> bool; - fn button(&self) -> i16; - fn buttons(&self) -> u16; - /// Get the X coordinate of the mouse relative to the window - fn client_x(&self) -> i32; - fn client_y(&self) -> i32; - fn ctrl_key(&self) -> bool; - fn meta_key(&self) -> bool; - fn page_x(&self) -> i32; - fn page_y(&self) -> i32; - fn screen_x(&self) -> i32; - fn screen_y(&self) -> i32; - fn shift_key(&self) -> bool; - fn get_modifier_state(&self, key_code: &str) -> bool; - } - - pub trait PointerEventInner: Send + Sync { - // Mouse only - fn alt_key(&self) -> bool; - fn button(&self) -> i16; - fn buttons(&self) -> u16; - fn client_x(&self) -> i32; - fn client_y(&self) -> i32; - fn ctrl_key(&self) -> bool; - fn meta_key(&self) -> bool; - fn page_x(&self) -> i32; - fn page_y(&self) -> i32; - fn screen_x(&self) -> i32; - fn screen_y(&self) -> i32; - fn shift_key(&self) -> bool; - fn get_modifier_state(&self, key_code: &str) -> bool; - fn pointer_id(&self) -> i32; - fn width(&self) -> i32; - fn height(&self) -> i32; - fn pressure(&self) -> f32; - fn tangential_pressure(&self) -> f32; - fn tilt_x(&self) -> i32; - fn tilt_y(&self) -> i32; - fn twist(&self) -> i32; - fn pointer_type(&self) -> String; - fn is_primary(&self) -> bool; - } - - pub trait SelectionEventInner: Send + Sync {} - - pub trait TouchEventInner: Send + Sync { - fn alt_key(&self) -> bool; - fn ctrl_key(&self) -> bool; - fn meta_key(&self) -> bool; - fn shift_key(&self) -> bool; - fn get_modifier_state(&self, key_code: &str) -> bool; - } - // changedTouches: DOMTouchList, - // targetTouches: DOMTouchList, - // touches: DOMTouchList, - - pub trait UIEventInner: Send + Sync { - fn detail(&self) -> i32; - } - // DOMAbstractView view - - pub trait WheelEventInner: Send + Sync { - fn delta_mode(&self) -> u32; - fn delta_x(&self) -> f64; - fn delta_y(&self) -> f64; - fn delta_z(&self) -> f64; - } - - pub trait MediaEventInner: Send + Sync {} - - pub trait ImageEventInner: Send + Sync { - // load error - } - - pub trait AnimationEventInner: Send + Sync { - fn animation_name(&self) -> String; - fn pseudo_element(&self) -> String; - fn elapsed_time(&self) -> f32; - } - - pub trait TransitionEventInner: Send + Sync { - fn property_name(&self) -> String; - fn pseudo_element(&self) -> String; - fn elapsed_time(&self) -> f32; - } - - pub trait ToggleEventInner: Send + Sync {} - - pub use util::KeyCode; - mod util { - - #[derive(Clone, Copy)] - pub enum KeyCode { - Backspace = 8, - Tab = 9, - Enter = 13, - Shift = 16, - Ctrl = 17, - Alt = 18, - Pause = 19, - CapsLock = 20, - Escape = 27, - PageUp = 33, - PageDown = 34, - End = 35, - Home = 36, - LeftArrow = 37, - UpArrow = 38, - RightArrow = 39, - DownArrow = 40, - Insert = 45, - Delete = 46, - Num0 = 48, - Num1 = 49, - Num2 = 50, - Num3 = 51, - Num4 = 52, - Num5 = 53, - Num6 = 54, - Num7 = 55, - Num8 = 56, - Num9 = 57, - A = 65, - B = 66, - C = 67, - D = 68, - E = 69, - F = 70, - G = 71, - H = 72, - I = 73, - J = 74, - K = 75, - L = 76, - M = 77, - N = 78, - O = 79, - P = 80, - Q = 81, - R = 82, - S = 83, - T = 84, - U = 85, - V = 86, - W = 87, - X = 88, - Y = 89, - Z = 90, - LeftWindow = 91, - RightWindow = 92, - SelectKey = 93, - Numpad0 = 96, - Numpad1 = 97, - Numpad2 = 98, - Numpad3 = 99, - Numpad4 = 100, - Numpad5 = 101, - Numpad6 = 102, - Numpad7 = 103, - Numpad8 = 104, - Numpad9 = 105, - Multiply = 106, - Add = 107, - Subtract = 109, - DecimalPoint = 110, - Divide = 111, - F1 = 112, - F2 = 113, - F3 = 114, - F4 = 115, - F5 = 116, - F6 = 117, - F7 = 118, - F8 = 119, - F9 = 120, - F10 = 121, - F11 = 122, - F12 = 123, - NumLock = 144, - ScrollLock = 145, - Semicolon = 186, - EqualSign = 187, - Comma = 188, - Dash = 189, - Period = 190, - ForwardSlash = 191, - GraveAccent = 192, - OpenBracket = 219, - BackSlash = 220, - CloseBraket = 221, - SingleQuote = 222, - Unknown, - } - - impl KeyCode { - pub fn from_raw_code(i: u8) -> Self { - use KeyCode::*; - match i { - 8 => Backspace, - 9 => Tab, - 13 => Enter, - 16 => Shift, - 17 => Ctrl, - 18 => Alt, - 19 => Pause, - 20 => CapsLock, - 27 => Escape, - 33 => PageUp, - 34 => PageDown, - 35 => End, - 36 => Home, - 37 => LeftArrow, - 38 => UpArrow, - 39 => RightArrow, - 40 => DownArrow, - 45 => Insert, - 46 => Delete, - 48 => Num0, - 49 => Num1, - 50 => Num2, - 51 => Num3, - 52 => Num4, - 53 => Num5, - 54 => Num6, - 55 => Num7, - 56 => Num8, - 57 => Num9, - 65 => A, - 66 => B, - 67 => C, - 68 => D, - 69 => E, - 70 => F, - 71 => G, - 72 => H, - 73 => I, - 74 => J, - 75 => K, - 76 => L, - 77 => M, - 78 => N, - 79 => O, - 80 => P, - 81 => Q, - 82 => R, - 83 => S, - 84 => T, - 85 => U, - 86 => V, - 87 => W, - 88 => X, - 89 => Y, - 90 => Z, - 91 => LeftWindow, - 92 => RightWindow, - 93 => SelectKey, - 96 => Numpad0, - 97 => Numpad1, - 98 => Numpad2, - 99 => Numpad3, - 100 => Numpad4, - 101 => Numpad5, - 102 => Numpad6, - 103 => Numpad7, - 104 => Numpad8, - 105 => Numpad9, - 106 => Multiply, - 107 => Add, - 109 => Subtract, - 110 => DecimalPoint, - 111 => Divide, - 112 => F1, - 113 => F2, - 114 => F3, - 115 => F4, - 116 => F5, - 117 => F6, - 118 => F7, - 119 => F8, - 120 => F9, - 121 => F10, - 122 => F11, - 123 => F12, - 144 => NumLock, - 145 => ScrollLock, - 186 => Semicolon, - 187 => EqualSign, - 188 => Comma, - 189 => Dash, - 190 => Period, - 191 => ForwardSlash, - 192 => GraveAccent, - 219 => OpenBracket, - 220 => BackSlash, - 221 => CloseBraket, - 222 => SingleQuote, - _ => Unknown, - } - } - - // get the raw code - pub fn raw_code(&self) -> u32 { - *self as u32 - } - } - } -} diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 810d0a88c..474af1699 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -184,7 +184,6 @@ pub enum DomEdit<'bump> { Remove { root: u64, }, - CreateTextNode { text: &'bump str, root: u64, diff --git a/packages/core/src/scheduler.rs b/packages/core/src/scheduler.rs index 304884c39..f774ae6f3 100644 --- a/packages/core/src/scheduler.rs +++ b/packages/core/src/scheduler.rs @@ -86,6 +86,7 @@ use std::{ #[derive(Clone)] pub(crate) struct EventChannel { pub task_counter: Rc>, + pub cur_subtree: Rc>, pub sender: UnboundedSender, pub schedule_any_immediate: Rc, pub submit_task: Rc TaskHandle>, @@ -178,8 +179,10 @@ impl Scheduler { let heuristics = HeuristicsEngine::new(); let task_counter = Rc::new(Cell::new(0)); + let cur_subtree = Rc::new(Cell::new(0)); let channel = EventChannel { + cur_subtree, task_counter: task_counter.clone(), sender: sender.clone(), schedule_any_immediate: { diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 19006042e..c1d5981fa 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -2,7 +2,7 @@ use crate::innerlude::*; use fxhash::FxHashMap; use std::{ any::{Any, TypeId}, - cell::RefCell, + cell::{Cell, RefCell}, collections::HashMap, future::Future, pin::Pin, @@ -23,6 +23,8 @@ pub struct Scope { pub(crate) parent_idx: Option, pub(crate) our_arena_idx: ScopeId, pub(crate) height: u32, + pub(crate) subtree: Cell, + pub(crate) is_subtree_root: Cell, // Nodes pub(crate) frames: ActiveFrame, @@ -71,6 +73,36 @@ impl Scope { self.frames.fin_head() } + /// Get the subtree ID that this scope belongs to. + /// + /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route + /// the mutations to the correct window/portal/subtree. + /// + /// + /// # Example + /// + /// ```rust + /// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} })); + /// dom.rebuild(); + /// + /// let base = dom.base_scope(); + /// + /// assert_eq!(base.subtree(), 0); + /// ``` + pub fn subtree(&self) -> u32 { + self.subtree.get() + } + + pub(crate) fn new_subtree(&self) -> Option { + if self.is_subtree_root.get() { + None + } else { + let cur = self.shared.cur_subtree.get(); + self.shared.cur_subtree.set(cur + 1); + Some(cur) + } + } + /// Get the height of this Scope - IE the number of scopes above it. /// /// A Scope with a height of `0` is the root scope - there are no other scopes above it. @@ -146,6 +178,7 @@ impl Scope { our_arena_idx: ScopeId, parent_idx: Option, height: u32, + subtree: u32, child_nodes: ScopeChildren, shared: EventChannel, ) -> Self { @@ -168,6 +201,8 @@ impl Scope { parent_idx, our_arena_idx, height, + subtree: Cell::new(subtree), + is_subtree_root: Cell::new(false), frames: ActiveFrame::new(), hooks: Default::default(), diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 252445c06..153933e0c 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -156,6 +156,7 @@ impl VirtualDom { myidx, None, 0, + 0, ScopeChildren(&[]), scheduler.pool.channel.clone(), )