Преглед на файлове

Feat: begin WIP on html macro

Jonathan Kelley преди 4 години
родител
ревизия
a8b1225

+ 26 - 17
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());
     }
 

+ 3 - 6
packages/core/src/dodriodiff.rs

@@ -63,13 +63,9 @@ pub struct DiffMachine<'a> {
     need_to_diff: FxHashSet<Index>,
 }
 
-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.
     // //

+ 18 - 0
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 {}

+ 2 - 1
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,

+ 25 - 6
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::<ListenerCallback>(),
+                std::mem::size_of::<CallbackFatPtr>()
+            );
+
+            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> {

+ 20 - 1
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! {
+            <div onclick={|_| println!("Hell owlrld")}>
+                "hello"
+            </div>
+        })
+    };
+
+    #[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| {

+ 33 - 1
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<Attr>,
@@ -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<LitBool>)
 }
 
+/// =======================================
+/// Parse just plain text
+/// =======================================
+///
 struct TextNode(MaybeExpr<LitStr>);
 
 impl Parse for TextNode {

+ 2 - 0
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]

+ 29 - 8
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! {
-        <div>
-            <div class="flex items-center justify-center flex-col">
-                <div class="font-bold text-xl"> "Count is {}" </div>
-                <button onclick={move |_| log::info!("button1 clicked!")}> "increment" </button>
-                <button onclick={move |_| log::info!("button2 clicked!")}> "decrement" </button>
-            </div>
-        </div>
+    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! {
+    //     <div onclick={move |_| log::info!("button1 clicked!")}>
+    //         "Hello"
+    //         // <div class="flex items-center justify-center flex-col">
+    //         //     <div class="font-bold text-xl"> "Count is ..." </div>
+    //         //     <button onclick={move |_| log::info!("button1 clicked!")}> "increment" </button>
+    //         //     <button onclick={move |_| log::info!("button2 clicked!")}> "decrement" </button>
+    //         // </div>
+    //     </div>
+    // })
 };

+ 92 - 57
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<dyn Fn(EventTrigger)>);
+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<u32, Node>,
-    callback: Option<Closure<dyn FnMut(&Event)>>,
+
+    // callback: RootCallback,
+    // callback: Option<Closure<dyn Fn(EventTrigger)>>,
     document: Document,
-    // templates: FxHashMap<CacheId, Node>,
+
+    // every callback gets a monotomically increasing callback ID
+    callback_id: usize,
+
+    // Map of callback_id to component index and listener id
+    callback_map: FxHashMap<usize, (usize, usize)>,
 }
 
+// templates: FxHashMap<CacheId, Node>,
+
 #[derive(Debug, Default)]
 pub struct Stack {
     list: Vec<Node>,
@@ -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::<Element>() {
+                if let Some(node) = node.dyn_ref::<HtmlInputElement>() {
                     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::<HtmlOptionElement>() {
+                    if name == "selected" {
+                        node.set_selected(true);
+                    }
                 }
             }
 
             // 4
             Edit::RemoveAttribute { name } => {
                 let node = self.stack.top();
-                if let Some(node) = node.dyn_ref::<Element>() {
+                if let Some(node) = node.dyn_ref::<HtmlInputElement>() {
                     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::<HtmlOptionElement>() {
+                    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::<Element>()
                     .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::<Element>() {
-                    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::<Element>() {
-                    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();
                 }
             }
 

+ 5 - 5
packages/web/src/lib.rs

@@ -62,16 +62,16 @@ impl WebsysRenderer {
         let (sender, mut receiver) = mpsc::unbounded::<EventTrigger>();
 
         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