diff --git a/packages/core/src/changelist.rs b/packages/core/src/changelist.rs index dc852520d..e44fa4c57 100644 --- a/packages/core/src/changelist.rs +++ b/packages/core/src/changelist.rs @@ -19,11 +19,9 @@ //! //! - - use bumpalo::Bump; -use crate::innerlude::{Listener}; +use crate::innerlude::Listener; /// The `Edit` represents a single modifcation of the renderer tree. /// @@ -308,14 +306,19 @@ impl<'a> EditMachine<'a> { self.forcing_new_listeners = previous; } - pub fn new_event_listener(&mut self, _listener: &Listener) { + pub fn new_event_listener(&mut self, listener: &Listener) { debug_assert!(self.traversal_is_committed()); - todo!("Event listener not wired up yet"); - // debug!("emit: new_event_listener({:?})", listener); - // let (a, b) = listener.get_callback_parts(); - // debug_assert!(a != 0); + // todo!("Event listener not wired up yet"); + log::debug!("emit: new_event_listener({:?})", listener); + let (a, b) = listener.get_callback_parts(); + debug_assert!(a != 0); // let event_id = self.ensure_string(listener.event); - // self.emitter.new_event_listener(event_id.into(), a, b); + self.emitter.push(Edit::NewEventListener { + event_type: listener.event.into(), + a, + b, + }); + // self.emitter.new_event_listener(listener.event.into(), a, b); } pub fn update_event_listener(&mut self, listener: &Listener) { @@ -326,19 +329,25 @@ impl<'a> EditMachine<'a> { return; } - // debug!("emit: update_event_listener({:?})", listener); - todo!("Event listener not wired up yet"); - // let (a, b) = listener.get_callback_parts(); - // debug_assert!(a != 0); - let _event_id = self.ensure_string(listener.event); + log::debug!("emit: update_event_listener({:?})", listener); + // todo!("Event listener not wired up yet"); + let (a, b) = listener.get_callback_parts(); + debug_assert!(a != 0); + self.emitter.push(Edit::UpdateEventListener { + event_type: listener.event.into(), + a, + b, + }); // self.emitter.update_event_listener(event_id.into(), a, b); } - pub fn remove_event_listener(&mut self, event: &str) { + pub fn remove_event_listener(&mut self, event: &'a str) { debug_assert!(self.traversal_is_committed()); + self.emitter + .push(Edit::RemoveEventListener { event_type: event }); // debug!("emit: remove_event_listener({:?})", event); - let _event_id = self.ensure_string(event); - todo!("Event listener not wired up yet"); + // let _event_id = self.ensure_string(event); + // todo!("Event listener not wired up yet"); // self.emitter.remove_event_listener(event_id.into()); } diff --git a/packages/core/src/dodriodiff.rs b/packages/core/src/dodriodiff.rs index a826db62b..269996248 100644 --- a/packages/core/src/dodriodiff.rs +++ b/packages/core/src/dodriodiff.rs @@ -63,13 +63,9 @@ pub struct DiffMachine<'a> { need_to_diff: FxHashSet, } -enum NeedToDiff { - PropsChanged, - Subscription, -} - impl<'a> DiffMachine<'a> { pub fn new(bump: &'a Bump) -> Self { + log::debug!("starsting diff machine"); Self { change_list: EditMachine::new(bump), immediate_queue: Vec::new(), @@ -83,6 +79,7 @@ impl<'a> DiffMachine<'a> { } pub fn diff_node(&mut self, old: &VNode<'a>, new: &VNode<'a>) { + log::debug!("Diffing nodes"); /* For each valid case, we "commit traversal", meaning we save this current position in the tree. Then, we diff and queue an edit event (via chagelist). s single trees - when components show up, we save that traversal and then re-enter later. @@ -876,6 +873,7 @@ impl<'a> DiffMachine<'a> { children, namespace, }) => { + log::info!("Creating {:#?}", node); if let Some(namespace) = namespace { self.change_list.create_element_ns(tag_name, namespace); } else { @@ -1001,7 +999,6 @@ enum KeyedPrefixResult { } mod support { - // // Get or create the template. // // diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 34f966397..50718868e 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -6,6 +6,7 @@ //! The goal here is to provide a consistent event interface across all renderer types use generational_arena::Index; +#[derive(Debug)] pub struct EventTrigger { pub component_id: Index, pub listener_id: u32, @@ -18,6 +19,7 @@ impl EventTrigger { } } +#[derive(Debug)] pub enum VirtualEvent { // Real events ClipboardEvent(ClipboardEvent), @@ -42,19 +44,35 @@ pub enum VirtualEvent { // these should reference the underlying event +#[derive(Debug)] pub struct ClipboardEvent {} +#[derive(Debug)] pub struct CompositionEvent {} +#[derive(Debug)] pub struct KeyboardEvent {} +#[derive(Debug)] pub struct FocusEvent {} +#[derive(Debug)] pub struct FormEvent {} +#[derive(Debug)] pub struct GenericEvent {} +#[derive(Debug)] pub struct MouseEvent {} +#[derive(Debug)] pub struct PointerEvent {} +#[derive(Debug)] pub struct SelectionEvent {} +#[derive(Debug)] pub struct TouchEvent {} +#[derive(Debug)] pub struct UIEvent {} +#[derive(Debug)] pub struct WheelEvent {} +#[derive(Debug)] pub struct MediaEvent {} +#[derive(Debug)] pub struct ImageEvent {} +#[derive(Debug)] pub struct AnimationEvent {} +#[derive(Debug)] pub struct TransitionEvent {} diff --git a/packages/core/src/nodebuilder.rs b/packages/core/src/nodebuilder.rs index e4ba2d4ce..63ff7446b 100644 --- a/packages/core/src/nodebuilder.rs +++ b/packages/core/src/nodebuilder.rs @@ -1070,7 +1070,8 @@ pub fn attr<'a>(name: &'static str, value: &'a str) -> Attribute<'a> { /// // do something when a click happens... /// }); /// ``` -pub fn on<'a, 'b, F: 'static>( +pub fn on<'a, 'b>( + // pub fn on<'a, 'b, F: 'static>( bump: &'a Bump, event: &'static str, callback: impl Fn(()) + 'a, diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 5a229290f..fdf88d4eb 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -3,8 +3,6 @@ //! //! These VNodes should be *very* cheap and *very* fast to construct - building a full tree should be insanely quick. - - use bumpalo::Bump; pub use vcomponent::VComponent; pub use velement::VElement; @@ -97,7 +95,7 @@ mod vnode { mod velement { use super::*; - use std::{fmt::Debug}; + use std::fmt::Debug; #[derive(Debug)] pub struct VElement<'a> { @@ -199,6 +197,29 @@ mod velement { .finish() } } + pub(crate) type ListenerCallback<'a> = &'a (dyn Fn(())); + union CallbackFatPtr<'a> { + callback: ListenerCallback<'a>, + parts: (u32, u32), + } + impl Listener<'_> { + #[inline] + pub(crate) fn get_callback_parts(&self) -> (u32, u32) { + assert_eq!( + std::mem::size_of::(), + std::mem::size_of::() + ); + + unsafe { + let fat = CallbackFatPtr { + callback: self.callback, + }; + let (a, b) = fat.parts; + debug_assert!(a != 0); + (a, b) + } + } + } /// The key for keyed children. /// @@ -265,9 +286,7 @@ mod vtext { /// Only supports the functional syntax mod vcomponent { use crate::innerlude::FC; - use std::{marker::PhantomData}; - - + use std::marker::PhantomData; #[derive(Debug)] pub struct VComponent<'src> { diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index cc01f54d3..0b35e3535 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -219,10 +219,29 @@ impl ActiveFrame { } } -#[cfg(test)] +// #[cfg(test)] mod tests { use super::*; + static ListenerTest: FC<()> = |ctx, props| { + ctx.view(html! { +
+ "hello" +
+ }) + }; + + #[test] + fn check_listeners() -> Result<()> { + let mut scope = Scope::new::<(), ()>(ListenerTest, (), None); + scope.run::<()>(); + + let nodes = scope.new_frame(); + dbg!(nodes); + + Ok(()) + } + #[test] fn test_scope() { let example: FC<()> = |ctx, props| { diff --git a/packages/html-macro-2/src/lib.rs b/packages/html-macro-2/src/lib.rs index bf20f922a..e607cbb7e 100644 --- a/packages/html-macro-2/src/lib.rs +++ b/packages/html-macro-2/src/lib.rs @@ -1,3 +1,18 @@ +//! +//! TODO: +//! - [ ] Support for VComponents +//! - [ ] Support for inline format in text +//! - [ ] Support for expressions in attribute positions +//! - [ ] Support for iterators +//! +//! +//! +//! +//! +//! +//! +//! + use ::{ proc_macro::TokenStream, proc_macro2::{Span, TokenStream as TokenStream2}, @@ -10,6 +25,8 @@ use ::{ }, }; +/// The html! macro makes it easy for developers to write jsx-style markup in their components. +/// We aim to keep functional parity with html templates. #[proc_macro] pub fn html(s: TokenStream) -> TokenStream { let html: HtmlRender = match syn::parse(s) { @@ -19,8 +36,10 @@ pub fn html(s: TokenStream) -> TokenStream { html.to_token_stream().into() } +// ============================================== +// Parse any stream coming from the html! macro +// ============================================== struct HtmlRender { - // ctx: Ident, kind: NodeOrList, } @@ -58,6 +77,9 @@ impl ToTokens for HtmlRender { } } +/// ============================================= +/// Parse any child as a node or list of nodes +/// ============================================= enum NodeOrList { Node(Node), List(NodeList), @@ -115,6 +137,9 @@ impl Parse for Node { } } +/// ======================================= +/// Parse the VNode::Element type +/// ======================================= struct Element { name: Ident, attrs: Vec, @@ -214,6 +239,9 @@ impl Parse for Element { } } +/// ======================================= +/// Parse a VElement's Attributes +/// ======================================= struct Attr { name: Ident, ty: AttrType, @@ -285,6 +313,10 @@ enum AttrType { // todo Bool(MaybeExpr) } +/// ======================================= +/// Parse just plain text +/// ======================================= +/// struct TextNode(MaybeExpr); impl Parse for TextNode { diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index 309250809..fac29f672 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -28,6 +28,7 @@ features = [ "Document", "Element", "HtmlElement", + "HtmlInputElement", "EventTarget", "HtmlCollection", "Node", @@ -39,6 +40,7 @@ features = [ "InputEvent", "DocumentType", "CharacterData", + "HtmlOptionElement", ] [profile.release] diff --git a/packages/web/examples/basic.rs b/packages/web/examples/basic.rs index b1a52ab62..ffd17ce5d 100644 --- a/packages/web/examples/basic.rs +++ b/packages/web/examples/basic.rs @@ -4,17 +4,38 @@ use dioxus_core::prelude::*; use dioxus_web::*; fn main() { + // Setup logging + wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); + // wasm_logger::init(wasm_logger::Config::with_prefix( + // log::Level::Debug, + // "dioxus_core", + // )); + console_error_panic_hook::set_once(); + + // Run the app wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App)); } static App: FC<()> = |ctx, _| { - ctx.view(html! { -
-
-
"Count is {}"
- - -
-
+ log::info!("Ran component"); + use dioxus::builder::*; + ctx.view(|b| { + div(b) + .child(text("hello")) + .listeners([on(b, "click", |_| { + // + log::info!("button1 clicked!"); + })]) + .finish() }) + // ctx.view(html! { + //
+ // "Hello" + // //
+ // //
"Count is ..."
+ // // + // // + // //
+ //
+ // }) }; diff --git a/packages/web/src/interpreter.rs b/packages/web/src/interpreter.rs index 9ada480f2..c5bd707f9 100644 --- a/packages/web/src/interpreter.rs +++ b/packages/web/src/interpreter.rs @@ -1,22 +1,42 @@ -use dioxus_core::changelist::Edit; +use std::fmt::Debug; + +use dioxus_core::{changelist::Edit, events::EventTrigger}; use fxhash::FxHashMap; use log::debug; use wasm_bindgen::{closure::Closure, JsCast}; -use web_sys::{window, Document, Element, Event, Node}; +use web_sys::{window, Document, Element, Event, HtmlInputElement, HtmlOptionElement, Node}; #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] pub struct CacheId(u32); +struct RootCallback(Box); +impl std::fmt::Debug for RootCallback { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!() + } +} + #[derive(Debug)] pub(crate) struct PatchMachine { - container: Element, pub(crate) stack: Stack, + + root: Element, + temporaries: FxHashMap, - callback: Option>, + + // callback: RootCallback, + // callback: Option>, document: Document, - // templates: FxHashMap, + + // every callback gets a monotomically increasing callback ID + callback_id: usize, + + // Map of callback_id to component index and listener id + callback_map: FxHashMap, } +// templates: FxHashMap, + #[derive(Debug, Default)] pub struct Stack { list: Vec, @@ -30,13 +50,13 @@ impl Stack { } pub fn push(&mut self, node: Node) { - debug!("stack-push: {:?}", node); + // debug!("stack-push: {:?}", node); self.list.push(node); } pub fn pop(&mut self) -> Node { let res = self.list.pop().unwrap(); - debug!("stack-pop: {:?}", res); + // debug!("stack-pop: {:?}", res); res } @@ -46,10 +66,10 @@ impl Stack { } pub fn top(&self) -> &Node { - log::info!( - "Called top of stack with {} items remaining", - self.list.len() - ); + // log::info!( + // "Called top of stack with {} items remaining", + // self.list.len() + // ); match self.list.last() { Some(a) => a, None => panic!("Called 'top' of an empty stack, make sure to push the root first"), @@ -58,19 +78,23 @@ impl Stack { } impl PatchMachine { - pub fn new(container: Element) -> Self { + pub fn new(root: Element, event_callback: impl Fn(EventTrigger)) -> Self { let document = window() .expect("must have access to the window") .document() .expect("must have access to the Document"); + // attach all listeners to the container element + + // templates: Default::default(), Self { - // templates: Default::default(), - container, + root, stack: Stack::with_capacity(20), temporaries: Default::default(), - callback: None, + // callback: None, document, + callback_id: 0, + callback_map: FxHashMap::default(), } } @@ -81,7 +105,7 @@ impl PatchMachine { } pub fn start(&mut self) { - if let Some(child) = self.container.first_child() { + if let Some(child) = self.root.first_child() { self.stack.push(child); } } @@ -96,9 +120,9 @@ impl PatchMachine { // self.templates.get(&id) } - pub fn init_events_trampoline(&mut self, _trampoline: ()) { - todo!("Event trampoline not a thing anymore") - // pub fn init_events_trampoline(&mut self, mut trampoline: EventsTrampoline) { + /// On any listener wakeup, find the listener that generated the event from th e attribute + // pub fn init_events_trampoline(&mut self, _trampoline: ()) { + pub fn init_events_trampoline(&mut self, event_channel: impl Fn(EventTrigger)) { // self.callback = Some(Closure::wrap(Box::new(move |event: &web_sys::Event| { // let target = event // .target() @@ -107,12 +131,12 @@ impl PatchMachine { // .expect("not a valid element"); // let typ = event.type_(); // let a: u32 = target - // .get_attribute(&format!("dodrio-a-{}", typ)) + // .get_attribute(&format!("dioxus-a-{}", typ)) // .and_then(|v| v.parse().ok()) // .unwrap_or_default(); // let b: u32 = target - // .get_attribute(&format!("dodrio-b-{}", typ)) + // .get_attribute(&format!("dioxus-b-{}", typ)) // .and_then(|v| v.parse().ok()) // .unwrap_or_default(); @@ -180,40 +204,44 @@ impl PatchMachine { Edit::SetAttribute { name, value } => { let node = self.stack.top(); - if let Some(node) = node.dyn_ref::() { + if let Some(node) = node.dyn_ref::() { node.set_attribute(name, value).unwrap(); // Some attributes are "volatile" and don't work through `setAttribute`. - // TODO: - // if name == "value" { - // node.set_value(value); - // } - // if name == "checked" { - // node.set_checked(true); - // } - // if name == "selected" { - // node.set_selected(true); - // } + if name == "value" { + node.set_value(value); + } + if name == "checked" { + node.set_checked(true); + } + } + + if let Some(node) = node.dyn_ref::() { + if name == "selected" { + node.set_selected(true); + } } } // 4 Edit::RemoveAttribute { name } => { let node = self.stack.top(); - if let Some(node) = node.dyn_ref::() { + if let Some(node) = node.dyn_ref::() { node.remove_attribute(name).unwrap(); // Some attributes are "volatile" and don't work through `removeAttribute`. - // TODO: - // if name == "value" { - // node.set_value(""); - // } - // if name == "checked" { - // node.set_checked(false); - // } - // if name == "selected" { - // node.set_selected(false); - // } + if name == "value" { + node.set_value(""); + } + if name == "checked" { + node.set_checked(false); + } + } + + if let Some(node) = node.dyn_ref::() { + if name == "selected" { + node.set_selected(true); + } } } @@ -266,29 +294,36 @@ impl PatchMachine { // 11 Edit::NewEventListener { event_type, a, b } => { + // attach the correct attributes to the element + // these will be used by accessing the event's target + // This ensures we only ever have one handler attached to the root, but decide + // dynamically when we want to call a listener. + let el = self.stack.top(); let el = el .dyn_ref::() .expect(&format!("not an element: {:?}", el)); - el.add_event_listener_with_callback( - event_type, - self.callback.as_ref().unwrap().as_ref().unchecked_ref(), - ) - .unwrap(); + // el.add_event_listener_with_callback( + // event_type, + // self.callback.as_ref().unwrap().as_ref().unchecked_ref(), + // ) + // .unwrap(); debug!("adding attributes: {}, {}", a, b); - el.set_attribute(&format!("dodrio-a-{}", event_type), &a.to_string()) + el.set_attribute(&format!("dioxus-a-{}", event_type), &a.to_string()) .unwrap(); - el.set_attribute(&format!("dodrio-b-{}", event_type), &b.to_string()) + el.set_attribute(&format!("dioxus-b-{}", event_type), &b.to_string()) .unwrap(); } // 12 Edit::UpdateEventListener { event_type, a, b } => { + // update our internal mapping, and then modify the attribute + if let Some(el) = self.stack.top().dyn_ref::() { - el.set_attribute(&format!("dodrio-a-{}", event_type), &a.to_string()) + el.set_attribute(&format!("dioxus-a-{}", event_type), &a.to_string()) .unwrap(); - el.set_attribute(&format!("dodrio-b-{}", event_type), &b.to_string()) + el.set_attribute(&format!("dioxus-b-{}", event_type), &b.to_string()) .unwrap(); } } @@ -296,11 +331,11 @@ impl PatchMachine { // 13 Edit::RemoveEventListener { event_type } => { if let Some(el) = self.stack.top().dyn_ref::() { - el.remove_event_listener_with_callback( - event_type, - self.callback.as_ref().unwrap().as_ref().unchecked_ref(), - ) - .unwrap(); + // el.remove_event_listener_with_callback( + // event_type, + // self.callback.as_ref().unwrap().as_ref().unchecked_ref(), + // ) + // .unwrap(); } } diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 0d3c116ba..454d10c26 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -62,16 +62,16 @@ impl WebsysRenderer { let (sender, mut receiver) = mpsc::unbounded::(); let body_element = prepare_websys_dom(); - let mut patch_machine = interpreter::PatchMachine::new(body_element.clone()); + let mut patch_machine = interpreter::PatchMachine::new(body_element.clone(), |_| {}); let root_node = body_element.first_child().unwrap(); patch_machine.stack.push(root_node); // todo: initialize the event registry properly on the root - self.internal_dom - .rebuild()? - .iter() - .for_each(|edit| patch_machine.handle_edit(edit)); + self.internal_dom.rebuild()?.iter().for_each(|edit| { + log::debug!("patching with {:?}", edit); + patch_machine.handle_edit(edit); + }); // Event loop waits for the receiver to finish up // TODO! Connect the sender to the virtual dom's suspense system