From 7a03c1d2b48590276b182465679387655fe08f3a Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 4 Oct 2021 01:28:04 -0400 Subject: [PATCH] wip: overhaul event system --- Cargo.toml | 1 + packages/core/examples/jsframework.rs | 7 +- packages/core/src/events.old.rs | 1023 ++++++++++++++++++++++++ packages/core/src/events.rs | 901 +++++++++++---------- packages/core/src/lib.rs | 6 +- packages/core/src/mutations.rs | 2 - packages/core/src/resources.rs | 10 - packages/core/src/scheduler.rs | 23 +- packages/core/src/test_dom.rs | 3 +- packages/core/src/threadsafe.rs | 23 +- packages/core/src/virtual_dom.rs | 102 +-- packages/core/tests/eventsystem.rs | 4 - packages/desktop/.vscode/settings.json | 3 +- packages/desktop/Cargo.toml | 7 +- packages/desktop/README.md | 36 +- packages/desktop/examples/core.rs | 33 + packages/desktop/examples/crm.rs | 111 +++ packages/desktop/examples/demo.rs | 10 +- packages/desktop/examples/tauri.rs | 11 + packages/desktop/src/cfg.rs | 14 +- packages/desktop/src/events.rs | 141 ++-- packages/desktop/src/index.html | 207 +---- packages/desktop/src/index.js | 216 +++++ packages/desktop/src/lib.rs | 270 ++++--- packages/desktop/src/logging.rs | 51 ++ packages/mobile/Cargo.toml | 2 +- packages/web/Cargo.toml | 2 +- packages/web/examples/blah.rs | 4 +- packages/web/src/dom.rs | 235 ++++-- packages/web/src/events.rs | 477 ----------- packages/web/src/lib.rs | 4 +- packages/webview-client/Cargo.toml | 8 + packages/webview-client/README.md | 8 + packages/webview-client/src/main.rs | 3 + 34 files changed, 2489 insertions(+), 1469 deletions(-) create mode 100644 packages/core/src/events.old.rs create mode 100644 packages/desktop/examples/core.rs create mode 100644 packages/desktop/examples/crm.rs create mode 100644 packages/desktop/examples/tauri.rs create mode 100644 packages/desktop/src/index.js create mode 100644 packages/desktop/src/logging.rs create mode 100644 packages/webview-client/Cargo.toml create mode 100644 packages/webview-client/README.md create mode 100644 packages/webview-client/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index c088139fa..523d0b521 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,6 +85,7 @@ members = [ "packages/ssr", "packages/desktop", "packages/mobile", + "packages/webview-client" ] diff --git a/packages/core/examples/jsframework.rs b/packages/core/examples/jsframework.rs index cbd5131cd..8ea858407 100644 --- a/packages/core/examples/jsframework.rs +++ b/packages/core/examples/jsframework.rs @@ -1,3 +1,5 @@ +use dioxus::events::on::MouseEvent; +use dioxus::events::DioxusEvent; use dioxus_core as dioxus; use dioxus_core::prelude::*; use dioxus_core_macro::*; @@ -37,6 +39,9 @@ struct RowProps { label: Label, } fn Row<'a>(cx: Context<'a>, props: &'a RowProps) -> DomTree<'a> { + let handler = move |evt: MouseEvent| { + let g = evt.button; + }; cx.render(rsx! { tr { td { class:"col-md-1", "{props.row_id}" } @@ -44,7 +49,7 @@ fn Row<'a>(cx: Context<'a>, props: &'a RowProps) -> DomTree<'a> { a { class: "lbl", "{props.label}" } } td { class: "col-md-1" - a { class: "remove", onclick: move |_| {/* remove */} + a { class: "remove", onclick: {handler} span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" } } } diff --git a/packages/core/src/events.old.rs b/packages/core/src/events.old.rs new file mode 100644 index 000000000..a0c97bec9 --- /dev/null +++ b/packages/core/src/events.old.rs @@ -0,0 +1,1023 @@ +//! 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/events.rs b/packages/core/src/events.rs index 88746faf3..b0ff08ea3 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -1,8 +1,8 @@ -//! This module provides a set of common events for all Dioxus apps to target, regardless of host platform. -//! ------------------------------------------------------------------------------------------------------- +//! An event system that's less confusing than Traits + RC; +//! This should hopefully make it easier to port to other platforms. //! -//! 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. +//! Unfortunately, it is less efficient than the original, but hopefully it's negligible. + use crate::{ innerlude::Listener, innerlude::{ElementId, NodeFactory, ScopeId}, @@ -34,6 +34,7 @@ pub struct UserEvent { pub event: SyntheticEvent, } +#[derive(Debug)] pub enum SyntheticEvent { AnimationEvent(on::AnimationEvent), ClipboardEvent(on::ClipboardEvent), @@ -41,7 +42,7 @@ pub enum SyntheticEvent { FocusEvent(on::FocusEvent), FormEvent(on::FormEvent), KeyboardEvent(on::KeyboardEvent), - GenericEvent(on::GenericEvent), + GenericEvent(DioxusEvent<()>), TouchEvent(on::TouchEvent), ToggleEvent(on::ToggleEvent), MediaEvent(on::MediaEvent), @@ -51,31 +52,6 @@ pub enum SyntheticEvent { 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. /// @@ -129,82 +105,101 @@ pub enum EventPriority { Low = 0, } -pub(crate) fn event_meta(event: &UserEvent) -> (bool, EventPriority) { - use EventPriority::*; +#[derive(Debug)] +pub struct DioxusEvent { + inner: T, + raw: Box, +} - match event.name { - // clipboard - "copy" | "cut" | "paste" => (true, Medium), +impl DioxusEvent { + pub fn new(inner: T, raw: F) -> Self { + let raw = Box::new(raw); + Self { inner, raw } + } - // Composition - "compositionend" | "compositionstart" | "compositionupdate" => (true, Low), + /// Return a reference to the raw event. User will need to downcast the event to the right platform-specific type. + pub fn native(&self) -> Option<&E> { + self.raw.downcast_ref() + } - // Keyboard - "keydown" | "keypress" | "keyup" => (true, High), + /// Returns whether or not a specific event is a bubbling event + pub fn bubbles(&self) -> bool { + todo!() + } + /// Sets or returns whether the event should propagate up the hierarchy or not + pub fn cancel_bubble(&self) { + todo!() + } + /// Returns whether or not an event can have its default action prevented + pub fn cancelable(&self) -> bool { + todo!() + } + /// Returns whether the event is composed or not + pub fn composed(&self) -> bool { + todo!() + } - // Focus - "focus" | "blur" => (true, Low), + // 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 + // pub fn composed_path(&self) -> String { + // todo!() + // } - // Form - "change" | "input" | "invalid" | "reset" | "submit" => (true, Medium), + /// Returns the element whose event listeners triggered the event + pub fn current_target(&self) { + todo!() + } + /// Returns whether or not the preventDefault method was called for the event + pub fn default_prevented(&self) -> bool { + todo!() + } + /// Returns which phase of the event flow is currently being evaluated + pub fn event_phase(&self) -> u16 { + todo!() + } - // Mouse - "click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit" - | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter" - | "mouseleave" | "mouseout" | "mouseover" | "mouseup" => (true, High), + /// Returns whether or not an event is trusted + pub fn is_trusted(&self) -> bool { + todo!() + } - "mousemove" => (false, Medium), + /// Cancels the event if it is cancelable, meaning that the default action that belongs to the event will + pub fn prevent_default(&self) { + todo!() + } - // Pointer - "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture" - | "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => { - (true, Medium) - } + /// Prevents other listeners of the same event from being called + pub fn stop_immediate_propagation(&self) { + todo!() + } - // Selection - "select" | "touchcancel" | "touchend" => (true, Medium), + /// Prevents further propagation of an event during event flow + pub fn stop_propagation(&self) { + todo!() + } - // Touch - "touchmove" | "touchstart" => (true, Medium), + /// Returns the element that triggered the event + pub fn target(&self) -> Option> { + todo!() + } - // 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), + /// Returns the time (in milliseconds relative to the epoch) at which the event was created + pub fn time_stamp(&self) -> f64 { + todo!() } } -pub use on::{ - AnimationEvent, ClipboardEvent, CompositionEvent, FocusEvent, FormEvent, GenericEvent, KeyCode, - KeyboardEvent, MediaEvent, MouseEvent, PointerEvent, SelectionEvent, ToggleEvent, TouchEvent, - TransitionEvent, WheelEvent, -}; +impl std::ops::Deref for DioxusEvent { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} 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] )* @@ -217,13 +212,14 @@ pub mod on { )* ) => { $( $(#[$attr])* - pub struct $wrapper(pub Arc); + #[derive(Debug)] + pub struct $wrapper(pub DioxusEvent<$eventdata>); // 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; + type Target = DioxusEvent<$eventdata>; fn deref(&self) -> &Self::Target { &self.0 } @@ -599,57 +595,20 @@ pub mod on { ]; } - pub struct GenericEvent(pub Arc); - - pub trait GenericEventInner { - /// 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 { + #[derive(Debug)] + pub struct ClipboardEventInner( // DOMDataTransfer clipboardData + ); + + #[derive(Debug)] + pub struct CompositionEventInner { + pub data: String, } - pub trait CompositionEventInner { - fn data(&self) -> String; - } - - pub trait KeyboardEventInner { - fn alt_key(&self) -> bool; - - fn char_code(&self) -> u32; + #[derive(Debug)] + pub struct KeyboardEventInner { + pub alt_key: bool, + pub char_code: u32, /// Identify which "key" was entered. /// @@ -669,8 +628,8 @@ pub mod on { /// _ => {} /// } /// ``` - /// - fn key(&self) -> String; + /// + pub key: String, /// Get the key code as an enum Variant. /// @@ -689,335 +648,393 @@ pub mod on { /// _ => {} /// } /// ``` - /// - 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 key_code: KeyCode, + pub ctrl_key: bool, + pub locale: String, + pub location: usize, + pub meta_key: bool, + pub repeat: bool, + pub shift_key: bool, + pub which: usize, + // get_modifier_state: bool, } - pub trait FocusEventInner { - /* DOMEventInnerTarget relatedTarget */ + #[derive(Debug)] + pub struct FocusEventInner {/* DOMEventInner: Send + SyncTarget relatedTarget */} + + #[derive(Debug)] + pub struct FormEventInner { + /* DOMEventInner: Send + SyncTarget relatedTarget */ + pub value: String, } - pub trait FormEventInner { - fn value(&self) -> String; + #[derive(Debug)] + pub struct MouseEventInner { + pub alt_key: bool, + pub button: i16, + pub buttons: u16, + pub client_x: i32, + pub client_y: i32, + pub ctrl_key: bool, + pub meta_key: bool, + pub page_x: i32, + pub page_y: i32, + pub screen_x: i32, + pub screen_y: i32, + pub shift_key: bool, + // fn get_modifier_state(&self, key_code: &str) -> bool; } - pub trait MouseEventInner { - 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 { + #[derive(Debug)] + pub struct PointerEventInner { // 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 alt_key: bool, + pub button: i16, + pub buttons: u16, + pub client_x: i32, + pub client_y: i32, + pub ctrl_key: bool, + pub meta_key: bool, + pub page_x: i32, + pub page_y: i32, + pub screen_x: i32, + pub screen_y: i32, + pub shift_key: bool, + pub pointer_id: i32, + pub width: i32, + pub height: i32, + pub pressure: f32, + pub tangential_pressure: f32, + pub tilt_x: i32, + pub tilt_y: i32, + pub twist: i32, + pub pointer_type: String, + pub is_primary: bool, + // pub get_modifier_state: bool, } - pub trait SelectionEventInner {} + #[derive(Debug)] + pub struct SelectionEventInner {} - pub trait TouchEventInner { - 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; + #[derive(Debug)] + pub struct TouchEventInner { + pub alt_key: bool, + pub ctrl_key: bool, + pub meta_key: bool, + pub shift_key: bool, + // get_modifier_state: bool, // changedTouches: DOMTouchList, // targetTouches: DOMTouchList, // touches: DOMTouchList, } - pub trait UIEventInner { - // DOMAbstractView view - fn detail(&self) -> i32; + #[derive(Debug)] + pub struct WheelEventInner { + pub delta_mode: u32, + pub delta_x: f64, + pub delta_y: f64, + pub delta_z: f64, } - pub trait WheelEventInner { - fn delta_mode(&self) -> u32; - fn delta_x(&self) -> f64; - fn delta_y(&self) -> f64; - fn delta_z(&self) -> f64; - } + #[derive(Debug)] + pub struct MediaEventInner {} - pub trait MediaEventInner {} - - pub trait ImageEventInner { + #[derive(Debug)] + pub struct ImageEventInner { // load error + pub load_error: bool, } - pub trait AnimationEventInner { - fn animation_name(&self) -> String; - fn pseudo_element(&self) -> String; - fn elapsed_time(&self) -> f32; + #[derive(Debug)] + pub struct AnimationEventInner { + pub animation_name: String, + pub pseudo_element: String, + pub elapsed_time: f32, } - pub trait TransitionEventInner { - fn property_name(&self) -> String; - fn pseudo_element(&self) -> String; - fn elapsed_time(&self) -> f32; + #[derive(Debug)] + pub struct TransitionEventInner { + pub property_name: String, + pub pseudo_element: String, + pub elapsed_time: f32, } - pub trait ToggleEventInner {} + #[derive(Debug)] + pub struct ToggleEventInner {} +} - pub use util::KeyCode; - mod util { +#[derive(Clone, Copy, Debug)] +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, +} - #[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, } + } - 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 - } - } + // get the raw code + pub fn raw_code(&self) -> u32 { + *self as u32 + } +} + +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), } } diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 047366129..8c07f0037 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -19,6 +19,7 @@ pub mod context; pub mod diff; pub mod diff_stack; pub mod events; +// pub mod events2; pub mod heuristics; pub mod hooklist; pub mod hooks; @@ -61,8 +62,8 @@ pub(crate) mod innerlude { pub use crate::innerlude::{ Context, DioxusElement, DomEdit, DomTree, ElementId, EventPriority, LazyNodes, MountType, - Mutations, NodeFactory, Properties, ScopeId, SuspendedContext, SyntheticEvent, TaskHandle, - TestDom, ThreadsafeVirtualDom, UserEvent, VNode, VirtualDom, FC, + Mutations, NodeFactory, Properties, SchedulerMsg, ScopeId, SuspendedContext, SyntheticEvent, + TaskHandle, TestDom, ThreadsafeVirtualDom, UserEvent, VNode, VirtualDom, FC, }; pub mod prelude { @@ -78,4 +79,5 @@ pub mod exports { //! Important dependencies that are used by the rest of the library // the foundation of this library pub use bumpalo; + pub use futures_channel; } diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 19c590e70..0a91989b7 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -185,7 +185,6 @@ pub enum DomEdit<'bump> { root: u64, }, - RemoveAllChildren, CreateTextNode { text: &'bump str, id: u64, @@ -232,7 +231,6 @@ impl DomEdit<'_> { DomEdit::AppendChildren { .. } => id == "AppendChildren", DomEdit::ReplaceWith { .. } => id == "ReplaceWith", DomEdit::Remove { .. } => id == "Remove", - DomEdit::RemoveAllChildren => id == "RemoveAllChildren", DomEdit::CreateTextNode { .. } => id == "CreateTextNode", DomEdit::CreateElement { .. } => id == "CreateElement", DomEdit::CreateElementNs { .. } => id == "CreateElementNs", diff --git a/packages/core/src/resources.rs b/packages/core/src/resources.rs index f0361b5f7..ebdd6ef49 100644 --- a/packages/core/src/resources.rs +++ b/packages/core/src/resources.rs @@ -43,16 +43,6 @@ impl ResourcePool { inner.get_mut(idx.0) } - // return a bumpframe with a lifetime attached to the arena borrow - // this is useful for merging lifetimes - pub fn with_scope_vnode<'b>( - &self, - _id: ScopeId, - _f: impl FnOnce(&mut Scope) -> &VNode<'b>, - ) -> Option<&VNode<'b>> { - todo!() - } - pub fn try_remove(&self, id: ScopeId) -> Option { let inner = unsafe { &mut *self.components.get() }; Some(inner.remove(id.0)) diff --git a/packages/core/src/scheduler.rs b/packages/core/src/scheduler.rs index dcb6acb33..819355c78 100644 --- a/packages/core/src/scheduler.rs +++ b/packages/core/src/scheduler.rs @@ -106,7 +106,8 @@ pub enum SchedulerMsg { } pub enum TaskMsg { - SubmitTask(FiberTask, u64), + // SubmitTask(FiberTask, u64), + // SubmitTask(FiberTask, u64), ToggleTask(u64), PauseTask(u64), ResumeTask(u64), @@ -163,7 +164,10 @@ pub(crate) struct Scheduler { } impl Scheduler { - pub(crate) fn new() -> Self { + pub(crate) fn new( + sender: UnboundedSender, + receiver: UnboundedReceiver, + ) -> Self { /* Preallocate 2000 elements and 100 scopes to avoid dynamic allocation. Perhaps this should be configurable from some external config? @@ -173,7 +177,6 @@ impl Scheduler { let heuristics = HeuristicsEngine::new(); - let (sender, receiver) = futures_channel::mpsc::unbounded::(); let task_counter = Rc::new(Cell::new(0)); let channel = EventChannel { @@ -183,15 +186,19 @@ impl Scheduler { let sender = sender.clone(); Rc::new(move |id| sender.unbounded_send(SchedulerMsg::Immediate(id)).unwrap()) }, + // todo: we want to get the futures out of the scheduler message + // the scheduler message should be send/sync submit_task: { Rc::new(move |fiber_task| { let task_id = task_counter.get(); task_counter.set(task_id + 1); - sender - .unbounded_send(SchedulerMsg::Task(TaskMsg::SubmitTask( - fiber_task, task_id, - ))) - .unwrap(); + + todo!(); + // sender + // .unbounded_send(SchedulerMsg::Task(TaskMsg::SubmitTask( + // fiber_task, task_id, + // ))) + // .unwrap(); TaskHandle { our_id: task_id, sender: sender.clone(), diff --git a/packages/core/src/test_dom.rs b/packages/core/src/test_dom.rs index 60d2de9b8..7b94496c2 100644 --- a/packages/core/src/test_dom.rs +++ b/packages/core/src/test_dom.rs @@ -12,7 +12,8 @@ pub struct TestDom { impl TestDom { pub fn new() -> TestDom { let bump = Bump::new(); - let scheduler = Scheduler::new(); + let (sender, receiver) = futures_channel::mpsc::unbounded::(); + let scheduler = Scheduler::new(sender, receiver); TestDom { bump, scheduler } } diff --git a/packages/core/src/threadsafe.rs b/packages/core/src/threadsafe.rs index 042c21f2a..813c1cee4 100644 --- a/packages/core/src/threadsafe.rs +++ b/packages/core/src/threadsafe.rs @@ -1,5 +1,12 @@ //! A threadsafe wrapper for the VirtualDom - +//! +//! This is an experimental module, and must be explicitly opted-into. +//! +//! It's not guaranteed that this module produces safe results, so use at your own peril. +//! +//! The only real "right" answer to a Send VirtualDom is by ensuring all hook data is Send +//! +//! use std::sync::{Arc, Mutex, MutexGuard}; use crate::VirtualDom; @@ -18,9 +25,17 @@ use crate::VirtualDom; /// directly. Even then, it's not possible to access any hook data. This means that non-Send types are only "in play" /// while the VirtualDom is locked with a non-Send marker. /// -/// Note that calling "wait for work" on the regular VirtualDom is inherently non-Send. If there are async tasks that -/// need to be awaited, they must be done thread-local since we don't place any requirements on user tasks. This can be -/// done with the function "spawn_local" in either tokio or async_std. +/// Calling "wait for work" on the ThreadsafeVirtualDom does indeed work, because this method only accesses `Send` types. +/// Otherwise, the VirtualDom must be unlocked on the current thread to modify any data. +/// +/// Dioxus does have the concept of local tasks and non-local tasks. +/// +/// For the ThreadsafeVirtualDom, non-Send tasks are not ran - and will error out during a Debug build if one is submitted. +/// +/// +/// +/// When Tasks are submitted to a thread-local executor, +/// pub struct ThreadsafeVirtualDom { inner: Arc>, } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index a9da36b9d..252445c06 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -19,8 +19,10 @@ //! This module includes just the barebones for a complete VirtualDOM API. //! Additional functionality is defined in the respective files. +use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender}; + use crate::innerlude::*; -use std::{any::Any, rc::Rc, sync::Arc}; +use std::{any::Any, rc::Rc}; /// An integrated virtual node system that progresses events and diffs UI trees. /// @@ -121,6 +123,20 @@ impl VirtualDom { /// let mutations = dom.rebuild(); /// ``` pub fn new_with_props(root: FC

, root_props: P) -> Self { + let (sender, receiver) = futures_channel::mpsc::unbounded::(); + Self::new_with_props_and_scheduler(root, root_props, sender, receiver) + } + + /// Launch the VirtualDom, but provide your own channel for receiving and sending messages into the scheduler. + /// + /// This is useful when the VirtualDom must be driven from outside a thread and it doesn't make sense to wait for the + /// VirtualDom to be created just to retrive its channel receiver. + pub fn new_with_props_and_scheduler( + root: FC

, + root_props: P, + sender: UnboundedSender, + receiver: UnboundedReceiver, + ) -> Self { let root_fc = Box::new(root); let root_props: Rc = Rc::new(root_props); @@ -132,7 +148,7 @@ impl VirtualDom { std::mem::transmute(root(Context { scope }, props)) }); - let scheduler = Scheduler::new(); + let scheduler = Scheduler::new(sender, receiver); let base_scope = scheduler.pool.insert_scope_with_key(|myidx| { Scope::new( @@ -343,47 +359,46 @@ impl VirtualDom { /// Waits for the scheduler to have work /// This lets us poll async tasks during idle periods without blocking the main thread. pub async fn wait_for_work(&mut self) { - todo!("making vdom send right now"); - // if self.scheduler.has_any_work() { - // log::debug!("No need to wait for work, we already have some"); - // return; - // } + if self.scheduler.has_any_work() { + log::debug!("No need to wait for work, we already have some"); + return; + } - // log::debug!("No active work.... waiting for some..."); - // use futures_util::StreamExt; + log::debug!("No active work.... waiting for some..."); + use futures_util::StreamExt; - // // right now this won't poll events if there is ongoing work - // // in the future we want to prioritize some events over ongoing work - // // this is coming in the "priorities" PR + // right now this won't poll events if there is ongoing work + // in the future we want to prioritize some events over ongoing work + // this is coming in the "priorities" PR - // // Wait for any new events if we have nothing to do - // // todo: poll the events once even if there is work to do to prevent starvation - // futures_util::select! { - // _ = self.scheduler.async_tasks.next() => {} - // msg = self.scheduler.receiver.next() => { - // match msg.unwrap() { - // SchedulerMsg::Task(t) => todo!(), - // SchedulerMsg::Immediate(im) => { - // self.scheduler.dirty_scopes.insert(im); - // } - // SchedulerMsg::UiEvent(evt) => { - // self.scheduler.ui_events.push_back(evt); - // } - // } - // }, - // } + // Wait for any new events if we have nothing to do + // todo: poll the events once even if there is work to do to prevent starvation + futures_util::select! { + _ = self.scheduler.async_tasks.next() => {} + msg = self.scheduler.receiver.next() => { + match msg.unwrap() { + SchedulerMsg::Task(t) => todo!(), + SchedulerMsg::Immediate(im) => { + self.scheduler.dirty_scopes.insert(im); + } + SchedulerMsg::UiEvent(evt) => { + self.scheduler.ui_events.push_back(evt); + } + } + }, + } - // while let Ok(Some(msg)) = self.scheduler.receiver.try_next() { - // match msg { - // SchedulerMsg::Task(t) => todo!(), - // SchedulerMsg::Immediate(im) => { - // self.scheduler.dirty_scopes.insert(im); - // } - // SchedulerMsg::UiEvent(evt) => { - // self.scheduler.ui_events.push_back(evt); - // } - // } - // } + while let Ok(Some(msg)) = self.scheduler.receiver.try_next() { + match msg { + SchedulerMsg::Task(t) => todo!(), + SchedulerMsg::Immediate(im) => { + self.scheduler.dirty_scopes.insert(im); + } + SchedulerMsg::UiEvent(evt) => { + self.scheduler.ui_events.push_back(evt); + } + } + } } } @@ -407,14 +422,5 @@ impl std::fmt::Display for VirtualDom { } } -/* -Send safety... - -The VirtualDom can only be "send" if the internals exposed to user code are also "send". - -IE it's okay to move an Rc from one thread to another thread as long as it's only used internally - -*/ - // we never actually use the contents of this root caller struct RootCaller(Rc Fn(&'b Scope) -> DomTree<'b> + 'static>); diff --git a/packages/core/tests/eventsystem.rs b/packages/core/tests/eventsystem.rs index 50a86617e..383832d0b 100644 --- a/packages/core/tests/eventsystem.rs +++ b/packages/core/tests/eventsystem.rs @@ -16,8 +16,4 @@ async fn event_queue_works() { let mut dom = VirtualDom::new(App); let edits = dom.rebuild(); - - async_std::task::spawn_local(async move { - // let mutations = dom.run_unbounded().await; - }); } diff --git a/packages/desktop/.vscode/settings.json b/packages/desktop/.vscode/settings.json index 1c68f97cc..c7ce540f2 100644 --- a/packages/desktop/.vscode/settings.json +++ b/packages/desktop/.vscode/settings.json @@ -1,3 +1,4 @@ { - "rust-analyzer.inlayHints.enable": true + "rust-analyzer.inlayHints.enable": true, + "rust-analyzer.cargo.allFeatures": true } diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index f95d4b0b5..12a86add8 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -19,15 +19,16 @@ thiserror = "1.0.23" log = "0.4.13" fern = { version = "0.6.0", features = ["colored"] } html-escape = "0.2.9" -wry = "0.11.0" +# wry = { version = "0.12.2", git = "https://github.com/jkelleyrtp/wry.git", branch = "jk/fnmut_rpc" } +wry = "0.12.2" tokio = { version = "1.12.0", features = ["full"] } +futures-channel = "0.3.16" [dev-dependencies] dioxus-html = { path = "../html" } -tide = "0.15.0" -tide-websockets = "0.3.0" dioxus-core-macro = { path = "../core-macro" } +dioxus-hooks = { path = "../hooks" } # thiserror = "1.0.23" # log = "0.4.13" # fern = { version = "0.6.0", features = ["colored"] } diff --git a/packages/desktop/README.md b/packages/desktop/README.md index dc840b09d..a581d16fd 100644 --- a/packages/desktop/README.md +++ b/packages/desktop/README.md @@ -6,26 +6,19 @@ Dioxus-webview is an attempt at making a simpler "Tauri" where creating desktop ```rust // main.rs -#[async_std::main] -async fn main() { - dioxus_desktop::new(|cx, props|{ - let (count, set_count) = use_state(cx, || 0); - cx.render(html! { -

-

"Dioxus Desktop Demo"

-

"Count is {count}"

- -
- }) - }) - .configure_webview(|view| { - // custom webview config options - }) - .launch() - .await; +fn main() { + dioxus_desktop::new(App, |c| c) + .launch() + .await; } +static App: FC<()> = |cx, props|{ + let (count, set_count) = use_state(cx, || 0); + rsx!(cx, div { + h1 { "Dioxus Desktop Demo" } + p { "Count is {count}"} + button { onclick: move |_| count += 1} + }) +}; ``` and then to create a native .app: @@ -45,3 +38,8 @@ By bridging the native process, desktop apps can access full multithreading powe Dioxus-desktop is a pure liveview application where all of the state and event handlers are proxied through the liveview and into the native process. For pure server-based liveview, this would normally be too slow (in both render performance and latency), but because the VDom is local, desktop apps are just as fast as Electron. Dioxus-desktop leverages dioxus-liveview under the hood, but with convenience wrappers around setting up the VDom bridge, proxying events, and serving the initial WebSys-Renderer. The backend is served by Tide, so an async runtime _is_ needed - we recommend async-std in Tokio mode. + + +## Async Runtime + + diff --git a/packages/desktop/examples/core.rs b/packages/desktop/examples/core.rs new file mode 100644 index 000000000..3130dfef5 --- /dev/null +++ b/packages/desktop/examples/core.rs @@ -0,0 +1,33 @@ +use dioxus_core::prelude::*; +use dioxus_core_macro::*; +use dioxus_html as dioxus_elements; + +fn main() { + let (window_loop, tasks) = dioxus_desktop::start(App, |c| c); + + std::thread::spawn(move || { + // + let runtime = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap(); + + runtime.block_on(async move { + let mut vir = VirtualDom::new_with_props(root, props); + let channel = vir.get_event_sender(); + loop { + vir.wait_for_work().await; + let edits = vir.run_with_deadline(|| false); + let edit_string = serde_json::to_string(&edits[0].edits).unwrap(); + event_tx.send(edit_string).unwrap(); + } + }) + }); + + window_loop.run(); +} + +static App: FC<()> = |cx| { + // + cx.render(rsx!(div {})) +}; diff --git a/packages/desktop/examples/crm.rs b/packages/desktop/examples/crm.rs new file mode 100644 index 000000000..14a299313 --- /dev/null +++ b/packages/desktop/examples/crm.rs @@ -0,0 +1,111 @@ +use dioxus_core as dioxus; +use dioxus_core::prelude::*; +use dioxus_core_macro::*; +use dioxus_hooks::*; + +use dioxus_html as dioxus_elements; + +fn main() { + dioxus_desktop::set_up_logging(true); + dioxus_desktop::launch(App, |c| c).unwrap(); +} + +enum Scene { + ClientsList, + NewClientForm, + Settings, +} + +#[derive(Clone, Debug, Default)] +pub struct Client { + pub first_name: String, + pub last_name: String, + pub description: String, +} + +static App: FC<()> = |cx, _| { + let scene = use_state(cx, || Scene::ClientsList); + let clients = use_ref(cx, || vec![] as Vec); + + let firstname = use_state(cx, || String::new()); + let lastname = use_state(cx, || String::new()); + let description = use_state(cx, || String::new()); + + let scene = match *scene { + Scene::ClientsList => { + rsx!(cx, div { class: "crm" + h2 { "List of clients" margin_bottom: "10px" } + div { class: "clients" margin_left: "10px" + {clients.read().iter().map(|client| rsx!( + div { class: "client" style: "margin-bottom: 50px" + p { "First Name: {client.first_name}" } + p { "Last Name: {client.last_name}" } + p {"Description: {client.description}"} + }) + )} + } + button { class: "pure-button pure-button-primary" onclick: move |_| scene.set(Scene::NewClientForm), "Add New" } + button { class: "pure-button" onclick: move |_| scene.set(Scene::Settings), "Settings" } + }) + } + Scene::NewClientForm => { + let add_new = move |_| { + clients.write().push(Client { + description: (*description).clone(), + first_name: (*firstname).clone(), + last_name: (*lastname).clone(), + }); + description.set(String::new()); + firstname.set(String::new()); + lastname.set(String::new()); + }; + rsx!(cx, div { class: "crm" + h2 {"Add new client" margin_bottom: "10px" } + form { class: "pure-form" + input { class: "new-client firstname" placeholder: "First name" value: "{firstname}" + oninput: move |evt| firstname.set(evt.value()) + } + input { class: "new-client lastname" placeholder: "Last name" value: "{lastname}" + oninput: move |evt| lastname.set(evt.value()) + } + textarea { class: "new-client description" placeholder: "Description" value: "{description}" + oninput: move |evt| description.set(evt.value()) + } + } + button { class: "pure-button pure-button-primary", onclick: {add_new}, "Add New" } + button { class: "pure-button", onclick: move |_| scene.set(Scene::ClientsList), "Go Back" } + }) + } + Scene::Settings => { + rsx!(cx, div { + h2 { "Settings" margin_bottom: "10px" } + button { + background: "rgb(202, 60, 60)" + class: "pure-button pure-button-primary" + onclick: move |_| { + clients.write().clear(); + scene.set(Scene::ClientsList); + }, + "Remove all clients" + } + button { + class: "pure-button pure-button-primary" + onclick: move |_| scene.set(Scene::ClientsList), + "Go Back" + } + }) + } + }; + + rsx!(cx, body { + link { + rel: "stylesheet" + href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css" + integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5" + crossorigin: "anonymous" + } + margin_left: "35%" + h1 {"Dioxus CRM Example"} + {scene} + }) +}; diff --git a/packages/desktop/examples/demo.rs b/packages/desktop/examples/demo.rs index bde43c18c..64fe878c9 100644 --- a/packages/desktop/examples/demo.rs +++ b/packages/desktop/examples/demo.rs @@ -5,20 +5,14 @@ use dioxus_core_macro::*; use dioxus_html as dioxus_elements; fn main() { - std::thread::spawn(|| { - let mut vdom = VirtualDom::new(App); - let f = async_std::task::block_on(vdom.wait_for_work()); - }); - let a = 10; - // async_std::task::spawn_blocking(|| async move { - // }); + dioxus_desktop::launch(App, |c| c).unwrap(); } static App: FC<()> = |cx, props| { - // cx.render(rsx!( div { "hello world!" } + {(0..10).map(|f| rsx!( div {"abc {f}"}))} )) }; diff --git a/packages/desktop/examples/tauri.rs b/packages/desktop/examples/tauri.rs new file mode 100644 index 000000000..77d1a08cb --- /dev/null +++ b/packages/desktop/examples/tauri.rs @@ -0,0 +1,11 @@ +fn main() { + tauri::AppBuilder::default().setup(move |app, name| { + // + let window = app.get_window(); + + let window = app.get_window(); + tauri::spawn(|| async move { + // + }); + }); +} diff --git a/packages/desktop/src/cfg.rs b/packages/desktop/src/cfg.rs index 4895c3e2f..8ba0ddc73 100644 --- a/packages/desktop/src/cfg.rs +++ b/packages/desktop/src/cfg.rs @@ -4,30 +4,36 @@ use dioxus_core::DomEdit; use wry::{ application::{ error::OsError, - event_loop::EventLoopWindowTarget, + event_loop::{EventLoop, EventLoopWindowTarget}, menu::MenuBar, window::{Fullscreen, Icon, Window, WindowBuilder}, }, - webview::{RpcRequest, RpcResponse}, + webview::{RpcRequest, RpcResponse, WebView}, }; pub struct DesktopConfig<'a> { pub window: WindowBuilder, - pub(crate) manual_edits: Option>, + pub(crate) manual_edits: Option>>, pub(crate) pre_rendered: Option, + pub(crate) event_handler: Option, &mut WebView)>>, } -impl DesktopConfig<'_> { +impl<'a> DesktopConfig<'a> { /// Initializes a new `WindowBuilder` with default values. #[inline] pub fn new() -> Self { Self { + event_handler: None, window: Default::default(), pre_rendered: None, manual_edits: None, } } + pub fn with_edits(&mut self, edits: Vec>) { + self.manual_edits = Some(edits); + } + pub fn with_prerendered(&mut self, content: String) -> &mut Self { self.pre_rendered = Some(content); self diff --git a/packages/desktop/src/events.rs b/packages/desktop/src/events.rs index 8ff2a9996..75c7f3cfb 100644 --- a/packages/desktop/src/events.rs +++ b/packages/desktop/src/events.rs @@ -22,69 +22,100 @@ pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent { let mut data: Vec = serde_json::from_value(val).unwrap(); let data = data.drain(..).next().unwrap(); - let event = SyntheticEvent::MouseEvent(MouseEvent(Arc::new(WebviewMouseEvent))); let scope = ScopeId(data.scope as usize); let mounted_dom_id = Some(ElementId(data.mounted_dom_id as usize)); + UserEvent { - name: todo!(), + name: "click", event, scope, mounted_dom_id, } } -#[derive(Debug)] -struct WebviewMouseEvent; -impl MouseEventInner for WebviewMouseEvent { - fn alt_key(&self) -> bool { - todo!() - } - - fn button(&self) -> i16 { - todo!() - } - - fn buttons(&self) -> u16 { - todo!() - } - - fn client_x(&self) -> i32 { - todo!() - } - - fn client_y(&self) -> i32 { - todo!() - } - - fn ctrl_key(&self) -> bool { - todo!() - } - - fn meta_key(&self) -> bool { - todo!() - } - - fn page_x(&self) -> i32 { - todo!() - } - - fn page_y(&self) -> i32 { - todo!() - } - - fn screen_x(&self) -> i32 { - todo!() - } - - fn screen_y(&self) -> i32 { - todo!() - } - - fn shift_key(&self) -> bool { - todo!() - } - - fn get_modifier_state(&self, key_code: &str) -> bool { - todo!() +fn event_name_from_typ(typ: &str) -> &'static str { + match typ { + "copy" => "copy", + "cut" => "cut", + "paste" => "paste", + "compositionend" => "compositionend", + "compositionstart" => "compositionstart", + "compositionupdate" => "compositionupdate", + "keydown" => "keydown", + "keypress" => "keypress", + "keyup" => "keyup", + "focus" => "focus", + "blur" => "blur", + "change" => "change", + "input" => "input", + "invalid" => "invalid", + "reset" => "reset", + "submit" => "submit", + "click" => "click", + "contextmenu" => "contextmenu", + "doubleclick" => "doubleclick", + "drag" => "drag", + "dragend" => "dragend", + "dragenter" => "dragenter", + "dragexit" => "dragexit", + "dragleave" => "dragleave", + "dragover" => "dragover", + "dragstart" => "dragstart", + "drop" => "drop", + "mousedown" => "mousedown", + "mouseenter" => "mouseenter", + "mouseleave" => "mouseleave", + "mousemove" => "mousemove", + "mouseout" => "mouseout", + "mouseover" => "mouseover", + "mouseup" => "mouseup", + "pointerdown" => "pointerdown", + "pointermove" => "pointermove", + "pointerup" => "pointerup", + "pointercancel" => "pointercancel", + "gotpointercapture" => "gotpointercapture", + "lostpointercapture" => "lostpointercapture", + "pointerenter" => "pointerenter", + "pointerleave" => "pointerleave", + "pointerover" => "pointerover", + "pointerout" => "pointerout", + "select" => "select", + "touchcancel" => "touchcancel", + "touchend" => "touchend", + "touchmove" => "touchmove", + "touchstart" => "touchstart", + "scroll" => "scroll", + "wheel" => "wheel", + "animationstart" => "animationstart", + "animationend" => "animationend", + "animationiteration" => "animationiteration", + "transitionend" => "transitionend", + "abort" => "abort", + "canplay" => "canplay", + "canplaythrough" => "canplaythrough", + "durationchange" => "durationchange", + "emptied" => "emptied", + "encrypted" => "encrypted", + "ended" => "ended", + "error" => "error", + "loadeddata" => "loadeddata", + "loadedmetadata" => "loadedmetadata", + "loadstart" => "loadstart", + "pause" => "pause", + "play" => "play", + "playing" => "playing", + "progress" => "progress", + "ratechange" => "ratechange", + "seeked" => "seeked", + "seeking" => "seeking", + "stalled" => "stalled", + "suspend" => "suspend", + "timeupdate" => "timeupdate", + "volumechange" => "volumechange", + "waiting" => "waiting", + "toggle" => "toggle", + _ => { + panic!("unsupported event type") + } } } diff --git a/packages/desktop/src/index.html b/packages/desktop/src/index.html index 7ce555efa..1e7372c48 100644 --- a/packages/desktop/src/index.html +++ b/packages/desktop/src/index.html @@ -9,212 +9,7 @@
- diff --git a/packages/desktop/src/index.js b/packages/desktop/src/index.js new file mode 100644 index 000000000..c62b6d4cf --- /dev/null +++ b/packages/desktop/src/index.js @@ -0,0 +1,216 @@ + +class Interpreter { + constructor(root) { + this.root = root; + this.stack = [root]; + this.listeners = { + "onclick": {} + }; + this.lastNodeWasText = false; + this.nodes = [root, root, root, root]; + } + + top() { + return this.stack[this.stack.length - 1]; + } + + pop() { + return this.stack.pop(); + } + + PushRoot(edit) { + const id = edit.id; + const node = this.nodes[id]; + console.log("pushing root ", node, "with id", id); + this.stack.push(node); + } + + PopRoot(_edit) { + this.stack.pop(); + } + + AppendChildren(edit) { + let root = this.stack[this.stack.length - (1 + edit.many)]; + + let to_add = this.stack.splice(this.stack.length - edit.many); + + for (let i = 0; i < edit.many; i++) { + root.appendChild(to_add[i]); + } + } + + ReplaceWith(edit) { + console.log(edit); + let root = this.nodes[edit.root]; + let els = this.stack.splice(this.stack.length - edit.m); + + console.log(root); + console.log(els); + + + root.replaceWith(...els); + } + + Remove(edit) { + let node = this.nodes[edit.element_id]; + node.remove(); + } + + CreateTextNode(edit) { + const node = document.createTextNode(edit.text); + this.nodes[edit.id] = node; + this.stack.push(node); + } + + CreateElement(edit) { + const tagName = edit.tag; + const el = document.createElement(tagName); + this.nodes[edit.id] = el; + this.stack.push(el); + } + + CreateElementNs(edit) { + let el = document.createElementNS(edit.ns, edit.tag); + this.stack.push(el); + this.nodes[edit.id] = el; + } + + CreatePlaceholder(edit) { + let el = document.createElement("pre"); + // let el = document.createComment("vroot"); + this.stack.push(el); + this.nodes[edit.id] = el; + } + + RemoveEventListener(edit) { } + + SetText(edit) { + this.top().textContent = edit.text; + } + + SetAttribute(edit) { + const name = edit.field; + const value = edit.value; + const ns = edit.ns; + const node = this.top(this.stack); + if (ns == "style") { + node.style[name] = value; + } else if (ns !== undefined) { + node.setAttributeNS(ns, name, value); + } else { + node.setAttribute(name, value); + } + if (name === "value") { + node.value = value; + } + if (name === "checked") { + node.checked = true; + } + if (name === "selected") { + node.selected = true; + } + } + RemoveAttribute(edit) { + const name = edit.field; + const node = this.top(this.stack); + node.removeAttribute(name); + + if (name === "value") { + node.value = null; + } + if (name === "checked") { + node.checked = false; + } + if (name === "selected") { + node.selected = false; + } + } + + InsertAfter(edit) { + let old = this.nodes[edit.element_id]; + let new_nodes = this.stack.splice(edit.many); + old.after(...new_nodes); + } + + InsertBefore(edit) { + let old = this.nodes[edit.element_id]; + let new_nodes = this.stack.splice(edit.many); + old.before(...new_nodes); + } + + NewEventListener(edit) { + const event_name = edit.event_name; + const mounted_node_id = edit.mounted_node_id; + const scope = edit.scope; + + const element = this.top(); + element.setAttribute(`dioxus-event-${event_name}`, `${scope}.${mounted_node_id}`); + + console.log("listener map is", this.listeners); + if (this.listeners[event_name] === undefined) { + console.log("adding listener!"); + this.listeners[event_name] = "bla"; + this.root.addEventListener(event_name, (event) => { + const target = event.target; + const val = target.getAttribute(`dioxus-event-${event_name}`); + const fields = val.split("."); + const scope_id = parseInt(fields[0]); + const real_id = parseInt(fields[1]); + + console.log(`parsed event with scope_id ${scope_id} and real_id ${real_id}`); + + rpc.call('user_event', { + event: event_name, + scope: scope_id, + mounted_dom_id: real_id, + }).then((reply) => { + console.log(reply); + this.stack.push(this.root); + + let edits = reply.edits; + + for (let x = 0; x < edits.length; x++) { + let edit = edits[x]; + console.log(edit); + + let f = this[edit.type]; + f.call(this, edit); + } + + console.log("initiated"); + }).catch((err) => { + console.log("failed to initiate", err); + }); + }); + } + } +} + +async function initialize() { + const reply = await rpc.call('initiate'); + let root = window.document.getElementById("_dioxusroot"); + const interpreter = new Interpreter(root); + console.log(reply); + + let pre_rendered = reply.pre_rendered; + if (pre_rendered !== undefined) { + root.innerHTML = pre_rendered; + } + + const edits = reply.edits; + + apply_edits(edits, interpreter); +} + +function apply_edits(edits, interpreter) { + for (let x = 0; x < edits.length; x++) { + let edit = edits[x]; + console.log(edit); + let f = interpreter[edit.type]; + f.call(interpreter, edit); + } + + console.log("stack completed: ", interpreter.stack); +} + +initialize(); diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 6b9ba9ea0..b93854442 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -4,20 +4,28 @@ //! use std::borrow::BorrowMut; +use std::cell::RefCell; use std::ops::{Deref, DerefMut}; +use std::rc::Rc; use std::sync::atomic::AtomicBool; use std::sync::mpsc::channel; use std::sync::{Arc, RwLock}; use cfg::DesktopConfig; +use dioxus_core::scheduler::SchedulerMsg; use dioxus_core::*; +// use futures_channel::mpsc::UnboundedSender; use serde::{Deserialize, Serialize}; + +mod logging; + +pub use logging::set_up_logging; pub use wry; use wry::application::event::{Event, WindowEvent}; -use wry::application::event_loop::{ControlFlow, EventLoop}; +use wry::application::event_loop::{self, ControlFlow, EventLoop}; use wry::application::window::Fullscreen; -use wry::webview::WebViewBuilder; +use wry::webview::{WebView, WebViewBuilder}; use wry::{ application::window::{Window, WindowBuilder}, webview::{RpcRequest, RpcResponse}, @@ -45,10 +53,16 @@ pub fn launch_with_props( run(root, props, builder) } +#[derive(Serialize)] enum RpcEvent<'a> { Initialize { edits: Vec> }, } +enum BridgeEvent { + Initialize(serde_json::Value), + Update(serde_json::Value), +} + #[derive(Serialize)] struct Response<'a> { pre_rendered: Option, @@ -60,72 +74,175 @@ pub fn run( props: T, user_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>, ) -> anyhow::Result<()> { - run_with_edits(root, props, user_builder, None) -} - -pub fn run_with_edits< - F: for<'a, 'b> FnOnce(&'a mut DesktopConfig<'b>) -> &'a mut DesktopConfig<'b>, - T: Properties + 'static + Send + Sync, ->( - root: FC, - props: T, - user_builder: F, - redits: Option>>, -) -> anyhow::Result<()> { - /* - - - */ - let mut cfg = DesktopConfig::new(); user_builder(&mut cfg); let DesktopConfig { window, manual_edits, pre_rendered, + .. } = cfg; let event_loop = EventLoop::new(); let window = window.build(&event_loop)?; - let (event_tx, mut event_rx) = tokio::sync::mpsc::unbounded_channel::(); + let (event_tx, mut event_rx) = tokio::sync::mpsc::unbounded_channel(); + let sender = launch_vdom_with_tokio(root, props, event_tx.clone()); + + let locked_receiver = Rc::new(RefCell::new(event_rx)); + + let webview = WebViewBuilder::new(window)? + .with_url("wry://src/index.html")? + .with_rpc_handler(move |_window: &Window, mut req: RpcRequest| { + match req.method.as_str() { + "initiate" => { + // + let mut rx = (*locked_receiver).borrow_mut(); + + match rx.try_recv() { + Ok(BridgeEvent::Initialize(edits)) => { + Some(RpcResponse::new_result(req.id.take(), Some(edits))) + } + _ => None, + } + } + "user_event" => { + // + let data = req.params.unwrap(); + log::debug!("Data: {:#?}", data); + let event = events::trigger_from_serialized(data); + sender.unbounded_send(SchedulerMsg::UiEvent(event)).unwrap(); + + let mut rx = (*locked_receiver).borrow_mut(); + + match rx.blocking_recv() { + Some(BridgeEvent::Update(edits)) => { + log::info!("Passing response back"); + Some(RpcResponse::new_result(req.id.take(), Some(edits))) + } + None => { + log::error!("Sender half is gone"); + None + } + _ => { + log::error!("No update event received"); + None + } + } + } + _ => todo!("this message failed"), + } + }) + // this isn't quite portable unfortunately :( + // todo: figure out a way to allow us to create the index.html with the index.js file separately + // it's a bit easier to hack with + .with_custom_protocol("wry".into(), move |request| { + use std::fs::{canonicalize, read}; + use wry::http::ResponseBuilder; + // Remove url scheme + let path = request.uri().replace("wry://", ""); + // Read the file content from file path + let content = read(canonicalize(&path)?)?; + + // Return asset contents and mime types based on file extentions + // If you don't want to do this manually, there are some crates for you. + // Such as `infer` and `mime_guess`. + let (data, meta) = if path.ends_with(".html") { + (content, "text/html") + } else if path.ends_with(".js") { + (content, "text/javascript") + } else if path.ends_with(".png") { + (content, "image/png") + } else { + unimplemented!(); + }; + + ResponseBuilder::new().mimetype(meta).body(data) + }) + .build()?; + + run_event_loop(event_loop, webview, event_tx); + + Ok(()) +} + +pub fn start( + root: FC

, + config_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>, +) -> ((), ()) { + // + ((), ()) +} + +// Create a new tokio runtime on a dedicated thread and then launch the apps VirtualDom. +fn launch_vdom_with_tokio( + root: FC, + props: C, + event_tx: tokio::sync::mpsc::UnboundedSender, +) -> futures_channel::mpsc::UnboundedSender { // Spawn the virtualdom onto its own thread // if it wants to spawn multithreaded tasks, it can use the executor directly + + let (sender, receiver) = futures_channel::mpsc::unbounded::(); + + let sender_2 = sender.clone(); std::thread::spawn(move || { - // - let runtime = tokio::runtime::Builder::new_current_thread() + // We create the runtim as multithreaded, so you can still "spawn" onto multiple threads + let runtime = tokio::runtime::Builder::new_multi_thread() .enable_all() .build() .unwrap(); runtime.block_on(async move { - let mut vir = VirtualDom::new_with_props(root, props); - let channel = vir.get_event_sender(); + let mut vir = VirtualDom::new_with_props_and_scheduler(root, props, sender, receiver); + let _ = vir.get_event_sender(); + + let edits = vir.rebuild(); + + #[derive(Serialize)] + struct Evt<'a> { + edits: Vec>, + } + + // let msg = RpcEvent::Initialize { edits: edits.edits }; + let edit_string = serde_json::to_value(Evt { edits: edits.edits }).unwrap(); + match event_tx.send(BridgeEvent::Initialize(edit_string)) { + Ok(_) => {} + Err(_) => {} + } + loop { vir.wait_for_work().await; - let edits = vir.run_with_deadline(|| false); - let edit_string = serde_json::to_string(&edits[0].edits).unwrap(); - event_tx.send(edit_string).unwrap(); + log::info!("{}", vir); + + let mut muts = vir.run_with_deadline(|| false); + log::info!("muts {:#?}", muts); + while let Some(edit) = muts.pop() { + let edit_string = serde_json::to_value(Evt { edits: edit.edits }).unwrap(); + match event_tx.send(BridgeEvent::Update(edit_string)) { + Ok(_) => {} + Err(er) => { + log::error!("Sending should not fail {}", er); + } + } + } + + log::info!("mutations sent on channel"); } }) }); - let dioxus_requsted = Arc::new(AtomicBool::new(false)); + sender_2 +} - let webview = WebViewBuilder::new(window)? - .with_url(&format!("data:text/html,{}", HTML_CONTENT))? - .with_rpc_handler(move |_window: &Window, mut req: RpcRequest| { - match req.method.as_str() { - "initiate" => {} - "user_event" => {} - _ => todo!("this message failed"), - } - todo!() - }) - .build()?; - - event_loop.run(move |event, _, control_flow| { +fn run_event_loop( + event_loop: EventLoop<()>, + webview: WebView, + event_tx: tokio::sync::mpsc::UnboundedSender, +) { + let _ = event_tx.clone(); + event_loop.run(move |event, target, control_flow| { *control_flow = ControlFlow::Wait; match event { @@ -146,74 +263,5 @@ pub fn run_with_edits< _ => {} } - }); - - Ok(()) + }) } - -// let edits = if let Some(edits) = &redits { -// serde_json::to_value(edits).unwrap() -// } else { -// let mut lock = vdom.write().unwrap(); -// // let mut reg_lock = registry.write().unwrap(); -// // Create the thin wrapper around the registry to collect the edits into -// let mut real = dom::WebviewDom::new(); -// let pre = pre_rendered.clone(); -// let response = match pre { -// Some(content) => { -// lock.rebuild_in_place().unwrap(); -// Response { -// edits: Vec::new(), -// pre_rendered: Some(content), -// } -// } -// None => { -// // -// let edits = { -// // let mut edits = Vec::new(); -// todo!() -// // lock.rebuild(&mut real, &mut edits).unwrap(); -// // edits -// }; -// Response { -// edits, -// pre_rendered: None, -// } -// } -// }; -// serde_json::to_value(&response).unwrap() -// }; -// // Return the edits into the webview runtime -// Some(RpcResponse::new_result(req.id.take(), Some(edits))) - -// log::debug!("User event received"); -// // let registry = registry.clone(); -// let vdom = vdom.clone(); -// let response = async_std::task::block_on(async move { -// let mut lock = vdom.write().unwrap(); -// // let mut reg_lock = registry.write().unwrap(); -// // a deserialized event -// let data = req.params.unwrap(); -// log::debug!("Data: {:#?}", data); -// let event = trigger_from_serialized(data); -// // lock.queue_event(event); -// // Create the thin wrapper around the registry to collect the edits into -// let mut real = dom::WebviewDom::new(); -// // Serialize the edit stream -// // -// let mut edits = Vec::new(); -// // lock.run(&mut real, &mut edits) -// // .await -// // .expect("failed to progress"); -// let response = Response { -// edits, -// pre_rendered: None, -// }; -// let response = serde_json::to_value(&response).unwrap(); -// // Give back the registry into its slot -// // *reg_lock = Some(real.consume()); -// // Return the edits into the webview runtime -// Some(RpcResponse::new_result(req.id.take(), Some(response))) -// }); -// response -// // spawn a task to clean up the garbage diff --git a/packages/desktop/src/logging.rs b/packages/desktop/src/logging.rs new file mode 100644 index 000000000..db605be9e --- /dev/null +++ b/packages/desktop/src/logging.rs @@ -0,0 +1,51 @@ +pub fn set_up_logging(enabled: bool) { + use fern::colors::{Color, ColoredLevelConfig}; + + if !enabled { + return; + } + + // configure colors for the whole line + let colors_line = ColoredLevelConfig::new() + .error(Color::Red) + .warn(Color::Yellow) + // we actually don't need to specify the color for debug and info, they are white by default + .info(Color::White) + .debug(Color::White) + // depending on the terminals color scheme, this is the same as the background color + .trace(Color::BrightBlack); + + // configure colors for the name of the level. + // since almost all of them are the same as the color for the whole line, we + // just clone `colors_line` and overwrite our changes + let colors_level = colors_line.clone().info(Color::Green); + // here we set up our fern Dispatch + + // when running tests in batch, the logger is re-used, so ignore the logger error + let _ = fern::Dispatch::new() + .format(move |out, message, record| { + out.finish(format_args!( + "{color_line}[{level}{color_line}] {message}\x1B[0m", + color_line = format_args!( + "\x1B[{}m", + colors_line.get_color(&record.level()).to_fg_str() + ), + level = colors_level.color(record.level()), + message = message, + )); + }) + // set the default log level. to filter out verbose log messages from dependencies, set + // this to Warn and overwrite the log level for your crate. + .level(log::LevelFilter::Debug) + // .level(log::LevelFilter::Warn) + // change log levels for individual modules. Note: This looks for the record's target + // field which defaults to the module path but can be overwritten with the `target` + // parameter: + // `info!(target="special_target", "This log message is about special_target");` + // .level_for("dioxus", log::LevelFilter::Debug) + // .level_for("dioxus", log::LevelFilter::Info) + // .level_for("pretty_colored", log::LevelFilter::Trace) + // output to stdout + .chain(std::io::stdout()) + .apply(); +} diff --git a/packages/mobile/Cargo.toml b/packages/mobile/Cargo.toml index c11d17ef4..df174023d 100644 --- a/packages/mobile/Cargo.toml +++ b/packages/mobile/Cargo.toml @@ -13,7 +13,7 @@ dioxus-core = { path = "../core", version = "0.1.2" } log = "0.4.14" serde = "1.0.126" serde_json = "1.0.64" -wry = "0.11.0" +wry = "0.12.2" [target.'cfg(target_os = "android")'.dependencies] diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index c4dbc8bd8..05c8ce4e3 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -72,9 +72,9 @@ crate-type = ["cdylib", "rlib"] im-rc = "15.0.0" separator = "0.4.1" uuid = { version = "0.8.2", features = ["v4", "wasm-bindgen"] } -dioxus-hooks = { path = "../hooks" } serde = { version = "1.0.126", features = ["derive"] } reqwest = { version = "0.11", features = ["json"] } +dioxus-hooks = { path = "../hooks" } dioxus-core-macro = { path = "../core-macro" } # rand = { version="0.8.4", features=["small_rng"] } # surf = { version = "2.3.1", default-features = false, features = [ diff --git a/packages/web/examples/blah.rs b/packages/web/examples/blah.rs index 3bcac4f72..d21cd5fd0 100644 --- a/packages/web/examples/blah.rs +++ b/packages/web/examples/blah.rs @@ -44,9 +44,9 @@ static App: FC<()> = |cx, props| { "dynamic subtree {state}" } div { - button { onclick: move |_| state+=1, "incr" } + button { onclick: move |e| state+=1, "incr" } br {} - button { onclick: move |_| state-=1, "decr" } + button { onclick: move |e| state-=1, "decr" } } } } diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 1aa364ef4..972c1297c 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -8,7 +8,7 @@ //! - Partial delegation?> use dioxus_core::{ - events::{SyntheticEvent, UserEvent}, + events::{DioxusEvent, KeyCode, SyntheticEvent, UserEvent}, mutations::NodeRefMutation, scheduler::SchedulerMsg, DomEdit, ElementId, ScopeId, @@ -110,7 +110,6 @@ impl WebsysDom { DomEdit::AppendChildren { many } => self.append_children(many), DomEdit::ReplaceWith { m, root } => self.replace_with(m, root), DomEdit::Remove { root } => self.remove(root), - DomEdit::RemoveAllChildren => self.remove_all_children(), DomEdit::CreateTextNode { text, id } => self.create_text_node(text, id), DomEdit::CreateElement { tag, id } => self.create_element(tag, None, id), DomEdit::CreateElementNs { tag, id, ns } => self.create_element(tag, Some(ns), id), @@ -208,10 +207,6 @@ impl WebsysDom { } } - fn remove_all_children(&mut self) { - todo!() - } - fn create_placeholder(&mut self, id: u64) { self.create_element("pre", None, id); self.set_attribute("hidden", "", None); @@ -464,87 +459,213 @@ impl Stack { } } +pub struct DioxusWebsysEvent(web_sys::Event); +unsafe impl Send for DioxusWebsysEvent {} +unsafe impl Sync for DioxusWebsysEvent {} + +// trait MyTrait {} +// impl MyTrait for web_sys::Event {} + // todo: some of these events are being casted to the wrong event type. // We need tests that simulate clicks/etc and make sure every event type works. fn virtual_event_from_websys_event(event: web_sys::Event) -> SyntheticEvent { use crate::events::*; use dioxus_core::events::on::*; match event.type_().as_str() { - "copy" | "cut" | "paste" => { - SyntheticEvent::ClipboardEvent(ClipboardEvent(Arc::new(WebsysClipboardEvent(event)))) - } + "copy" | "cut" | "paste" => SyntheticEvent::ClipboardEvent(ClipboardEvent( + DioxusEvent::new(ClipboardEventInner(), DioxusWebsysEvent(event)), + )), "compositionend" | "compositionstart" | "compositionupdate" => { - let evt: web_sys::CompositionEvent = event.dyn_into().unwrap(); - SyntheticEvent::CompositionEvent(CompositionEvent(Arc::new(WebsysCompositionEvent( - evt, - )))) + let evt: &web_sys::CompositionEvent = event.dyn_ref().unwrap(); + SyntheticEvent::CompositionEvent(CompositionEvent(DioxusEvent::new( + CompositionEventInner { + data: evt.data().unwrap_or_default(), + }, + DioxusWebsysEvent(event), + ))) } "keydown" | "keypress" | "keyup" => { - let evt: web_sys::KeyboardEvent = event.dyn_into().unwrap(); - SyntheticEvent::KeyboardEvent(KeyboardEvent(Arc::new(WebsysKeyboardEvent(evt)))) - } - "focus" | "blur" => { - let evt: web_sys::FocusEvent = event.dyn_into().unwrap(); - SyntheticEvent::FocusEvent(FocusEvent(Arc::new(WebsysFocusEvent(evt)))) - } - "change" => { - let evt = event.dyn_into().unwrap(); - SyntheticEvent::GenericEvent(GenericEvent(Arc::new(WebsysGenericUiEvent(evt)))) + let evt: &web_sys::KeyboardEvent = event.dyn_ref().unwrap(); + SyntheticEvent::KeyboardEvent(KeyboardEvent(DioxusEvent::new( + KeyboardEventInner { + alt_key: evt.alt_key(), + char_code: evt.char_code(), + key: evt.key(), + key_code: KeyCode::from_raw_code(evt.key_code() as u8), + ctrl_key: evt.ctrl_key(), + locale: "not implemented".to_string(), + location: evt.location() as usize, + meta_key: evt.meta_key(), + repeat: evt.repeat(), + shift_key: evt.shift_key(), + which: evt.which() as usize, + }, + DioxusWebsysEvent(event), + ))) } + "focus" | "blur" => SyntheticEvent::FocusEvent(FocusEvent(DioxusEvent::new( + FocusEventInner {}, + DioxusWebsysEvent(event), + ))), + "change" => SyntheticEvent::GenericEvent(DioxusEvent::new((), DioxusWebsysEvent(event))), + + // todo: these handlers might get really slow if the input box gets large and allocation pressure is heavy + // don't have a good solution with the serialized event problem "input" | "invalid" | "reset" | "submit" => { - let evt: web_sys::Event = event.dyn_into().unwrap(); - SyntheticEvent::FormEvent(FormEvent(Arc::new(WebsysFormEvent(evt)))) + let evt: &web_sys::Event = event.dyn_ref().unwrap(); + + let target: web_sys::EventTarget = evt.target().unwrap(); + let value: String = (&target) + .dyn_ref() + .map(|input: &web_sys::HtmlInputElement| input.value()) + .or_else(|| { + target + .dyn_ref() + .map(|input: &web_sys::HtmlTextAreaElement| input.value()) + }) + // select elements are NOT input events - because - why woudn't they be?? + .or_else(|| { + target + .dyn_ref() + .map(|input: &web_sys::HtmlSelectElement| input.value()) + }) + .or_else(|| { + target + .dyn_ref::() + .unwrap() + .text_content() + }) + .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener"); + + SyntheticEvent::FormEvent(FormEvent(DioxusEvent::new( + FormEventInner { value }, + DioxusWebsysEvent(event), + ))) } "click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit" | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter" | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => { - let evt: web_sys::MouseEvent = event.dyn_into().unwrap(); - SyntheticEvent::MouseEvent(MouseEvent(Arc::new(WebsysMouseEvent(evt)))) + let evt: &web_sys::MouseEvent = event.dyn_ref().unwrap(); + SyntheticEvent::MouseEvent(MouseEvent(DioxusEvent::new( + MouseEventInner { + alt_key: evt.alt_key(), + button: evt.button(), + buttons: evt.buttons(), + client_x: evt.client_x(), + client_y: evt.client_y(), + ctrl_key: evt.ctrl_key(), + meta_key: evt.meta_key(), + screen_x: evt.screen_x(), + screen_y: evt.screen_y(), + shift_key: evt.shift_key(), + page_x: evt.page_x(), + page_y: evt.page_y(), + }, + DioxusWebsysEvent(event), + ))) } "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture" | "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => { - let evt: web_sys::PointerEvent = event.dyn_into().unwrap(); - SyntheticEvent::PointerEvent(PointerEvent(Arc::new(WebsysPointerEvent(evt)))) - } - "select" => { - let evt: web_sys::UiEvent = event.dyn_into().unwrap(); - SyntheticEvent::SelectionEvent(SelectionEvent(Arc::new(WebsysGenericUiEvent(evt)))) + let evt: &web_sys::PointerEvent = event.dyn_ref().unwrap(); + SyntheticEvent::PointerEvent(PointerEvent(DioxusEvent::new( + PointerEventInner { + alt_key: evt.alt_key(), + button: evt.button(), + buttons: evt.buttons(), + client_x: evt.client_x(), + client_y: evt.client_y(), + ctrl_key: evt.ctrl_key(), + meta_key: evt.meta_key(), + page_x: evt.page_x(), + page_y: evt.page_y(), + screen_x: evt.screen_x(), + screen_y: evt.screen_y(), + shift_key: evt.shift_key(), + pointer_id: evt.pointer_id(), + width: evt.width(), + height: evt.height(), + pressure: evt.pressure(), + tangential_pressure: evt.tangential_pressure(), + tilt_x: evt.tilt_x(), + tilt_y: evt.tilt_y(), + twist: evt.twist(), + pointer_type: evt.pointer_type(), + is_primary: evt.is_primary(), + // get_modifier_state: evt.get_modifier_state(), + }, + DioxusWebsysEvent(event), + ))) } + "select" => SyntheticEvent::SelectionEvent(SelectionEvent(DioxusEvent::new( + SelectionEventInner {}, + DioxusWebsysEvent(event), + ))), + "touchcancel" | "touchend" | "touchmove" | "touchstart" => { - let evt: web_sys::TouchEvent = event.dyn_into().unwrap(); - SyntheticEvent::TouchEvent(TouchEvent(Arc::new(WebsysTouchEvent(evt)))) - } - "scroll" => { - let evt: web_sys::UiEvent = event.dyn_into().unwrap(); - SyntheticEvent::GenericEvent(GenericEvent(Arc::new(WebsysGenericUiEvent(evt)))) + let evt: &web_sys::TouchEvent = event.dyn_ref().unwrap(); + SyntheticEvent::TouchEvent(TouchEvent(DioxusEvent::new( + TouchEventInner { + alt_key: evt.alt_key(), + ctrl_key: evt.ctrl_key(), + meta_key: evt.meta_key(), + shift_key: evt.shift_key(), + }, + DioxusWebsysEvent(event), + ))) } + + "scroll" => SyntheticEvent::GenericEvent(DioxusEvent::new((), DioxusWebsysEvent(event))), + "wheel" => { - let evt: web_sys::WheelEvent = event.dyn_into().unwrap(); - SyntheticEvent::WheelEvent(WheelEvent(Arc::new(WebsysWheelEvent(evt)))) + let evt: &web_sys::WheelEvent = event.dyn_ref().unwrap(); + SyntheticEvent::WheelEvent(WheelEvent(DioxusEvent::new( + WheelEventInner { + delta_x: evt.delta_x(), + delta_y: evt.delta_y(), + delta_z: evt.delta_z(), + delta_mode: evt.delta_mode(), + }, + DioxusWebsysEvent(event), + ))) } + "animationstart" | "animationend" | "animationiteration" => { - let evt: web_sys::AnimationEvent = event.dyn_into().unwrap(); - SyntheticEvent::AnimationEvent(AnimationEvent(Arc::new(WebsysAnimationEvent(evt)))) + let evt: &web_sys::AnimationEvent = event.dyn_ref().unwrap(); + SyntheticEvent::AnimationEvent(AnimationEvent(DioxusEvent::new( + AnimationEventInner { + elapsed_time: evt.elapsed_time(), + animation_name: evt.animation_name(), + pseudo_element: evt.pseudo_element(), + }, + DioxusWebsysEvent(event), + ))) } + "transitionend" => { - let evt: web_sys::TransitionEvent = event.dyn_into().unwrap(); - SyntheticEvent::TransitionEvent(TransitionEvent(Arc::new(WebsysTransitionEvent(evt)))) + let evt: &web_sys::TransitionEvent = event.dyn_ref().unwrap(); + SyntheticEvent::TransitionEvent(TransitionEvent(DioxusEvent::new( + TransitionEventInner { + elapsed_time: evt.elapsed_time(), + property_name: evt.property_name(), + pseudo_element: evt.pseudo_element(), + }, + DioxusWebsysEvent(event), + ))) } + "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted" | "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play" | "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend" - | "timeupdate" | "volumechange" | "waiting" => { - let evt: web_sys::UiEvent = event.dyn_into().unwrap(); - SyntheticEvent::MediaEvent(MediaEvent(Arc::new(WebsysMediaEvent(evt)))) - } - "toggle" => { - let evt: web_sys::UiEvent = event.dyn_into().unwrap(); - SyntheticEvent::ToggleEvent(ToggleEvent(Arc::new(WebsysToggleEvent(evt)))) - } - _ => { - let evt: web_sys::UiEvent = event.dyn_into().unwrap(); - SyntheticEvent::GenericEvent(GenericEvent(Arc::new(WebsysGenericUiEvent(evt)))) - } + | "timeupdate" | "volumechange" | "waiting" => SyntheticEvent::MediaEvent(MediaEvent( + DioxusEvent::new(MediaEventInner {}, DioxusWebsysEvent(event)), + )), + + "toggle" => SyntheticEvent::ToggleEvent(ToggleEvent(DioxusEvent::new( + ToggleEventInner {}, + DioxusWebsysEvent(event), + ))), + + _ => SyntheticEvent::GenericEvent(DioxusEvent::new((), DioxusWebsysEvent(event))), } } diff --git a/packages/web/src/events.rs b/packages/web/src/events.rs index f64ffc8b7..01c321854 100644 --- a/packages/web/src/events.rs +++ b/packages/web/src/events.rs @@ -3,480 +3,3 @@ use dioxus_core::events::on::*; use wasm_bindgen::JsCast; use web_sys::{Event, UiEvent}; - -/// All events implement the generic event type - they're all `UI events` -trait WebsysGenericEvent { - fn as_ui_event(&self) -> &UiEvent; -} - -impl GenericEventInner for &dyn WebsysGenericEvent { - /// On WebSys, this returns an &UiEvent which can be casted via dyn_ref into the correct sub type. - fn raw_event(&self) -> &dyn std::any::Any { - self.as_ui_event() - } - - fn bubbles(&self) -> bool { - self.as_ui_event().bubbles() - } - - fn cancel_bubble(&self) { - self.as_ui_event().cancel_bubble(); - } - - fn cancelable(&self) -> bool { - self.as_ui_event().cancelable() - } - - fn composed(&self) -> bool { - self.as_ui_event().composed() - } - - fn current_target(&self) { - if cfg!(debug_assertions) { - todo!("Current target does not return anything useful.\nPlease try casting the event directly."); - } - // self.as_ui_event().current_target(); - } - - fn default_prevented(&self) -> bool { - self.as_ui_event().default_prevented() - } - - fn event_phase(&self) -> u16 { - self.as_ui_event().event_phase() - } - - fn is_trusted(&self) -> bool { - self.as_ui_event().is_trusted() - } - - fn prevent_default(&self) { - self.as_ui_event().prevent_default() - } - - fn stop_immediate_propagation(&self) { - self.as_ui_event().stop_immediate_propagation() - } - - fn stop_propagation(&self) { - self.as_ui_event().stop_propagation() - } - - fn target(&self) { - todo!() - } - - fn time_stamp(&self) -> f64 { - self.as_ui_event().time_stamp() - } -} - -macro_rules! implement_generic_event { - ( - $($event:ident),* - ) => { - $( - impl WebsysGenericEvent for $event { - fn as_ui_event(&self) -> &UiEvent { - self.0.dyn_ref().unwrap() - } - } - )* - }; -} - -implement_generic_event! { - WebsysClipboardEvent, - WebsysCompositionEvent, - WebsysKeyboardEvent, - WebsysGenericUiEvent, - WebsysFocusEvent, - WebsysFormEvent, - WebsysMouseEvent, - WebsysPointerEvent, - WebsysWheelEvent, - WebsysAnimationEvent, - WebsysTransitionEvent, - WebsysTouchEvent, - WebsysMediaEvent, - WebsysToggleEvent -} - -// unfortunately, currently experimental, and web_sys needs to be configured to use it :>( -pub struct WebsysClipboardEvent(pub Event); - -impl ClipboardEventInner for WebsysClipboardEvent {} - -pub struct WebsysCompositionEvent(pub web_sys::CompositionEvent); - -impl CompositionEventInner for WebsysCompositionEvent { - fn data(&self) -> String { - self.0.data().unwrap_or_else(|| String::new()) - } -} - -pub struct WebsysKeyboardEvent(pub web_sys::KeyboardEvent); -impl KeyboardEventInner for WebsysKeyboardEvent { - fn alt_key(&self) -> bool { - self.0.alt_key() - } - fn char_code(&self) -> u32 { - self.0.char_code() - } - fn key(&self) -> String { - self.0.key() - } - - fn key_code(&self) -> KeyCode { - KeyCode::from_raw_code(self.0.key_code() as u8) - } - - fn ctrl_key(&self) -> bool { - self.0.ctrl_key() - } - - fn get_modifier_state(&self, key_code: &str) -> bool { - self.0.get_modifier_state(key_code) - } - - fn locale(&self) -> String { - if cfg!(debug_assertions) { - todo!("Locale is currently not supported. :(") - } else { - String::from("en-US") - } - } - - fn location(&self) -> usize { - self.0.location() as usize - } - - fn meta_key(&self) -> bool { - self.0.meta_key() - } - - fn repeat(&self) -> bool { - self.0.repeat() - } - - fn shift_key(&self) -> bool { - self.0.shift_key() - } - - fn which(&self) -> usize { - self.0.which() as usize - } -} - -pub struct WebsysGenericUiEvent(pub UiEvent); -impl GenericEventInner for WebsysGenericUiEvent { - fn raw_event(&self) -> &dyn std::any::Any { - // self.0.raw_event() - todo!() - } - - fn bubbles(&self) -> bool { - self.0.bubbles() - } - - fn cancel_bubble(&self) { - self.0.cancel_bubble(); - } - - fn cancelable(&self) -> bool { - self.0.cancelable() - } - - fn composed(&self) -> bool { - self.0.composed() - } - - fn current_target(&self) { - // self.0.current_target() - } - - fn default_prevented(&self) -> bool { - self.0.default_prevented() - } - - fn event_phase(&self) -> u16 { - self.0.event_phase() - } - - fn is_trusted(&self) -> bool { - self.0.is_trusted() - } - - fn prevent_default(&self) { - self.0.prevent_default() - } - - fn stop_immediate_propagation(&self) { - self.0.stop_immediate_propagation() - } - - fn stop_propagation(&self) { - self.0.stop_propagation() - } - - fn target(&self) { - // self.0.target() - } - - fn time_stamp(&self) -> f64 { - self.0.time_stamp() - } -} - -impl UIEventInner for WebsysGenericUiEvent { - fn detail(&self) -> i32 { - todo!() - } -} - -impl SelectionEventInner for WebsysGenericUiEvent {} - -pub struct WebsysFocusEvent(pub web_sys::FocusEvent); -impl FocusEventInner for WebsysFocusEvent {} - -pub struct WebsysFormEvent(pub web_sys::Event); -impl FormEventInner for WebsysFormEvent { - // technically a controlled component, so we need to manually grab out the target data - fn value(&self) -> String { - let this: web_sys::EventTarget = self.0.target().unwrap(); - (&this) - .dyn_ref() - .map(|input: &web_sys::HtmlInputElement| input.value()) - .or_else(|| { - this - .dyn_ref() - .map(|input: &web_sys::HtmlTextAreaElement| input.value()) - }) - // select elements are NOT input events - because - why woudn't they be?? - .or_else(|| { - this - .dyn_ref() - .map(|input: &web_sys::HtmlSelectElement| input.value()) - }) - .or_else(|| { - this - .dyn_ref::() - .unwrap() - .text_content() - }) - .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener") - } -} - -pub struct WebsysMouseEvent(pub web_sys::MouseEvent); -impl MouseEventInner for WebsysMouseEvent { - fn alt_key(&self) -> bool { - self.0.alt_key() - } - fn button(&self) -> i16 { - self.0.button() - } - fn buttons(&self) -> u16 { - self.0.buttons() - } - fn client_x(&self) -> i32 { - self.0.client_x() - } - fn client_y(&self) -> i32 { - self.0.client_y() - } - fn ctrl_key(&self) -> bool { - self.0.ctrl_key() - } - fn meta_key(&self) -> bool { - self.0.meta_key() - } - fn page_x(&self) -> i32 { - self.0.page_x() - } - fn page_y(&self) -> i32 { - self.0.page_y() - } - fn screen_x(&self) -> i32 { - self.0.screen_x() - } - fn screen_y(&self) -> i32 { - self.0.screen_y() - } - fn shift_key(&self) -> bool { - self.0.shift_key() - } - - // yikes - // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values - fn get_modifier_state(&self, key_code: &str) -> bool { - self.0.get_modifier_state(key_code) - } -} - -pub struct WebsysPointerEvent(pub web_sys::PointerEvent); -impl PointerEventInner for WebsysPointerEvent { - fn alt_key(&self) -> bool { - self.0.alt_key() - } - fn button(&self) -> i16 { - self.0.button() - } - fn buttons(&self) -> u16 { - self.0.buttons() - } - fn client_x(&self) -> i32 { - self.0.client_x() - } - fn client_y(&self) -> i32 { - self.0.client_y() - } - fn ctrl_key(&self) -> bool { - self.0.ctrl_key() - } - fn meta_key(&self) -> bool { - self.0.meta_key() - } - fn page_x(&self) -> i32 { - self.0.page_x() - } - fn page_y(&self) -> i32 { - self.0.page_y() - } - fn screen_x(&self) -> i32 { - self.0.screen_x() - } - fn screen_y(&self) -> i32 { - self.0.screen_y() - } - fn shift_key(&self) -> bool { - self.0.shift_key() - } - - // yikes - // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values - fn get_modifier_state(&self, key_code: &str) -> bool { - self.0.get_modifier_state(key_code) - } - - fn pointer_id(&self) -> i32 { - self.0.pointer_id() - } - - fn width(&self) -> i32 { - self.0.width() - } - - fn height(&self) -> i32 { - self.0.height() - } - - fn pressure(&self) -> f32 { - self.0.pressure() - } - - fn tangential_pressure(&self) -> f32 { - self.0.tangential_pressure() - } - - fn tilt_x(&self) -> i32 { - self.0.tilt_x() - } - - fn tilt_y(&self) -> i32 { - self.0.tilt_y() - } - - fn twist(&self) -> i32 { - self.0.twist() - } - - fn pointer_type(&self) -> String { - self.0.pointer_type() - } - - fn is_primary(&self) -> bool { - self.0.is_primary() - } -} - -pub struct WebsysWheelEvent(pub web_sys::WheelEvent); -impl WheelEventInner for WebsysWheelEvent { - fn delta_mode(&self) -> u32 { - self.0.delta_mode() - } - - fn delta_x(&self) -> f64 { - self.0.delta_x() - } - - fn delta_y(&self) -> f64 { - self.0.delta_y() - } - - fn delta_z(&self) -> f64 { - self.0.delta_z() - } -} -pub struct WebsysAnimationEvent(pub web_sys::AnimationEvent); -impl AnimationEventInner for WebsysAnimationEvent { - fn animation_name(&self) -> String { - self.0.animation_name() - } - - fn pseudo_element(&self) -> String { - self.0.pseudo_element() - } - - fn elapsed_time(&self) -> f32 { - self.0.elapsed_time() - } -} - -pub struct WebsysTransitionEvent(pub web_sys::TransitionEvent); -impl TransitionEventInner for WebsysTransitionEvent { - fn property_name(&self) -> String { - self.0.property_name() - } - - fn pseudo_element(&self) -> String { - self.0.pseudo_element() - } - - fn elapsed_time(&self) -> f32 { - self.0.elapsed_time() - } -} - -pub struct WebsysTouchEvent(pub web_sys::TouchEvent); -impl TouchEventInner for WebsysTouchEvent { - fn alt_key(&self) -> bool { - self.0.alt_key() - } - - fn ctrl_key(&self) -> bool { - self.0.ctrl_key() - } - - fn meta_key(&self) -> bool { - self.0.meta_key() - } - - fn shift_key(&self) -> bool { - self.0.shift_key() - } - - fn get_modifier_state(&self, key_code: &str) -> bool { - if cfg!(debug_assertions) { - todo!("get_modifier_state is not currently supported for touch events"); - } else { - false - } - } -} - -pub struct WebsysMediaEvent(pub web_sys::UiEvent); -impl MediaEventInner for WebsysMediaEvent {} - -pub struct WebsysToggleEvent(pub web_sys::UiEvent); -impl ToggleEventInner for WebsysToggleEvent {} diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 27b3afb78..f613692cc 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -116,7 +116,7 @@ pub fn launch(root_component: FC<()>, configuration: impl FnOnce(WebConfig) -> W /// ``` pub fn launch_with_props(root_component: FC, root_properties: T, configuration_builder: F) where - T: Properties + 'static, + T: Send + 'static, F: FnOnce(WebConfig) -> WebConfig, { let config = configuration_builder(WebConfig::default()); @@ -135,7 +135,7 @@ where /// wasm_bindgen_futures::spawn_local(app_fut); /// } /// ``` -pub async fn run_with_props(root: FC, root_props: T, cfg: WebConfig) { +pub async fn run_with_props(root: FC, root_props: T, cfg: WebConfig) { let mut dom = VirtualDom::new_with_props(root, root_props); intern_cached_strings(); diff --git a/packages/webview-client/Cargo.toml b/packages/webview-client/Cargo.toml new file mode 100644 index 000000000..0218e3a0c --- /dev/null +++ b/packages/webview-client/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "dioxus-webview-client" +version = "0.0.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] \ No newline at end of file diff --git a/packages/webview-client/README.md b/packages/webview-client/README.md new file mode 100644 index 000000000..e3032b25c --- /dev/null +++ b/packages/webview-client/README.md @@ -0,0 +1,8 @@ +# the client part of the vdom + + +this crate is designed to be the "receiving end" of the dioxus virtualdom, using wasm_bindgen to expose an API for the receiving end. + + + + diff --git a/packages/webview-client/src/main.rs b/packages/webview-client/src/main.rs new file mode 100644 index 000000000..e7a11a969 --- /dev/null +++ b/packages/webview-client/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +}