diff --git a/Cargo.toml b/Cargo.toml index 19f2fae71..1e1a592bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ dioxus-mobile = { path = "./packages/mobile", optional = true } [features] # core -default = ["core", "ssr"] +default = ["core", "ssr", "web"] core = ["macro", "hooks", "html"] macro = ["dioxus-core-macro"] hooks = ["dioxus-hooks"] @@ -56,12 +56,17 @@ gloo-timers = "0.2.1" [target.'cfg(target_arch = "wasm32")'.dev-dependencies] gloo-timers = "0.2.1" -surf = { version = "2.2.0", default-features = false, features = [ - "wasm-client", -], git = "https://github.com/jkelleyrtp/surf/", branch = "jk/fix-the-wasm" } +wasm-logger = "0.2.0" +console_error_panic_hook = "0.1.6" +rand = { version = "0.8.4", features = ["small_rng"] } +wasm-bindgen = { version = "0.2.71", features = ["enable-interning"] } + +# surf = { version = "2.2.0", default-features = false, features = [ +# "wasm-client", +# ], git = "https://github.com/jkelleyrtp/surf/", branch = "jk/fix-the-wasm" } -[dependencies.getrandom] +[dev-dependencies.getrandom] version = "0.2" features = ["js"] diff --git a/examples/web_tick.rs b/examples/web_tick.rs new file mode 100644 index 000000000..8d58acfec --- /dev/null +++ b/examples/web_tick.rs @@ -0,0 +1,143 @@ +#![allow(non_upper_case_globals, non_snake_case)] +//! Example: Webview Renderer +//! ------------------------- +//! +//! This example shows how to use the dioxus_desktop crate to build a basic desktop application. +//! +//! Under the hood, the dioxus_desktop crate bridges a native Dioxus VirtualDom with a custom prebuit application running +//! in the webview runtime. Custom handlers are provided for the webview instance to consume patches and emit user events +//! into the native VDom instance. +//! +//! Currently, NodeRefs won't work properly, but all other event functionality will. + +use dioxus::prelude::*; + +fn main() { + // Setup logging + // wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); + // console_error_panic_hook::set_once(); + for adj in ADJECTIVES { + wasm_bindgen::intern(adj); + } + for col in COLOURS { + wasm_bindgen::intern(col); + } + for no in NOUNS { + wasm_bindgen::intern(no); + } + wasm_bindgen::intern("col-md-1"); + wasm_bindgen::intern("col-md-6"); + wasm_bindgen::intern("glyphicon glyphicon-remove remove"); + wasm_bindgen::intern("remove"); + wasm_bindgen::intern("dioxus"); + wasm_bindgen::intern("lbl"); + wasm_bindgen::intern("true"); + + dioxus::web::launch(App, |c| c); +} + +static App: FC<()> = |cx| { + // let mut count = use_state(cx, || 0); + + let mut rng = SmallRng::from_entropy(); + let rows = (0..1_000).map(|f| { + let label = Label::new(&mut rng); + rsx! { + Row { + row_id: f, + label: label + } + } + }); + cx.render(rsx! { + table { + tbody { + {rows} + } + } + }) + // cx.render(rsx! { + // div { + // // h1 { "Hifive counter: {count}" } + // // {cx.children()} + // // button { onclick: move |_| count += 1, "Up high!" } + // // button { onclick: move |_| count -= 1, "Down low!" } + // {(0..1000).map(|i| rsx!{ div { "Count: {count}" } })} + // } + // }) +}; + +#[derive(PartialEq, Props)] +struct RowProps { + row_id: usize, + label: Label, +} +fn Row<'a>(cx: Context<'a, RowProps>) -> DomTree { + let [adj, col, noun] = cx.label.0; + cx.render(rsx! { + tr { + td { class:"col-md-1", "{cx.row_id}" } + td { class:"col-md-1", onclick: move |_| { /* run onselect */ } + a { class: "lbl", "{adj}" "{col}" "{noun}" } + } + td { class: "col-md-1" + a { class: "remove", onclick: move |_| {/* remove */} + span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" } + } + } + td { class: "col-md-6" } + } + }) +} +use rand::prelude::*; + +#[derive(PartialEq)] +struct Label([&'static str; 3]); + +impl Label { + fn new(rng: &mut SmallRng) -> Self { + Label([ + ADJECTIVES.choose(rng).unwrap(), + COLOURS.choose(rng).unwrap(), + NOUNS.choose(rng).unwrap(), + ]) + } +} + +static ADJECTIVES: &[&str] = &[ + "pretty", + "large", + "big", + "small", + "tall", + "short", + "long", + "handsome", + "plain", + "quaint", + "clean", + "elegant", + "easy", + "angry", + "crazy", + "helpful", + "mushy", + "odd", + "unsightly", + "adorable", + "important", + "inexpensive", + "cheap", + "expensive", + "fancy", +]; + +static COLOURS: &[&str] = &[ + "red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black", + "orange", +]; + +static NOUNS: &[&str] = &[ + "table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger", + "pizza", "mouse", "keyboard", +]; diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 005afdd29..c55ddecd1 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -26,7 +26,7 @@ fxhash = "0.2.1" longest-increasing-subsequence = "0.1.0" # internall used -log = "0.4" +log = { verison = "0.4", features = ["release_max_level_off"] } futures-util = "0.3.15" diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index 840cb34b8..9ed7a2277 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -90,17 +90,17 @@ impl SharedResources { let comps = unsafe { &*components.get() }; if let Some(scope) = comps.get(idx.0) { - todo!("implement immediates again") + // todo!("implement immediates again") + // + // queue - // .unbounded_send(EventTrigger::new( - // VirtualEvent::ScheduledUpdate { - // height: scope.height, - // }, - // idx, - // None, - // EventPriority::High, - // )) - // .expect("The event queu receiver should *never* be dropped"); + // .unbounded_send(EventTrigger::new( + // V + // idx, + // None, + // EventPriority::High, + // )) + // .expect("The event queu receiver should *never* be dropped"); } }) as Rc }; diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 1646219aa..4aeabd89b 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -138,14 +138,12 @@ impl<'bump> DiffMachine<'bump> { } // - pub async fn diff_scope(&mut self, id: ScopeId) -> Result<()> { - let component = self - .vdom - .get_scope_mut(id) - .ok_or_else(|| Error::NotMounted)?; - let (old, new) = (component.frames.wip_head(), component.frames.fin_head()); - self.diff_node(old, new); - Ok(()) + pub async fn diff_scope(&mut self, id: ScopeId) { + if let Some(component) = self.vdom.get_scope_mut(id) { + let (old, new) = (component.frames.wip_head(), component.frames.fin_head()); + self.stack.push(DiffInstruction::DiffNode { new, old }); + self.work().await; + } } /// Progress the diffing for this "fiber" diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 5c56f953e..41868aa95 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -153,6 +153,7 @@ pub enum VirtualEvent { MouseEvent(on::MouseEvent), PointerEvent(on::PointerEvent), } + impl VirtualEvent { pub fn is_input_event(&self) -> bool { match self { @@ -215,7 +216,7 @@ pub mod on { #![allow(unused)] use bumpalo::boxed::Box as BumpBox; - use std::{cell::RefCell, fmt::Debug, ops::Deref, rc::Rc}; + use std::{any::Any, cell::RefCell, fmt::Debug, ops::Deref, rc::Rc}; use crate::{ innerlude::NodeFactory, @@ -563,6 +564,8 @@ pub mod on { } 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 @@ -571,14 +574,18 @@ pub mod on { fn cancelable(&self) -> bool; /// Returns whether the event is composed or not fn composed(&self) -> bool; - /// Returns the event's path - fn composed_path(&self) -> String; + + // 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) -> usize; + 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 @@ -590,7 +597,7 @@ pub mod on { /// 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) -> usize; + fn time_stamp(&self) -> f64; } pub trait ClipboardEventInner { @@ -688,8 +695,8 @@ pub mod on { pub trait PointerEventInner { // Mouse only fn alt_key(&self) -> bool; - fn button(&self) -> usize; - fn buttons(&self) -> usize; + fn button(&self) -> i16; + fn buttons(&self) -> u16; fn client_x(&self) -> i32; fn client_y(&self) -> i32; fn ctrl_key(&self) -> bool; @@ -699,12 +706,12 @@ pub mod on { fn screen_x(&self) -> i32; fn screen_y(&self) -> i32; fn shift_key(&self) -> bool; - fn get_modifier_state(&self, key_code: usize) -> bool; - fn pointer_id(&self) -> usize; - fn width(&self) -> usize; - fn height(&self) -> usize; - fn pressure(&self) -> usize; - fn tangential_pressure(&self) -> usize; + 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; @@ -719,7 +726,7 @@ pub mod on { fn ctrl_key(&self) -> bool; fn meta_key(&self) -> bool; fn shift_key(&self) -> bool; - fn get_modifier_state(&self, key_code: usize) -> bool; + fn get_modifier_state(&self, key_code: &str) -> bool; // changedTouches: DOMTouchList, // targetTouches: DOMTouchList, // touches: DOMTouchList, @@ -731,10 +738,10 @@ pub mod on { } pub trait WheelEventInner { - fn delta_mode(&self) -> i32; - fn delta_x(&self) -> i32; - fn delta_y(&self) -> i32; - fn delta_z(&self) -> i32; + fn delta_mode(&self) -> u32; + fn delta_x(&self) -> f64; + fn delta_y(&self) -> f64; + fn delta_z(&self) -> f64; } pub trait MediaEventInner {} diff --git a/packages/core/src/scheduler.rs b/packages/core/src/scheduler.rs index b8fc07f15..3c635da04 100644 --- a/packages/core/src/scheduler.rs +++ b/packages/core/src/scheduler.rs @@ -50,15 +50,13 @@ pub struct Scheduler { shared: SharedResources, - waypoints: VecDeque, - high_priorty: PriortySystem, medium_priority: PriortySystem, low_priority: PriortySystem, } pub enum FiberResult<'a> { - Done(&'a mut Mutations<'a>), + Done(Mutations<'a>), Interrupted, } @@ -75,7 +73,6 @@ impl Scheduler { garbage_scopes: HashSet::new(), current_priority: EventPriority::Low, - waypoints: VecDeque::new(), high_priorty: PriortySystem::new(), medium_priority: PriortySystem::new(), @@ -202,7 +199,9 @@ impl Scheduler { } pub fn has_work(&self) -> bool { - self.waypoints.len() > 0 + self.high_priorty.has_work() + || self.medium_priority.has_work() + || self.low_priority.has_work() } pub fn has_pending_garbage(&self) -> bool { @@ -219,10 +218,10 @@ impl Scheduler { } /// If a the fiber finishes its works (IE needs to be committed) the scheduler will drop the dirty scope - pub fn work_with_deadline( - &mut self, - mut deadline: &mut Pin>>, - ) -> FiberResult { + pub async fn work_with_deadline<'a>( + &'a mut self, + deadline: &mut Pin>>, + ) -> FiberResult<'a> { // check if we need to elevate priority self.current_priority = match ( self.high_priorty.has_work(), @@ -234,11 +233,46 @@ impl Scheduler { (false, false, _) => EventPriority::Low, }; - let mut is_ready = || -> bool { (&mut deadline).now_or_never().is_some() }; + let mut machine = DiffMachine::new_headless(&self.shared); - // TODO: remove this unwrap - proprogate errors out - // self.get_current_fiber().work(is_ready).unwrap() - todo!() + let dirty_root = { + let dirty_roots = match self.current_priority { + EventPriority::High => &self.high_priorty.dirty_scopes, + EventPriority::Medium => &self.medium_priority.dirty_scopes, + EventPriority::Low => &self.low_priority.dirty_scopes, + }; + let mut height = 0; + let mut dirty_root = { + let root = dirty_roots.iter().next(); + if root.is_none() { + return FiberResult::Done(machine.mutations); + } + root.unwrap() + }; + + for root in dirty_roots { + if let Some(scope) = self.shared.get_scope(*root) { + if scope.height < height { + height = scope.height; + dirty_root = root; + } + } + } + dirty_root + }; + + match { + let fut = machine.diff_scope(*dirty_root).fuse(); + pin_mut!(fut); + + match futures_util::future::select(deadline, fut).await { + futures_util::future::Either::Left((deadline, work_fut)) => true, + futures_util::future::Either::Right((_, deadline_fut)) => false, + } + } { + true => FiberResult::Done(machine.mutations), + false => FiberResult::Interrupted, + } } // waits for a trigger, canceling early if the deadline is reached @@ -356,32 +390,6 @@ pub struct DirtyScope { start_tick: u32, } -/* -A "waypoint" represents a frozen unit in time for the DiffingMachine to resume from. Whenever the deadline runs out -while diffing, the diffing algorithm generates a Waypoint in order to easily resume from where it left off. Waypoints are -fairly expensive to create, especially for big trees, so it's a good idea to pre-allocate them. - -Waypoints are created pessimisticly, and are only generated when an "Error" state is bubbled out of the diffing machine. -This saves us from wasting cycles book-keeping waypoints for 99% of edits where the deadline is not reached. -*/ -pub struct Waypoint { - // the progenitor of this waypoint - root: ScopeId, - - edits: Vec>, - - // a saved position in the tree - // these indicies continue to map through the tree into children nodes. - // A sequence of usizes is all that is needed to represent the path to a node. - tree_position: SmallVec<[usize; 10]>, - - seen_scopes: HashSet, - - invalidate_scopes: HashSet, - - priority_level: EventPriority, -} - pub struct PriortySystem { pub pending_scopes: Vec, pub dirty_scopes: HashSet, diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 3b95aec76..76a283154 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -298,11 +298,11 @@ impl VirtualDom { } // Create work from the pending event queue - self.scheduler.consume_pending_events(); + self.scheduler.consume_pending_events().unwrap(); // Work through the current subtree, and commit the results when it finishes // When the deadline expires, give back the work - match self.scheduler.work_with_deadline(&mut deadline) { + match self.scheduler.work_with_deadline(&mut deadline).await { FiberResult::Done(mut mutations) => { committed_mutations.extend(&mut mutations); @@ -332,7 +332,10 @@ impl VirtualDom { true } - pub async fn wait_for_any_work(&self) {} + pub async fn wait_for_any_work(&mut self) { + let mut timeout = Box::pin(futures_util::future::pending().fuse()); + self.scheduler.wait_for_any_trigger(&mut timeout).await; + } } // TODO! diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index e02b10861..cc588eb91 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -14,9 +14,9 @@ js-sys = "0.3" wasm-bindgen = { version = "0.2.71", features = ["enable-interning"] } lazy_static = "1.4.0" wasm-bindgen-futures = "0.4.20" -wasm-logger = "0.2.0" log = "0.4.14" fxhash = "0.2.1" +wasm-logger = "0.2.0" console_error_panic_hook = "0.1.6" generational-arena = "0.2.8" wasm-bindgen-test = "0.3.21" diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index d36e6e2d8..d48e8d187 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -1,7 +1,7 @@ -use std::{collections::HashMap, rc::Rc, sync::Arc}; +use std::{collections::HashMap, fmt::Debug, rc::Rc, sync::Arc}; use dioxus_core::{ - events::{EventTrigger, VirtualEvent}, + events::{on::GenericEventInner, EventTrigger, VirtualEvent}, mutations::NodeRefMutation, DomEdit, ElementId, ScopeId, }; @@ -9,7 +9,7 @@ use fxhash::FxHashMap; use wasm_bindgen::{closure::Closure, JsCast}; use web_sys::{ window, CssStyleDeclaration, Document, Element, Event, HtmlElement, HtmlInputElement, - HtmlOptionElement, Node, NodeList, + HtmlOptionElement, Node, NodeList, UiEvent, }; use crate::{nodeslab::NodeSlab, WebConfig}; @@ -436,284 +436,82 @@ impl Stack { } } -fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent { +fn virtual_event_from_websys_event(event: web_sys::Event) -> VirtualEvent { + use crate::events::*; use dioxus_core::events::on::*; match event.type_().as_str() { "copy" | "cut" | "paste" => { - struct WebsysClipboardEvent(); - impl ClipboardEventInner for WebsysClipboardEvent {} - VirtualEvent::ClipboardEvent(ClipboardEvent(Rc::new(WebsysClipboardEvent()))) + VirtualEvent::ClipboardEvent(ClipboardEvent(Rc::new(WebsysClipboardEvent(event)))) } - "compositionend" | "compositionstart" | "compositionupdate" => { let evt: web_sys::CompositionEvent = event.clone().dyn_into().unwrap(); - struct WebsysCompositionEvent(web_sys::CompositionEvent); - impl CompositionEventInner for WebsysCompositionEvent { - fn data(&self) -> String { - todo!() - } - } VirtualEvent::CompositionEvent(CompositionEvent(Rc::new(WebsysCompositionEvent(evt)))) } - "keydown" | "keypress" | "keyup" => { - struct Event(web_sys::KeyboardEvent); - impl KeyboardEventInner for Event { - 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 { - todo!("Locale is currently not supported. :(") - } - - 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 - } - } - let evt: web_sys::KeyboardEvent = event.clone().dyn_into().unwrap(); - VirtualEvent::KeyboardEvent(KeyboardEvent(Rc::new(Event(evt)))) + let evt: web_sys::KeyboardEvent = event.dyn_into().unwrap(); + VirtualEvent::KeyboardEvent(KeyboardEvent(Rc::new(WebsysKeyboardEvent(evt)))) } - "focus" | "blur" => { - struct Event(web_sys::FocusEvent); - impl FocusEventInner for Event {} - let evt: web_sys::FocusEvent = event.clone().dyn_into().unwrap(); - VirtualEvent::FocusEvent(FocusEvent(Rc::new(Event(evt)))) + let evt: web_sys::FocusEvent = event.dyn_into().unwrap(); + VirtualEvent::FocusEvent(FocusEvent(Rc::new(WebsysFocusEvent(evt)))) } - "change" => { - // struct Event(web_sys::Event); - // impl GenericEventInner for Event { - // fn bubbles(&self) -> bool { - // todo!() - // } - - // fn cancel_bubble(&self) { - // todo!() - // } - - // fn cancelable(&self) -> bool { - // todo!() - // } - - // fn composed(&self) -> bool { - // todo!() - // } - - // fn composed_path(&self) -> String { - // todo!() - // } - - // fn current_target(&self) { - // todo!() - // } - - // fn default_prevented(&self) -> bool { - // todo!() - // } - - // fn event_phase(&self) -> usize { - // todo!() - // } - - // fn is_trusted(&self) -> bool { - // todo!() - // } - - // fn prevent_default(&self) { - // todo!() - // } - - // fn stop_immediate_propagation(&self) { - // todo!() - // } - - // fn stop_propagation(&self) { - // todo!() - // } - - // fn target(&self) { - // todo!() - // } - - // fn time_stamp(&self) -> usize { - // todo!() - // } - // } - // let evt: web_sys::Event = event.clone().dyn_into().expect("wrong error typ"); - // VirtualEvent::Event(GenericEvent(Rc::new(Event(evt)))) - todo!() + let evt = event.dyn_into().unwrap(); + VirtualEvent::UIEvent(UIEvent(Rc::new(WebsysGenericUiEvent(evt)))) } - "input" | "invalid" | "reset" | "submit" => { - // is a special react events - let evt: web_sys::InputEvent = event.clone().dyn_into().expect("wrong event type"); - let this: web_sys::EventTarget = evt.target().unwrap(); - - let value = (&this) - .dyn_ref() - .map(|input: &web_sys::HtmlInputElement| input.value()) - .or_else(|| { - (&this) - .dyn_ref() - .map(|input: &web_sys::HtmlTextAreaElement| 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"); - - todo!() - // VirtualEvent::FormEvent(FormEvent { value }) + let evt: web_sys::InputEvent = event.clone().dyn_into().unwrap(); + VirtualEvent::FormEvent(FormEvent(Rc::new(WebsysFormEvent(evt)))) } - "click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit" | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter" | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => { let evt: web_sys::MouseEvent = event.clone().dyn_into().unwrap(); - - #[derive(Debug)] - pub struct CustomMouseEvent(web_sys::MouseEvent); - impl dioxus_core::events::on::MouseEventInner for CustomMouseEvent { - 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) - } - } - VirtualEvent::MouseEvent(MouseEvent(Rc::new(CustomMouseEvent(evt)))) + VirtualEvent::MouseEvent(MouseEvent(Rc::new(WebsysMouseEvent(evt)))) } - "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture" | "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => { let evt: web_sys::PointerEvent = event.clone().dyn_into().unwrap(); - todo!() + VirtualEvent::PointerEvent(PointerEvent(Rc::new(WebsysPointerEvent(evt)))) } - "select" => { - // let evt: web_sys::SelectionEvent = event.clone().dyn_into().unwrap(); - // not required to construct anything special beyond standard event stuff - todo!() + let evt: web_sys::UiEvent = event.clone().dyn_into().unwrap(); + VirtualEvent::SelectionEvent(SelectionEvent(Rc::new(WebsysGenericUiEvent(evt)))) } - "touchcancel" | "touchend" | "touchmove" | "touchstart" => { let evt: web_sys::TouchEvent = event.clone().dyn_into().unwrap(); - todo!() + VirtualEvent::TouchEvent(TouchEvent(Rc::new(WebsysTouchEvent(evt)))) } - "scroll" => { - // let evt: web_sys::UIEvent = event.clone().dyn_into().unwrap(); - todo!() + let evt: web_sys::UiEvent = event.clone().dyn_into().unwrap(); + VirtualEvent::UIEvent(UIEvent(Rc::new(WebsysGenericUiEvent(evt)))) } - "wheel" => { let evt: web_sys::WheelEvent = event.clone().dyn_into().unwrap(); - todo!() + VirtualEvent::WheelEvent(WheelEvent(Rc::new(WebsysWheelEvent(evt)))) } "animationstart" | "animationend" | "animationiteration" => { let evt: web_sys::AnimationEvent = event.clone().dyn_into().unwrap(); - todo!() + VirtualEvent::AnimationEvent(AnimationEvent(Rc::new(WebsysAnimationEvent(evt)))) } - "transitionend" => { let evt: web_sys::TransitionEvent = event.clone().dyn_into().unwrap(); - todo!() + VirtualEvent::TransitionEvent(TransitionEvent(Rc::new(WebsysTransitionEvent(evt)))) } - "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted" | "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play" | "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend" | "timeupdate" | "volumechange" | "waiting" => { - // not required to construct anything special beyond standard event stuff - - // let evt: web_sys::MediaEvent = event.clone().dyn_into().unwrap(); - // let evt: web_sys::MediaEvent = event.clone().dyn_into().unwrap(); - todo!() + let evt: web_sys::UiEvent = event.clone().dyn_into().unwrap(); + VirtualEvent::MediaEvent(MediaEvent(Rc::new(WebsysMediaEvent(evt)))) } - "toggle" => { - // not required to construct anything special beyond standard event stuff (target) - - // let evt: web_sys::ToggleEvent = event.clone().dyn_into().unwrap(); - todo!() + let evt: web_sys::UiEvent = event.clone().dyn_into().unwrap(); + VirtualEvent::ToggleEvent(ToggleEvent(Rc::new(WebsysToggleEvent(evt)))) } _ => { - todo!() + let evt: web_sys::UiEvent = event.clone().dyn_into().unwrap(); + VirtualEvent::UIEvent(UIEvent(Rc::new(WebsysGenericUiEvent(evt)))) } } } @@ -763,7 +561,7 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result { // let triggered_scope: ScopeId = KeyData::from_ffi(gi_id).into(); log::debug!("Triggered scope is {:#?}", triggered_scope); Ok(EventTrigger::new( - virtual_event_from_websys_event(event), + virtual_event_from_websys_event(event.clone()), ScopeId(triggered_scope as usize), Some(ElementId(real_id as usize)), dioxus_core::events::EventPriority::High, diff --git a/packages/web/src/events.rs b/packages/web/src/events.rs new file mode 100644 index 000000000..3a84f080c --- /dev/null +++ b/packages/web/src/events.rs @@ -0,0 +1,418 @@ +//! Ported events into Dioxus Synthetic Event system +//! +//! event porting is pretty boring, sorry. + +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 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::InputEvent); +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()) + }) + .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 9b840d74c..e8180ef85 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -56,6 +56,7 @@ use std::rc::Rc; pub use crate::cfg::WebConfig; use crate::dom::load_document; +use cache::intern_cache; use dioxus::prelude::Properties; use dioxus::virtual_dom::VirtualDom; pub use dioxus_core as dioxus; @@ -64,6 +65,7 @@ use dioxus_core::prelude::FC; mod cache; mod cfg; mod dom; +mod events; mod nodeslab; mod ric_raf; @@ -114,6 +116,8 @@ where pub async fn run_with_props(root: FC, root_props: T, cfg: WebConfig) { let mut dom = VirtualDom::new_with_props(root, root_props); + intern_cache(); + let hydrating = cfg.hydrate; let root_el = load_document().get_element_by_id(&cfg.rootname).unwrap(); diff --git a/packages/web/src/ric_raf.rs b/packages/web/src/ric_raf.rs index 429fa0259..0a914540c 100644 --- a/packages/web/src/ric_raf.rs +++ b/packages/web/src/ric_raf.rs @@ -1,10 +1,7 @@ //! RequestAnimationFrame and RequestIdleCallback port and polyfill. -use std::{cell::RefCell, fmt, rc::Rc}; - use gloo_timers::future::TimeoutFuture; use js_sys::Function; -use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; use wasm_bindgen::{prelude::Closure, JsValue}; use web_sys::Window; @@ -21,13 +18,15 @@ impl RafLoop { pub fn new() -> Self { let (raf_sender, raf_receiver) = async_channel::unbounded(); - let raf_closure: Closure = - Closure::wrap(Box::new(move |v: JsValue| raf_sender.try_send(()).unwrap())); + let raf_closure: Closure = Closure::wrap(Box::new(move |_v: JsValue| { + raf_sender.try_send(()).unwrap() + })); let (ric_sender, ric_receiver) = async_channel::unbounded(); - let ric_closure: Closure = - Closure::wrap(Box::new(move |v: JsValue| ric_sender.try_send(()).unwrap())); + let ric_closure: Closure = Closure::wrap(Box::new(move |_v: JsValue| { + ric_sender.try_send(()).unwrap() + })); // execute the polyfill for safari Function::new_no_args(include_str!("./ricpolyfill.js")) @@ -44,45 +43,16 @@ impl RafLoop { } /// waits for some idle time and returns a timeout future that expires after the idle time has passed pub async fn wait_for_idle_time(&self) -> TimeoutFuture { - // comes with its own safari polyfill :) - let ric_fn = self.ric_closure.as_ref().dyn_ref::().unwrap(); let deadline: u32 = self.window.request_idle_callback(ric_fn).unwrap(); - self.ric_receiver.recv().await.unwrap(); - let deadline = TimeoutFuture::new(deadline); deadline } pub async fn wait_for_raf(&self) { let raf_fn = self.raf_closure.as_ref().dyn_ref::().unwrap(); - let id: i32 = self.window.request_animation_frame(raf_fn).unwrap(); + let _id: i32 = self.window.request_animation_frame(raf_fn).unwrap(); self.raf_receiver.recv().await.unwrap(); } } - -#[derive(Debug)] -pub struct AnimationFrame { - render_id: i32, - closure: Closure, - callback_wrapper: Rc>>, -} - -struct CallbackWrapper(Box); -impl fmt::Debug for CallbackWrapper { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("CallbackWrapper") - } -} - -impl Drop for AnimationFrame { - fn drop(&mut self) { - if self.callback_wrapper.borrow_mut().is_some() { - web_sys::window() - .unwrap_throw() - .cancel_animation_frame(self.render_id) - .unwrap_throw() - } - } -}