Evan Almloff 3 роки тому
батько
коміт
26d92b6e51

+ 4 - 0
Cargo.toml

@@ -61,6 +61,7 @@ members = [
     "packages/fermi",
     "packages/tui",
     "packages/liveview",
+    "packages/native-core",
 ]
 
 [dev-dependencies]
@@ -88,3 +89,6 @@ harness = false
 [[bench]]
 name = "jsframework"
 harness = false
+
+[profile.release]
+debug = true

+ 49 - 7
examples/tui_color_test.rs

@@ -4,11 +4,54 @@ fn main() {
     dioxus::tui::launch_cfg(
         app,
         dioxus::tui::Config {
-            rendering_mode: dioxus::tui::RenderingMode::Ansi,
+            rendering_mode: dioxus::tui::RenderingMode::Rgb,
         },
     );
 }
 
+#[derive(Props, PartialEq)]
+struct BoxProps {
+    x: i32,
+    y: i32,
+    hue: f32,
+    alpha: f32,
+}
+fn Box(cx: Scope<BoxProps>) -> Element {
+    let painted = use_state(&cx, || true);
+
+    // use_future(&cx, (), move |_| {
+    //     let count = count.to_owned();
+    //     let update = cx.schedule_update();
+    //     async move {
+    //         loop {
+    //             count.with_mut(|i| *i += 1);
+    //             tokio::time::sleep(std::time::Duration::from_millis(800)).await;
+    //             update();
+    //         }
+    //     }
+    // });
+
+    let x = cx.props.x;
+    let y = cx.props.y;
+    let hue = cx.props.hue;
+    let current_painted = painted.get();
+    let alpha = cx.props.alpha + if *current_painted { 100.0 } else { 0.0 };
+
+    cx.render(rsx! {
+        div {
+            left: "{x}px",
+            top: "{y}px",
+            width: "100%",
+            height: "100%",
+            background_color: "hsl({hue}, 100%, 50%, {alpha}%)",
+            align_items: "center",
+            onkeydown: |_| painted.with_mut(|i| *i = !*i),
+            onmouseenter: |_| painted.with_mut(|i| *i = !*i),
+            p{" "}
+        }
+    })
+}
+
 fn app(cx: Scope) -> Element {
     let steps = 50;
     cx.render(rsx! {
@@ -28,12 +71,11 @@ fn app(cx: Scope) -> Element {
                                 {
                                     let alpha = y as f32*100.0/steps as f32;
                                     cx.render(rsx! {
-                                        div {
-                                            left: "{x}px",
-                                            top: "{y}px",
-                                            width: "10%",
-                                            height: "100%",
-                                            background_color: "hsl({hue}, 100%, 50%, {alpha}%)",
+                                        Box{
+                                            x: x,
+                                            y: y,
+                                            alpha: alpha,
+                                            hue: hue,
                                         }
                                     })
                                 }

+ 14 - 3
examples/tui_readme.rs

@@ -5,15 +5,26 @@ fn main() {
 }
 
 fn app(cx: Scope) -> Element {
+    let alpha = use_state(&cx, || 100);
+
     cx.render(rsx! {
         div {
+            onwheel: move |evt| alpha.set((**alpha + evt.data.delta_y as i64).min(100).max(0)),
+
             width: "100%",
             height: "10px",
             background_color: "red",
-            justify_content: "center",
-            align_items: "center",
+            // justify_content: "center",
+            // align_items: "center",
 
-            "Hello world!"
+            p{
+                color: "rgba(0, 255, 0, {alpha}%)",
+                "Hello world!"
+            }
+            p{
+                "{alpha}"
+            }
+            // p{"Hi"}
         }
     })
 }

+ 1 - 1
examples/tui_text.rs

@@ -15,7 +15,7 @@ fn app(cx: Scope) -> Element {
             onwheel: move |evt| alpha.set((**alpha + evt.data.delta_y as i64).min(100).max(0)),
 
             p {
-                background_color: "black",
+                // background_color: "black",
                 flex_direction: "column",
                 justify_content: "center",
                 align_items: "center",

+ 1 - 1
packages/core/src/events.rs

@@ -49,7 +49,7 @@ impl BubbleState {
 ///     }
 /// )).unwrap();
 /// ```
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct UserEvent {
     /// The originator of the event trigger if available
     pub scope_id: Option<ScopeId>,

+ 1 - 1
packages/tui/src/layout_attributes.rs → packages/native-core/src/layout_attributes.rs

@@ -29,7 +29,7 @@
 - [ ] pub aspect_ratio: Number,
 */
 
-use stretch2::{prelude::*, style::PositionType, style::Style};
+use stretch2::{prelude::*, style::PositionType};
 
 /// applies the entire html namespace defined in dioxus-html
 pub fn apply_layout_attributes(

+ 23 - 14
packages/native-core/src/lib.rs

@@ -1,6 +1,7 @@
 use std::collections::{HashMap, HashSet, VecDeque};
 
 use dioxus_core::{ElementId, Mutations, VNode, VirtualDom};
+pub mod layout_attributes;
 
 /// A tree that can sync with dioxus mutations backed by a hashmap.
 /// Intended for use in lazy native renderers with a state that passes from parrent to children and or accumulates state from children to parrents.
@@ -10,7 +11,7 @@ use dioxus_core::{ElementId, Mutations, VNode, VirtualDom};
 pub struct Tree<US: BubbledUpState = (), DS: PushedDownState = ()> {
     pub root: usize,
     pub nodes: Vec<Option<TreeNode<US, DS>>>,
-    pub listeners: HashMap<usize, HashSet<&'static str>>,
+    pub nodes_listening: HashMap<&'static str, HashSet<usize>>,
 }
 
 impl<US: BubbledUpState, DS: PushedDownState> Tree<US, DS> {
@@ -29,7 +30,7 @@ impl<US: BubbledUpState, DS: PushedDownState> Tree<US, DS> {
                 )));
                 v
             },
-            listeners: HashMap::new(),
+            nodes_listening: HashMap::new(),
         }
     }
 
@@ -128,17 +129,17 @@ impl<US: BubbledUpState, DS: PushedDownState> Tree<US, DS> {
                         scope: _,
                         root,
                     } => {
-                        if let Some(v) = self.listeners.get_mut(&(root as usize)) {
-                            v.insert(event_name);
+                        if let Some(v) = self.nodes_listening.get_mut(event_name) {
+                            v.insert(root as usize);
                         } else {
                             let mut hs = HashSet::new();
-                            hs.insert(event_name);
-                            self.listeners.insert(root as usize, hs);
+                            hs.insert(root as usize);
+                            self.nodes_listening.insert(event_name, hs);
                         }
                     }
                     RemoveEventListener { root, event } => {
-                        let v = self.listeners.get_mut(&(root as usize)).unwrap();
-                        v.remove(event);
+                        let v = self.nodes_listening.get_mut(event).unwrap();
+                        v.remove(&(root as usize));
                     }
                     SetText {
                         root,
@@ -210,10 +211,8 @@ impl<US: BubbledUpState, DS: PushedDownState> Tree<US, DS> {
                 to_rerender.insert(id);
                 if let Some(p) = parent {
                     let i = to_bubble.partition_point(|(_, h)| *h < height - 1);
-                    // println!("{i}");
-                    // println!("{to_bubble:?}");
                     // make sure the parent is not already queued
-                    if to_bubble.len() == 0 || to_bubble.get(i).unwrap().0 != p.0 {
+                    if i >= to_bubble.len() || to_bubble.get(i).unwrap().0 != p.0 {
                         to_bubble.insert(i, (p.0, height - 1));
                     }
                 }
@@ -307,10 +306,20 @@ impl<US: BubbledUpState, DS: PushedDownState> Tree<US, DS> {
     fn get_mut(&mut self, id: usize) -> &mut TreeNode<US, DS> {
         self.nodes.get_mut(id).unwrap().as_mut().unwrap()
     }
+
+    pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&TreeNode<US, DS>> {
+        if let Some(nodes) = self.nodes_listening.get(event) {
+            let mut listening: Vec<_> = nodes.iter().map(|id| self.get(*id)).collect();
+            listening.sort_by(|n1, n2| (n1.height).cmp(&n2.height).reverse());
+            listening
+        } else {
+            Vec::new()
+        }
+    }
 }
 
 /// The node is stored client side and stores render data
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct TreeNode<US: BubbledUpState, DS: PushedDownState> {
     pub id: ElementId,
     pub parent: Option<ElementId>,
@@ -320,7 +329,7 @@ pub struct TreeNode<US: BubbledUpState, DS: PushedDownState> {
     pub height: u16,
 }
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub enum TreeNodeType {
     Text {
         text: String,
@@ -505,7 +514,7 @@ fn test_insert() {
             )));
             v
         },
-        listeners: HashMap::new(),
+        nodes_listening: HashMap::new(),
     };
     println!("{:?}", mutations);
     let to_update = tree.apply_mutations(vec![mutations.0]);

+ 329 - 183
packages/tui/src/hooks.rs

@@ -4,12 +4,12 @@ use crossterm::event::{
 use dioxus_core::*;
 
 use dioxus_html::{on::*, KeyCode};
-use dioxus_native_core::{Tree, TreeNodeType};
+use dioxus_native_core::{Tree, TreeNode};
 use futures::{channel::mpsc::UnboundedReceiver, StreamExt};
 use std::{
     any::Any,
     cell::RefCell,
-    collections::HashSet,
+    collections::{HashMap, HashSet},
     rc::Rc,
     sync::Arc,
     time::{Duration, Instant},
@@ -163,12 +163,35 @@ impl InnerInputState {
 
     fn update<'a>(
         &mut self,
-        dom: &'a VirtualDom,
         evts: &mut Vec<EventCore>,
         resolved_events: &mut Vec<UserEvent>,
         layout: &Stretch,
-        layouts: &mut Tree<RinkLayout, StyleModifier>,
-        node: &'a VNode<'a>,
+        tree: &mut Tree<RinkLayout, StyleModifier>,
+    ) {
+        let previous_mouse = self
+            .mouse
+            .as_ref()
+            .map(|m| (clone_mouse_data(&m.0), m.1.clone()));
+
+        self.wheel = None;
+
+        for e in evts.iter_mut() {
+            self.apply_event(e);
+        }
+
+        self.resolve_mouse_events(previous_mouse, resolved_events, layout, tree);
+
+        // for s in &self.subscribers {
+        //     s();
+        // }
+    }
+
+    fn resolve_mouse_events(
+        &self,
+        previous_mouse: Option<(MouseData, Vec<u16>)>,
+        resolved_events: &mut Vec<UserEvent>,
+        layout: &Stretch,
+        tree: &mut Tree<RinkLayout, StyleModifier>,
     ) {
         struct Data<'b> {
             new_pos: (i32, i32),
@@ -187,136 +210,31 @@ impl InnerInputState {
                 && layout.location.y as i32 + layout.size.height as i32 >= point.1
         }
 
-        fn get_mouse_events<'c, 'd>(
-            dom: &'c VirtualDom,
+        fn try_create_event(
+            name: &'static str,
+            data: Arc<dyn Any + Send + Sync>,
+            will_bubble: &mut HashSet<ElementId>,
             resolved_events: &mut Vec<UserEvent>,
-            layout: &Stretch,
-            layouts: &Tree<RinkLayout, StyleModifier>,
-            node: &'c VNode<'c>,
-            data: &'d Data<'d>,
-        ) -> HashSet<&'static str> {
-            match node {
-                VNode::Fragment(f) => {
-                    let mut union = HashSet::new();
-                    for child in f.children {
-                        union = union
-                            .union(&get_mouse_events(
-                                dom,
-                                resolved_events,
-                                layout,
-                                layouts,
-                                child,
-                                data,
-                            ))
-                            .copied()
-                            .collect();
-                    }
-                    return union;
-                }
-
-                VNode::Component(vcomp) => {
-                    let idx = vcomp.scope.get().unwrap();
-                    let new_node = dom.get_scope(idx).unwrap().root_node();
-                    return get_mouse_events(dom, resolved_events, layout, layouts, new_node, data);
-                }
-
-                VNode::Placeholder(_) => return HashSet::new(),
-
-                VNode::Element(_) | VNode::Text(_) => {}
-            }
-
-            let id = node.try_mounted_id().unwrap();
-            let node = layouts.get(id.0);
-
-            let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
-
-            let previously_contained = data
-                .old_pos
-                .filter(|pos| layout_contains_point(node_layout, *pos))
-                .is_some();
-            let currently_contains = layout_contains_point(node_layout, data.new_pos);
-
-            match &node.node_type {
-                TreeNodeType::Element { children, .. } => {
-                    let mut events = HashSet::new();
-                    if previously_contained || currently_contains {
-                        for c in children {
-                            events = events
-                                .union(&get_mouse_events(
-                                    dom,
-                                    resolved_events,
-                                    layout,
-                                    layouts,
-                                    dom.get_element(*c).unwrap(),
-                                    data,
-                                ))
-                                .copied()
-                                .collect();
-                        }
-                    }
-                    let mut try_create_event = |name| {
-                        // only trigger event if the event was not triggered already by a child
-                        if events.insert(name) {
-                            resolved_events.push(UserEvent {
-                                scope_id: None,
-                                priority: EventPriority::Medium,
-                                name,
-                                element: Some(node.id),
-                                data: Arc::new(clone_mouse_data(data.mouse_data)),
-                            })
-                        }
-                    };
-                    if currently_contains {
-                        if !previously_contained {
-                            try_create_event("mouseenter");
-                            try_create_event("mouseover");
-                        }
-                        if data.clicked {
-                            try_create_event("mousedown");
-                        }
-                        if data.released {
-                            try_create_event("mouseup");
-                            match data.mouse_data.button {
-                                0 => try_create_event("click"),
-                                2 => try_create_event("contextmenu"),
-                                _ => (),
-                            }
-                        }
-                        if let Some(w) = data.wheel_data {
-                            if data.wheel_delta != 0.0 {
-                                resolved_events.push(UserEvent {
-                                    scope_id: None,
-                                    priority: EventPriority::Medium,
-                                    name: "wheel",
-                                    element: Some(node.id),
-                                    data: Arc::new(clone_wheel_data(w)),
-                                })
-                            }
-                        }
-                    } else if previously_contained {
-                        try_create_event("mouseleave");
-                        try_create_event("mouseout");
-                    }
-                    events
+            node: &TreeNode<RinkLayout, StyleModifier>,
+            tree: &Tree<RinkLayout, StyleModifier>,
+        ) {
+            // only trigger event if the event was not triggered already by a child
+            if will_bubble.insert(node.id) {
+                let mut parent = node.parent;
+                while let Some(parent_id) = parent {
+                    will_bubble.insert(parent_id);
+                    parent = tree.get(parent_id.0).parent;
                 }
-                TreeNodeType::Text { .. } => HashSet::new(),
-                _ => todo!(),
+                resolved_events.push(UserEvent {
+                    scope_id: None,
+                    priority: EventPriority::Medium,
+                    name,
+                    element: Some(node.id),
+                    data: data,
+                })
             }
         }
 
-        let previous_mouse = self
-            .mouse
-            .as_ref()
-            .map(|m| (clone_mouse_data(&m.0), m.1.clone()));
-        // println!("{previous_mouse:?}");
-
-        self.wheel = None;
-
-        for e in evts.iter_mut() {
-            self.apply_event(e);
-        }
-
-        // resolve hover events
         if let Some(mouse) = &self.mouse {
             let new_pos = (mouse.0.screen_x, mouse.0.screen_y);
             let old_pos = previous_mouse
@@ -338,12 +256,245 @@ impl InnerInputState {
                 mouse_data,
                 wheel_data,
             };
-            get_mouse_events(dom, resolved_events, layout, layouts, node, &data);
-        }
 
-        // for s in &self.subscribers {
-        //     s();
-        // }
+            {
+                // mousemove
+                let mut will_bubble = HashSet::new();
+                for node in tree.get_listening_sorted("mousemove") {
+                    let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
+                    let previously_contained = data
+                        .old_pos
+                        .filter(|pos| layout_contains_point(node_layout, *pos))
+                        .is_some();
+                    let currently_contains = layout_contains_point(node_layout, data.new_pos);
+
+                    if currently_contains {
+                        if previously_contained {
+                            try_create_event(
+                                "mousemove",
+                                Arc::new(clone_mouse_data(data.mouse_data)),
+                                &mut will_bubble,
+                                resolved_events,
+                                node,
+                                tree,
+                            );
+                        }
+                    }
+                }
+            }
+
+            {
+                // mouseenter
+                let mut will_bubble = HashSet::new();
+                for node in tree.get_listening_sorted("mouseenter") {
+                    let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
+                    let previously_contained = data
+                        .old_pos
+                        .filter(|pos| layout_contains_point(node_layout, *pos))
+                        .is_some();
+                    let currently_contains = layout_contains_point(node_layout, data.new_pos);
+
+                    if currently_contains {
+                        if !previously_contained {
+                            try_create_event(
+                                "mouseenter",
+                                Arc::new(clone_mouse_data(data.mouse_data)),
+                                &mut will_bubble,
+                                resolved_events,
+                                node,
+                                tree,
+                            );
+                        }
+                    }
+                }
+            }
+
+            {
+                // mouseover
+                let mut will_bubble = HashSet::new();
+                for node in tree.get_listening_sorted("mouseover") {
+                    let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
+                    let previously_contained = data
+                        .old_pos
+                        .filter(|pos| layout_contains_point(node_layout, *pos))
+                        .is_some();
+                    let currently_contains = layout_contains_point(node_layout, data.new_pos);
+
+                    if currently_contains {
+                        if !previously_contained {
+                            try_create_event(
+                                "mouseover",
+                                Arc::new(clone_mouse_data(data.mouse_data)),
+                                &mut will_bubble,
+                                resolved_events,
+                                node,
+                                tree,
+                            );
+                        }
+                    }
+                }
+            }
+
+            {
+                // mousedown
+                let mut will_bubble = HashSet::new();
+                for node in tree.get_listening_sorted("mousedown") {
+                    let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
+                    let currently_contains = layout_contains_point(node_layout, data.new_pos);
+
+                    if currently_contains {
+                        if data.clicked {
+                            try_create_event(
+                                "mousedown",
+                                Arc::new(clone_mouse_data(data.mouse_data)),
+                                &mut will_bubble,
+                                resolved_events,
+                                node,
+                                tree,
+                            );
+                        }
+                    }
+                }
+            }
+
+            {
+                // mouseup
+                let mut will_bubble = HashSet::new();
+                for node in tree.get_listening_sorted("mouseup") {
+                    let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
+                    let currently_contains = layout_contains_point(node_layout, data.new_pos);
+
+                    if currently_contains {
+                        if data.released {
+                            try_create_event(
+                                "mouseup",
+                                Arc::new(clone_mouse_data(data.mouse_data)),
+                                &mut will_bubble,
+                                resolved_events,
+                                node,
+                                tree,
+                            );
+                        }
+                    }
+                }
+            }
+
+            {
+                // click
+                let mut will_bubble = HashSet::new();
+                for node in tree.get_listening_sorted("click") {
+                    let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
+                    let currently_contains = layout_contains_point(node_layout, data.new_pos);
+
+                    if currently_contains {
+                        if data.released && data.mouse_data.button == 0 {
+                            try_create_event(
+                                "click",
+                                Arc::new(clone_mouse_data(data.mouse_data)),
+                                &mut will_bubble,
+                                resolved_events,
+                                node,
+                                tree,
+                            );
+                        }
+                    }
+                }
+            }
+
+            {
+                // contextmenu
+                let mut will_bubble = HashSet::new();
+                for node in tree.get_listening_sorted("contextmenu") {
+                    let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
+                    let currently_contains = layout_contains_point(node_layout, data.new_pos);
+
+                    if currently_contains {
+                        if data.released && data.mouse_data.button == 2 {
+                            try_create_event(
+                                "contextmenu",
+                                Arc::new(clone_mouse_data(data.mouse_data)),
+                                &mut will_bubble,
+                                resolved_events,
+                                node,
+                                tree,
+                            );
+                        }
+                    }
+                }
+            }
+
+            {
+                // wheel
+                let mut will_bubble = HashSet::new();
+                for node in tree.get_listening_sorted("wheel") {
+                    let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
+                    let currently_contains = layout_contains_point(node_layout, data.new_pos);
+
+                    if currently_contains {
+                        if let Some(w) = data.wheel_data {
+                            if data.wheel_delta != 0.0 {
+                                try_create_event(
+                                    "wheel",
+                                    Arc::new(clone_wheel_data(w)),
+                                    &mut will_bubble,
+                                    resolved_events,
+                                    node,
+                                    tree,
+                                );
+                            }
+                        }
+                    }
+                }
+            }
+
+            {
+                // mouseleave
+                let mut will_bubble = HashSet::new();
+                for node in tree.get_listening_sorted("mouseleave") {
+                    let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
+                    let previously_contained = data
+                        .old_pos
+                        .filter(|pos| layout_contains_point(node_layout, *pos))
+                        .is_some();
+                    let currently_contains = layout_contains_point(node_layout, data.new_pos);
+
+                    if !currently_contains && previously_contained {
+                        try_create_event(
+                            "mouseleave",
+                            Arc::new(clone_mouse_data(data.mouse_data)),
+                            &mut will_bubble,
+                            resolved_events,
+                            node,
+                            tree,
+                        );
+                    }
+                }
+            }
+
+            {
+                // mouseout
+                let mut will_bubble = HashSet::new();
+                for node in tree.get_listening_sorted("mouseout") {
+                    let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
+                    let previously_contained = data
+                        .old_pos
+                        .filter(|pos| layout_contains_point(node_layout, *pos))
+                        .is_some();
+                    let currently_contains = layout_contains_point(node_layout, data.new_pos);
+
+                    if !currently_contains && previously_contained {
+                        try_create_event(
+                            "mouseout",
+                            Arc::new(clone_mouse_data(data.mouse_data)),
+                            &mut will_bubble,
+                            resolved_events,
+                            node,
+                            tree,
+                        );
+                    }
+                }
+            }
+        }
     }
 
     // fn subscribe(&mut self, f: Rc<dyn Fn() + 'static>) {
@@ -364,7 +515,7 @@ impl RinkInputHandler {
         cx: &ScopeState,
     ) -> (Self, Rc<RefCell<InnerInputState>>) {
         let queued_events = Rc::new(RefCell::new(Vec::new()));
-        let queued_events2 = Rc::<RefCell<std::vec::Vec<_>>>::downgrade(&queued_events);
+        let queued_events2 = Rc::downgrade(&queued_events);
 
         cx.push_future(async move {
             while let Some(evt) = receiver.next().await {
@@ -391,68 +542,63 @@ impl RinkInputHandler {
 
     pub fn get_events<'a>(
         &self,
-        dom: &'a VirtualDom,
         layout: &Stretch,
-        layouts: &mut Tree<RinkLayout, StyleModifier>,
-        node: &'a VNode<'a>,
+        tree: &mut Tree<RinkLayout, StyleModifier>,
     ) -> Vec<UserEvent> {
-        // todo: currently resolves events in all nodes, but once the focus system is added it should filter by focus
-        fn inner(
-            queue: &[(&'static str, Arc<dyn Any + Send + Sync>)],
-            resolved: &mut Vec<UserEvent>,
-            node: &VNode,
-        ) {
-            match node {
-                VNode::Fragment(frag) => {
-                    for c in frag.children {
-                        inner(queue, resolved, c);
-                    }
-                }
-                VNode::Element(el) => {
-                    for l in el.listeners {
-                        for (name, data) in queue.iter() {
-                            if *name == l.event {
-                                if let Some(id) = el.id.get() {
-                                    resolved.push(UserEvent {
-                                        scope_id: None,
-                                        priority: EventPriority::Medium,
-                                        name: *name,
-                                        element: Some(id),
-                                        data: data.clone(),
-                                    });
-                                }
-                            }
-                        }
-                    }
-                    for c in el.children {
-                        inner(queue, resolved, c);
-                    }
-                }
-                _ => (),
-            }
-        }
-
         let mut resolved_events = Vec::new();
 
         (*self.state).borrow_mut().update(
-            dom,
             &mut (*self.queued_events).borrow_mut(),
             &mut resolved_events,
             layout,
-            layouts,
-            node,
+            tree,
         );
 
-        let events: Vec<_> = self
+        let events = self
             .queued_events
             .replace(Vec::new())
             .into_iter()
             // these events were added in the update stage
-            .filter(|e| !["mousedown", "mouseup", "mousemove", "drag", "wheel"].contains(&e.0))
-            .map(|e| (e.0, e.1.into_any()))
-            .collect();
+            .filter(|e| {
+                ![
+                    "mouseenter",
+                    "mouseover",
+                    "mouseleave",
+                    "mouseout",
+                    "mousedown",
+                    "mouseup",
+                    "mousemove",
+                    "drag",
+                    "wheel",
+                    "click",
+                    "contextmenu",
+                ]
+                .contains(&e.0)
+            })
+            .map(|evt| (evt.0, evt.1.into_any()));
 
-        inner(&events, &mut resolved_events, node);
+        // todo: currently resolves events in all nodes, but once the focus system is added it should filter by focus
+        let mut hm: HashMap<&'static str, Vec<Arc<dyn Any + Send + Sync>>> = HashMap::new();
+        for (event, data) in events {
+            if let Some(v) = hm.get_mut(event) {
+                v.push(data);
+            } else {
+                hm.insert(event, vec![data]);
+            }
+        }
+        for (event, datas) in hm {
+            for node in tree.get_listening_sorted(event) {
+                for data in &datas {
+                    resolved_events.push(UserEvent {
+                        scope_id: None,
+                        priority: EventPriority::Medium,
+                        name: event,
+                        element: Some(node.id),
+                        data: data.clone(),
+                    });
+                }
+            }
+        }
 
         resolved_events
     }

+ 1 - 4
packages/tui/src/layout.rs

@@ -2,7 +2,7 @@ use dioxus_core::*;
 use dioxus_native_core::BubbledUpState;
 use stretch2::prelude::*;
 
-use crate::layout_attributes::apply_layout_attributes;
+use dioxus_native_core::layout_attributes::apply_layout_attributes;
 
 /*
 The layout system uses the lineheight as one point.
@@ -42,7 +42,6 @@ impl BubbledUpState for RinkLayout {
 
                 if let Some(n) = self.node {
                     if self.style != style {
-                        panic!("new style: {style:?}");
                         stretch.set_style(n, style).unwrap();
                     }
                 } else {
@@ -74,11 +73,9 @@ impl BubbledUpState for RinkLayout {
 
                 if let Some(n) = self.node {
                     if &stretch.children(n).unwrap() != &child_layout {
-                        panic!("new children: {child_layout:?}");
                         stretch.set_children(n, &child_layout).unwrap();
                     }
                     if self.style != style {
-                        panic!("new style: {style:?}");
                         stretch.set_style(n, style).unwrap();
                     }
                 } else {

+ 59 - 57
packages/tui/src/lib.rs

@@ -1,3 +1,6 @@
+// notes:
+// mouse events binary search was broken for absolutely positioned elements
+
 use anyhow::Result;
 use crossterm::{
     event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyModifiers},
@@ -6,23 +9,19 @@ use crossterm::{
 };
 use dioxus_core::exports::futures_channel::mpsc::unbounded;
 use dioxus_core::*;
+use dioxus_html::on::{KeyboardData, MouseData, PointerData, TouchData, WheelData};
 use dioxus_native_core::Tree;
 use futures::{channel::mpsc::UnboundedSender, pin_mut, StreamExt};
-use std::{
-    io,
-    time::{Duration, Instant},
-};
-use stretch2::{
-    prelude::{Layout, Size},
-    Stretch,
-};
+use std::collections::HashSet;
+use std::{io, time::Duration};
+use stretch2::{prelude::Size, Stretch};
 use style_attributes::StyleModifier;
+use tokio::time::Instant;
 use tui::{backend::CrosstermBackend, Terminal};
 
 mod config;
 mod hooks;
 mod layout;
-mod layout_attributes;
 mod render;
 mod style;
 mod style_attributes;
@@ -31,7 +30,6 @@ mod widget;
 pub use config::*;
 pub use hooks::*;
 pub use layout::*;
-pub use layout_attributes::*;
 pub use render::*;
 
 pub fn launch(app: Component<()>) {
@@ -104,6 +102,12 @@ pub fn render_vdom(
             let mut terminal = Terminal::new(backend).unwrap();
 
             terminal.clear().unwrap();
+            let mut to_rerender: HashSet<usize> = tree
+                .nodes
+                .iter()
+                .filter_map(|n| n.as_ref())
+                .map(|n| n.id.0)
+                .collect();
 
             loop {
                 /*
@@ -115,54 +119,51 @@ pub fn render_vdom(
 
                 use simd to compare lines for diffing?
 
-
-                todo: reuse the layout and node objects.
-                our work_with_deadline method can tell us which nodes are dirty.
+                todo: lazy re-rendering
                 */
 
-                /*
-                Compute the layout given the terminal size
-                */
-                let mut events = Vec::new();
-
-                terminal.draw(|frame| {
-                    // size is guaranteed to not change when rendering
-                    let dims = frame.size();
-                    println!("{dims:?}");
-                    let width = dims.width;
-                    let height = dims.height;
-                    let root_id = tree.root;
-                    let root_node = tree.get(root_id).up_state.node.unwrap();
-                    stretch
-                        .compute_layout(
-                            root_node,
-                            Size {
-                                width: stretch2::prelude::Number::Defined((width - 1) as f32),
-                                height: stretch2::prelude::Number::Defined((height - 1) as f32),
-                            },
-                        )
-                        .unwrap();
-
-                    // resolve events before rendering
-                    events = handler.get_events(
-                        vdom,
-                        &stretch,
-                        &mut tree,
-                        vdom.base_scope().root_node(),
-                    );
-                    for n in &tree.nodes {
-                        if let Some(node) = n {
-                            let Layout { location, size, .. } =
-                                stretch.layout(node.up_state.node.unwrap()).unwrap();
-                            println!("{node:#?}");
-                            println!("\t{location:?}: {size:?}");
-                        }
-                    }
-                    let root = tree.get(tree.root);
-                    // render::render_vnode(frame, &stretch, &tree, &root, cfg);
-                })?;
+                if !to_rerender.is_empty() {
+                    terminal.draw(|frame| {
+                        // size is guaranteed to not change when rendering
+                        let dims = frame.size();
+                        // println!("{dims:?}");
+                        let width = dims.width;
+                        let height = dims.height;
+                        let root_id = tree.root;
+                        let root_node = tree.get(root_id).up_state.node.unwrap();
+                        stretch
+                            .compute_layout(
+                                root_node,
+                                Size {
+                                    width: stretch2::prelude::Number::Defined((width - 1) as f32),
+                                    height: stretch2::prelude::Number::Defined((height - 1) as f32),
+                                },
+                            )
+                            .unwrap();
+                        let root = tree.get(tree.root);
+                        render::render_vnode(frame, &stretch, &tree, &root, cfg);
+                    })?;
+                }
 
-                for e in events {
+                // resolve events before rendering
+                // todo: events do not trigger update?
+                for e in handler.get_events(&stretch, &mut tree) {
+                    let tname = if e.data.is::<PointerData>() {
+                        "PointerData"
+                    } else if e.data.is::<WheelData>() {
+                        "WheelData"
+                    } else if e.data.is::<MouseData>() {
+                        "MouseData"
+                    } else if e.data.is::<KeyboardData>() {
+                        "KeyboardData"
+                    } else if e.data.is::<TouchData>() {
+                        "TouchData"
+                    } else if e.data.is::<(u16, u16)>() {
+                        "(u16, u16)"
+                    } else {
+                        panic!()
+                    };
+                    // println!("{tname}: {e:?}");
                     vdom.handle_message(SchedulerMsg::Event(e));
                 }
 
@@ -199,8 +200,10 @@ pub fn render_vdom(
                 }
 
                 let mutations = vdom.work_with_deadline(|| false);
+                // updates the tree's nodes
                 let to_update = tree.apply_mutations(mutations);
-                let _to_rerender = tree
+                // update the style and layout
+                to_rerender = tree
                     .update_state(&vdom, to_update, &mut stretch, &mut ())
                     .unwrap();
             }
@@ -220,7 +223,6 @@ pub fn render_vdom(
 enum InputEvent {
     UserInput(TermEvent),
     Tick,
-
     #[allow(dead_code)]
     Close,
 }

+ 9 - 9
packages/tui/src/render.rs

@@ -1,4 +1,4 @@
-use dioxus_native_core::{Tree, TreeNode};
+use dioxus_native_core::{layout_attributes::UnitSystem, Tree, TreeNode};
 use std::io::Stdout;
 use stretch2::{
     geometry::Point,
@@ -11,7 +11,7 @@ use crate::{
     style::{RinkColor, RinkStyle},
     style_attributes::{BorderEdge, BorderStyle},
     widget::{RinkBuffer, RinkCell, RinkWidget, WidgetWithContext},
-    Config, RinkLayout, StyleModifier, UnitSystem,
+    Config, RinkLayout, StyleModifier,
 };
 
 const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5];
@@ -48,7 +48,7 @@ pub fn render_vnode<'a>(
                         // println!("{:?}", self.style);
                         new_cell.set_style(self.style);
                         new_cell.symbol = c.to_string();
-                        buf.set(area.left() + i as u16, area.top(), &new_cell);
+                        buf.set(area.left() + i as u16, area.top(), new_cell);
                     }
                 }
             }
@@ -150,7 +150,7 @@ impl RinkWidget for &TreeNode<RinkLayout, StyleModifier> {
             buf.set(
                 (current[0] + pos[0] as i32) as u16,
                 (current[1] + pos[1] as i32) as u16,
-                &new_cell,
+                new_cell,
             );
         }
 
@@ -267,7 +267,7 @@ impl RinkWidget for &TreeNode<RinkLayout, StyleModifier> {
                 if let Some(c) = self.down_state.style.bg {
                     new_cell.bg = c;
                 }
-                buf.set(x, y, &new_cell);
+                buf.set(x, y, new_cell);
             }
         }
 
@@ -295,7 +295,7 @@ impl RinkWidget for &TreeNode<RinkLayout, StyleModifier> {
             }
             for x in (area.left() + last_radius[0] + 1)..(area.right() - radius[0]) {
                 new_cell.symbol = symbols.horizontal.to_string();
-                buf.set(x, area.top(), &new_cell);
+                buf.set(x, area.top(), new_cell.clone());
             }
             draw_arc(
                 [area.right() - radius[0] - 1, area.top() + radius[1]],
@@ -330,7 +330,7 @@ impl RinkWidget for &TreeNode<RinkLayout, StyleModifier> {
             }
             for y in (area.top() + last_radius[1] + 1)..(area.bottom() - radius[1]) {
                 new_cell.symbol = symbols.vertical.to_string();
-                buf.set(area.right() - 1, y, &new_cell);
+                buf.set(area.right() - 1, y, new_cell.clone());
             }
             draw_arc(
                 [area.right() - radius[0] - 1, area.bottom() - radius[1] - 1],
@@ -365,7 +365,7 @@ impl RinkWidget for &TreeNode<RinkLayout, StyleModifier> {
             }
             for x in (area.left() + radius[0])..(area.right() - last_radius[0] - 1) {
                 new_cell.symbol = symbols.horizontal.to_string();
-                buf.set(x, area.bottom() - 1, &new_cell);
+                buf.set(x, area.bottom() - 1, new_cell.clone());
             }
             draw_arc(
                 [area.left() + radius[0], area.bottom() - radius[1] - 1],
@@ -400,7 +400,7 @@ impl RinkWidget for &TreeNode<RinkLayout, StyleModifier> {
             }
             for y in (area.top() + radius[1])..(area.bottom() - last_radius[1] - 1) {
                 new_cell.symbol = symbols.vertical.to_string();
-                buf.set(area.left(), y, &new_cell);
+                buf.set(area.left(), y, new_cell.clone());
             }
             draw_arc(
                 [area.left() + radius[0], area.top() + radius[1]],

+ 5 - 6
packages/tui/src/style_attributes.rs

@@ -30,14 +30,13 @@
 */
 
 use dioxus_core::{Attribute, VNode};
-use dioxus_native_core::PushedDownState;
-
-use crate::{
-    parse_value,
-    style::{RinkColor, RinkStyle},
-    UnitSystem,
+use dioxus_native_core::{
+    layout_attributes::{parse_value, UnitSystem},
+    PushedDownState,
 };
 
+use crate::style::{RinkColor, RinkStyle};
+
 #[derive(Default, Clone, PartialEq, Debug)]
 pub struct StyleModifier {
     pub style: RinkStyle,

+ 2 - 2
packages/tui/src/widget.rs

@@ -20,7 +20,7 @@ impl<'a> RinkBuffer<'a> {
         Self { buf, cfg }
     }
 
-    pub fn set(&mut self, x: u16, y: u16, new: &RinkCell) {
+    pub fn set(&mut self, x: u16, y: u16, new: RinkCell) {
         let area = self.buf.area();
         if x < area.x || x > area.width || y < area.y || y > area.height {
             panic!("({x}, {y}) is not in {area:?}");
@@ -34,7 +34,7 @@ impl<'a> RinkBuffer<'a> {
             }
         } else {
             cell.modifier = new.modifier;
-            cell.symbol = new.symbol.clone();
+            cell.symbol = new.symbol;
             cell.fg = convert(self.cfg.rendering_mode, new.fg.blend(cell.bg));
         }
     }

+ 31 - 30
packages/tui/tests/relayout.rs

@@ -1,3 +1,4 @@
+use stretch::style::Dimension;
 use stretch2 as stretch;
 
 #[test]
@@ -6,14 +7,7 @@ fn relayout() {
     let node1 = stretch
         .new_node(
             stretch::style::Style {
-                position: stretch::geometry::Point {
-                    x: stretch::style::Dimension::Points(10f32),
-                    y: stretch::style::Dimension::Points(10f32),
-                },
-                size: stretch::geometry::Size {
-                    width: stretch::style::Dimension::Points(10f32),
-                    height: stretch::style::Dimension::Points(10f32),
-                },
+                size: stretch::geometry::Size { width: Dimension::Points(8f32), height: Dimension::Points(80f32) },
                 ..Default::default()
             },
             &[],
@@ -22,10 +16,9 @@ fn relayout() {
     let node0 = stretch
         .new_node(
             stretch::style::Style {
-                size: stretch::geometry::Size {
-                    width: stretch::style::Dimension::Percent(1f32),
-                    height: stretch::style::Dimension::Percent(1f32),
-                },
+                align_self: stretch::prelude::AlignSelf::Center,
+                size: stretch::geometry::Size { width: Dimension::Auto, height: Dimension::Auto },
+                // size: stretch::geometry::Size { width: Dimension::Percent(1.0), height: Dimension::Percent(1.0) },
                 ..Default::default()
             },
             &[node1],
@@ -34,30 +27,38 @@ fn relayout() {
     let node = stretch
         .new_node(
             stretch::style::Style {
-                size: stretch::geometry::Size {
-                    width: stretch::style::Dimension::Points(100f32),
-                    height: stretch::style::Dimension::Points(100f32),
-                },
+                size: stretch::geometry::Size { width: Dimension::Percent(1f32), height: Dimension::Percent(1f32) },
                 ..Default::default()
             },
             &[node0],
         )
         .unwrap();
-    for _ in 0..10 {
+    println!("0:");
+    stretch
+        .compute_layout(
+            node,
+            stretch::geometry::Size {
+                width: stretch::prelude::Number::Defined(100f32),
+                height: stretch::prelude::Number::Defined(100f32),
+            },
+        )
+        .unwrap();
+    let initial = stretch.layout(node).unwrap().location;
+    let initial0 = stretch.layout(node0).unwrap().location;
+    let initial1 = stretch.layout(node1).unwrap().location;
+    for i in 1..10 {
+        println!("\n\n{i}:");
         stretch
-            .compute_layout(node, stretch::geometry::Size::undefined())
+            .compute_layout(
+                node,
+                stretch::geometry::Size {
+                    width: stretch::prelude::Number::Defined(100f32),
+                    height: stretch::prelude::Number::Defined(100f32),
+                },
+            )
             .unwrap();
-        assert_eq!(stretch.layout(node).unwrap().size.width, 100f32);
-        assert_eq!(stretch.layout(node).unwrap().size.height, 100f32);
-        assert_eq!(stretch.layout(node).unwrap().location.x, 0f32);
-        assert_eq!(stretch.layout(node).unwrap().location.y, 0f32);
-        assert_eq!(stretch.layout(node1).unwrap().size.width, 10f32);
-        assert_eq!(stretch.layout(node1).unwrap().size.height, 10f32);
-        assert_eq!(stretch.layout(node1).unwrap().location.x, 0f32);
-        assert_eq!(stretch.layout(node1).unwrap().location.y, 0f32);
-        assert_eq!(stretch.layout(node0).unwrap().size.width, 100f32);
-        assert_eq!(stretch.layout(node0).unwrap().size.height, 100f32);
-        assert_eq!(stretch.layout(node0).unwrap().location.x, 0f32);
-        assert_eq!(stretch.layout(node0).unwrap().location.y, 0f32);
+        assert_eq!(stretch.layout(node).unwrap().location, initial);
+        assert_eq!(stretch.layout(node0).unwrap().location, initial0);
+        assert_eq!(stretch.layout(node1).unwrap().location, initial1);
     }
 }