Pārlūkot izejas kodu

Merge pull request #11 from Demonthos/master

html native event handlers
Jonathan Kelley 3 gadi atpakaļ
vecāks
revīzija
e197d328ab
5 mainītis faili ar 779 papildinājumiem un 108 dzēšanām
  1. 1 0
      Cargo.toml
  2. 102 0
      examples/hover.rs
  3. 35 25
      examples/keys.rs
  4. 622 79
      src/hooks.rs
  5. 19 4
      src/lib.rs

+ 1 - 0
Cargo.toml

@@ -11,6 +11,7 @@ crossterm = "0.22.1"
 anyhow = "1.0.42"
 anyhow = "1.0.42"
 thiserror = "1.0.24"
 thiserror = "1.0.24"
 dioxus = "0.1.8"
 dioxus = "0.1.8"
+dioxus-html = "0.1.6"
 hecs = "0.7.3"
 hecs = "0.7.3"
 ctrlc = "3.2.1"
 ctrlc = "3.2.1"
 bumpalo = { version = "3.8.0", features = ["boxed"] }
 bumpalo = { version = "3.8.0", features = ["boxed"] }

+ 102 - 0
examples/hover.rs

@@ -0,0 +1,102 @@
+use std::{convert::TryInto, sync::Arc};
+
+use dioxus::{events::MouseData, prelude::*};
+
+fn main() {
+    rink::launch(app);
+}
+
+fn app(cx: Scope) -> Element {
+    fn to_str(c: &[i32; 3]) -> String {
+        "#".to_string() + &c.iter().map(|c| format!("{c:02X?}")).collect::<String>()
+    }
+
+    fn get_brightness(m: Arc<MouseData>) -> i32 {
+        let mb = m.buttons;
+        let b: i32 = m.buttons.count_ones().try_into().unwrap();
+        127 * b
+    }
+
+    let (q1_color, set_q1_color) = use_state(&cx, || [200; 3]);
+    let (q2_color, set_q2_color) = use_state(&cx, || [200; 3]);
+    let (q3_color, set_q3_color) = use_state(&cx, || [200; 3]);
+    let (q4_color, set_q4_color) = use_state(&cx, || [200; 3]);
+
+    let q1_color_str = to_str(q1_color);
+    let q2_color_str = to_str(q2_color);
+    let q3_color_str = to_str(q3_color);
+    let q4_color_str = to_str(q4_color);
+
+    cx.render(rsx! {
+        div {
+            width: "100%",
+            height: "100%",
+            flex_direction: "column",
+
+            div {
+                width: "100%",
+                height: "50%",
+                flex_direction: "row",
+                div {
+                    border_width: "1px",
+                    width: "50%",
+                    height: "100%",
+                    justify_content: "center",
+                    align_items: "center",
+                    background_color: "{q1_color_str}",
+                    onmouseenter: move |m| set_q1_color([get_brightness(m.data), 0, 0]),
+                    onmousedown: move |m| set_q1_color([get_brightness(m.data), 0, 0]),
+                    onmouseup: move |m| set_q1_color([get_brightness(m.data), 0, 0]),
+                    onwheel: move |w| set_q1_color([q1_color[0] + (10.0*w.delta_y) as i32, 0, 0]),
+                    onmouseleave: move |_| set_q1_color([200; 3]),
+                    "click me"
+                }
+                div {
+                    width: "50%",
+                    height: "100%",
+                    justify_content: "center",
+                    align_items: "center",
+                    background_color: "{q2_color_str}",
+                    onmouseenter: move |m| set_q2_color([get_brightness(m.data); 3]),
+                    onmousedown: move |m| set_q2_color([get_brightness(m.data); 3]),
+                    onmouseup: move |m| set_q2_color([get_brightness(m.data); 3]),
+                    onwheel: move |w| set_q2_color([q2_color[0] + (10.0*w.delta_y) as i32;3]),
+                    onmouseleave: move |_| set_q2_color([200; 3]),
+                    "click me"
+                }
+            }
+
+            div {
+                width: "100%",
+                height: "50%",
+                flex_direction: "row",
+                div {
+                    width: "50%",
+                    height: "100%",
+                    justify_content: "center",
+                    align_items: "center",
+                    background_color: "{q3_color_str}",
+                    onmouseenter: move |m| set_q3_color([0, get_brightness(m.data), 0]),
+                    onmousedown: move |m| set_q3_color([0, get_brightness(m.data), 0]),
+                    onmouseup: move |m| set_q3_color([0, get_brightness(m.data), 0]),
+                    onwheel: move |w| set_q3_color([0, q3_color[1] + (10.0*w.delta_y) as i32, 0]),
+                    onmouseleave: move |_| set_q3_color([200; 3]),
+                    "click me"
+                }
+                div {
+                    width: "50%",
+                    height: "100%",
+                    justify_content: "center",
+                    align_items: "center",
+                    background_color: "{q4_color_str}",
+                    onmouseenter: move |m| set_q4_color([0, 0, get_brightness(m.data)]),
+                    onmousedown: move |m| set_q4_color([0, 0, get_brightness(m.data)]),
+                    onmouseup: move |m| set_q4_color([0, 0, get_brightness(m.data)]),
+                    onwheel: move |w| set_q4_color([0, 0, q4_color[2] + (10.0*w.delta_y) as i32]),
+                    onmouseleave: move |_| set_q4_color([200; 3]),
+                    "click me"
+                }
+            }
+        }
+    })
+}

+ 35 - 25
examples/keys.rs

@@ -1,15 +1,18 @@
-use crossterm::event::{KeyCode, KeyEvent, MouseEvent};
+use dioxus::events::WheelEvent;
 use dioxus::prelude::*;
 use dioxus::prelude::*;
+use dioxus_html::on::{KeyboardEvent, MouseEvent};
+use dioxus_html::KeyCode;
 
 
 fn main() {
 fn main() {
     rink::launch(app);
     rink::launch(app);
 }
 }
 
 
 fn app(cx: Scope) -> Element {
 fn app(cx: Scope) -> Element {
-    let (key, set_key) = use_state(&cx, || KeyCode::Null);
+    let (key, set_key) = use_state(&cx, || "".to_string());
     let (mouse, set_mouse) = use_state(&cx, || (0, 0));
     let (mouse, set_mouse) = use_state(&cx, || (0, 0));
-    let (size, set_size) = use_state(&cx, || (0, 0));
     let (count, set_count) = use_state(&cx, || 0);
     let (count, set_count) = use_state(&cx, || 0);
+    let (buttons, set_buttons) = use_state(&cx, || 0);
+    let (mouse_clicked, set_mouse_clicked) = use_state(&cx, || false);
 
 
     cx.render(rsx! {
     cx.render(rsx! {
         div {
         div {
@@ -19,30 +22,37 @@ fn app(cx: Scope) -> Element {
             justify_content: "center",
             justify_content: "center",
             align_items: "center",
             align_items: "center",
             flex_direction: "column",
             flex_direction: "column",
-
-            rink::InputHandler {
-                onkeydown: move |evt: KeyEvent| {
-                    use crossterm::event::KeyCode::*;
-                    match evt.code {
-                        Left => set_count(count + 1),
-                        Right => set_count(count - 1),
-                        Up => set_count(count + 10),
-                        Down => set_count(count - 10),
-                        _ => {},
-                    }
-                    set_key(evt.code);
-                },
-                onmousedown: move |evt: MouseEvent| {
-                    set_mouse((evt.row, evt.column));
-                },
-                onresize: move |dims| {
-                    set_size(dims);
-                },
+            onkeydown: move |evt: KeyboardEvent| {
+                match evt.data.key_code {
+                    KeyCode::LeftArrow => set_count(count + 1),
+                    KeyCode::RightArrow => set_count(count - 1),
+                    KeyCode::UpArrow => set_count(count + 10),
+                    KeyCode::DownArrow => set_count(count - 10),
+                    _ => {},
+                }
+                set_key(format!("{:?} repeating: {:?}", evt.key, evt.repeat));
+            },
+            onwheel: move |evt: WheelEvent| {
+                set_count(count + evt.data.delta_y as i64);
+            },
+            ondrag: move |evt: MouseEvent| {
+                set_mouse((evt.data.screen_x, evt.data.screen_y));
             },
             },
+            onmousedown: move |evt: MouseEvent| {
+                set_mouse((evt.data.screen_x, evt.data.screen_y));
+                set_buttons(evt.data.buttons);
+                set_mouse_clicked(true);
+            },
+            onmouseup: move |evt: MouseEvent| {
+                set_buttons(evt.data.buttons);
+                set_mouse_clicked(false);
+            },
+
             "count: {count:?}",
             "count: {count:?}",
-            "key: {key:?}",
-            "mouse: {mouse:?}",
-            "resize: {size:?}",
+            "key: {key}",
+            "mouse buttons: {buttons:b}",
+            "mouse pos: {mouse:?}",
+            "mouse button pressed: {mouse_clicked}"
         }
         }
     })
     })
 }
 }

+ 622 - 79
src/hooks.rs

@@ -1,109 +1,652 @@
-use crossterm::event::{Event as TermEvent, KeyEvent, MouseEvent};
+use crossterm::event::{
+    Event as TermEvent, KeyCode as TermKeyCode, KeyModifiers, MouseButton, MouseEventKind,
+};
 use dioxus::core::*;
 use dioxus::core::*;
-use dioxus::prelude::Props;
+
+use dioxus_html::{on::*, KeyCode};
 use futures::{channel::mpsc::UnboundedReceiver, StreamExt};
 use futures::{channel::mpsc::UnboundedReceiver, StreamExt};
 use std::{
 use std::{
-    cell::{Cell, RefCell},
-    collections::HashMap,
+    any::Any,
+    cell::RefCell,
+    collections::{HashMap, HashSet},
     rc::Rc,
     rc::Rc,
+    sync::Arc,
+    time::{Duration, Instant},
 };
 };
+use stretch2::{prelude::Layout, Stretch};
+
+use crate::TuiNode;
+
+// a wrapper around the input state for easier access
+// todo: fix loop
+// pub struct InputState(Rc<Rc<RefCell<InnerInputState>>>);
+// impl InputState {
+//     pub fn get(cx: &ScopeState) -> InputState {
+//         let inner = cx
+//             .consume_context::<Rc<RefCell<InnerInputState>>>()
+//             .expect("Rink InputState can only be used in Rink apps!");
+//         (**inner).borrow_mut().subscribe(cx.schedule_update());
+//         InputState(inner)
+//     }
+
+//     pub fn mouse(&self) -> Option<MouseData> {
+//         let data = (**self.0).borrow();
+//         data.mouse.as_ref().map(|m| clone_mouse_data(m))
+//     }
+
+//     pub fn wheel(&self) -> Option<WheelData> {
+//         let data = (**self.0).borrow();
+//         data.wheel.as_ref().map(|w| clone_wheel_data(w))
+//     }
 
 
-pub struct RinkContext {
-    last_event: Rc<Cell<Option<TermEvent>>>,
-    subscribers: Rc<RefCell<HashMap<ScopeId, bool>>>,
+//     pub fn screen(&self) -> Option<(u16, u16)> {
+//         let data = (**self.0).borrow();
+//         data.screen.as_ref().map(|m| m.clone())
+//     }
+
+//     pub fn last_key_pressed(&self) -> Option<KeyboardData> {
+//         let data = (**self.0).borrow();
+//         data.last_key_pressed
+//             .as_ref()
+//             .map(|k| clone_keyboard_data(&k.0))
+//     }
+// }
+
+type EventCore = (&'static str, EventData);
+
+#[derive(Debug)]
+enum EventData {
+    Mouse(MouseData),
+    Wheel(WheelData),
+    Screen((u16, u16)),
+    Keyboard(KeyboardData),
+}
+impl EventData {
+    fn into_any(self) -> Arc<dyn Any + Send + Sync> {
+        match self {
+            Self::Mouse(m) => Arc::new(m),
+            Self::Wheel(w) => Arc::new(w),
+            Self::Screen(s) => Arc::new(s),
+            Self::Keyboard(k) => Arc::new(k),
+        }
+    }
 }
 }
 
 
-impl RinkContext {
-    pub fn new(mut receiver: UnboundedReceiver<TermEvent>, cx: &ScopeState) -> Self {
-        let updater = cx.schedule_update_any();
-        let last_event = Rc::new(Cell::new(None));
-        let last_event2 = last_event.clone();
-        let subscribers = Rc::new(RefCell::new(HashMap::new()));
-        let subscribers2 = subscribers.clone();
+const MAX_REPEAT_TIME: Duration = Duration::from_millis(100);
 
 
-        cx.push_future(async move {
-            while let Some(evt) = receiver.next().await {
-                last_event2.replace(Some(evt));
-                for (subscriber, received) in subscribers2.borrow_mut().iter_mut() {
-                    updater(*subscriber);
-                    *received = false;
-                }
-            }
-        });
+pub struct InnerInputState {
+    mouse: Option<(MouseData, Vec<u16>)>,
+    wheel: Option<WheelData>,
+    last_key_pressed: Option<(KeyboardData, Instant)>,
+    screen: Option<(u16, u16)>,
+    // subscribers: Vec<Rc<dyn Fn() + 'static>>,
+}
 
 
+impl InnerInputState {
+    fn new() -> Self {
         Self {
         Self {
-            last_event,
-            subscribers,
+            mouse: None,
+            wheel: None,
+            last_key_pressed: None,
+            screen: None,
+            // subscribers: Vec::new(),
         }
         }
     }
     }
 
 
-    pub fn subscribe_to_events(&self, scope: ScopeId) {
-        self.subscribers.borrow_mut().insert(scope, false);
-    }
+    // stores current input state and transforms events based on that state
+    fn apply_event(&mut self, evt: &mut EventCore) {
+        match evt.1 {
+            // limitations: only two buttons may be held at once
+            EventData::Mouse(ref mut m) => match &mut self.mouse {
+                Some(state) => {
+                    let mut buttons = state.0.buttons;
+                    state.0 = clone_mouse_data(m);
+                    match evt.0 {
+                        // this code only runs when there are no buttons down
+                        "mouseup" => {
+                            buttons = 0;
+                            state.1 = Vec::new();
+                        }
+                        "mousedown" => {
+                            if state.1.contains(&m.buttons) {
+                                // if we already pressed a button and there is another button released the button crossterm sends is the button remaining
+                                if state.1.len() > 1 {
+                                    evt.0 = "mouseup";
+                                    state.1 = vec![m.buttons];
+                                }
+                                // otherwise some other button was pressed. In testing it was consistantly this mapping
+                                else {
+                                    match m.buttons {
+                                        0x01 => state.1.push(0x02),
+                                        0x02 => state.1.push(0x01),
+                                        0x04 => state.1.push(0x01),
+                                        _ => (),
+                                    }
+                                }
+                            } else {
+                                state.1.push(m.buttons);
+                            }
 
 
-    pub fn get_event(&self, scope: ScopeId) -> Option<TermEvent> {
-        let mut subscribers = self.subscribers.borrow_mut();
-        let received = subscribers.get_mut(&scope)?;
-        if !*received {
-            *received = true;
-            self.last_event.get()
-        } else {
-            None
+                            buttons = state.1.iter().copied().reduce(|a, b| a | b).unwrap();
+                        }
+                        _ => (),
+                    }
+                    state.0.buttons = buttons;
+                    m.buttons = buttons;
+                }
+                None => {
+                    self.mouse = Some((
+                        clone_mouse_data(m),
+                        if m.buttons == 0 {
+                            Vec::new()
+                        } else {
+                            vec![m.buttons]
+                        },
+                    ));
+                }
+            },
+            EventData::Wheel(ref w) => self.wheel = Some(clone_wheel_data(w)),
+            EventData::Screen(ref s) => self.screen = Some(s.clone()),
+            EventData::Keyboard(ref mut k) => {
+                let repeat = self
+                    .last_key_pressed
+                    .as_ref()
+                    .filter(|k2| k2.0.key == k.key && k2.1.elapsed() < MAX_REPEAT_TIME)
+                    .is_some();
+                k.repeat = repeat;
+                let new = clone_keyboard_data(k);
+                self.last_key_pressed = Some((new, Instant::now()));
+            }
         }
         }
     }
     }
-}
 
 
-#[derive(Props)]
-pub struct AppHandlerProps<'a> {
-    #[props(default)]
-    onkeydown: EventHandler<'a, KeyEvent>,
+    fn update<'a>(
+        &mut self,
+        dom: &'a VirtualDom,
+        evts: &mut Vec<EventCore>,
+        resolved_events: &mut Vec<UserEvent>,
+        layout: &Stretch,
+        layouts: &mut HashMap<ElementId, TuiNode<'a>>,
+        node: &'a VNode<'a>,
+    ) {
+        struct Data<'b> {
+            new_pos: (i32, i32),
+            old_pos: Option<(i32, i32)>,
+            clicked: bool,
+            released: bool,
+            wheel_delta: f64,
+            mouse_data: &'b MouseData,
+            wheel_data: &'b Option<WheelData>,
+        };
+
+        fn layout_contains_point(layout: &Layout, point: (i32, i32)) -> bool {
+            layout.location.x as i32 <= point.0
+                && layout.location.x as i32 + layout.size.width as i32 >= point.0
+                && layout.location.y as i32 <= point.1
+                && layout.location.y as i32 + layout.size.height as i32 >= point.1
+        }
+
+        fn get_mouse_events<'c, 'd>(
+            dom: &'c VirtualDom,
+            resolved_events: &mut Vec<UserEvent>,
+            layout: &Stretch,
+            layouts: &HashMap<ElementId, TuiNode<'c>>,
+            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).unwrap();
+
+            let node_layout = layout.layout(node.layout).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 {
+                VNode::Element(el) => {
+                    let mut events = HashSet::new();
+                    if previously_contained || currently_contains {
+                        for c in el.children {
+                            events = events
+                                .union(&get_mouse_events(
+                                    dom,
+                                    resolved_events,
+                                    layout,
+                                    layouts,
+                                    c,
+                                    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(el.id.get().unwrap()),
+                                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(el.id.get().unwrap()),
+                                    data: Arc::new(clone_wheel_data(w)),
+                                })
+                            }
+                        }
+                    } else {
+                        if previously_contained {
+                            try_create_event("mouseleave");
+                            try_create_event("mouseout");
+                        }
+                    }
+                    events
+                }
+                VNode::Text(_) => HashSet::new(),
+                _ => todo!(),
+            }
+        }
+
+        let previous_mouse = self
+            .mouse
+            .as_ref()
+            .map(|m| (clone_mouse_data(&m.0), m.1.clone()));
+        // println!("{previous_mouse:?}");
+
+        self.wheel = None;
 
 
-    #[props(default)]
-    onmousedown: EventHandler<'a, MouseEvent>,
+        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
+                .as_ref()
+                .map(|m| (m.0.screen_x, m.0.screen_y));
+            let clicked =
+                (!mouse.0.buttons & previous_mouse.as_ref().map(|m| m.0.buttons).unwrap_or(0)) > 0;
+            let released =
+                (mouse.0.buttons & !previous_mouse.map(|m| m.0.buttons).unwrap_or(0)) > 0;
+            let wheel_delta = self.wheel.as_ref().map_or(0.0, |w| w.delta_y);
+            let mouse_data = &mouse.0;
+            let wheel_data = &self.wheel;
+            let data = Data {
+                new_pos,
+                old_pos,
+                clicked,
+                released,
+                wheel_delta,
+                mouse_data,
+                wheel_data,
+            };
+            get_mouse_events(dom, resolved_events, layout, layouts, node, &data);
+        }
 
 
-    #[props(default)]
-    onresize: EventHandler<'a, (u16, u16)>,
+        // for s in &self.subscribers {
+        //     s();
+        // }
+    }
+
+    // fn subscribe(&mut self, f: Rc<dyn Fn() + 'static>) {
+    //     self.subscribers.push(f)
+    // }
 }
 }
 
 
-/// This component lets you handle input events
-///
-/// Once attached to the DOM, it will listen for input events from the terminal
-///
-///
-pub fn InputHandler<'a>(cx: Scope<'a, AppHandlerProps<'a>>) -> Element {
-    let rcx = cx.use_hook(|_| {
-        let rcx = cx
-            .consume_context::<RinkContext>()
-            .unwrap_or_else(|| panic!("Rink InputHandlers can only be used in Rink apps!"));
-
-        // our component will only re-render if new events are received ... or if the parent is updated
-        // todo: if update was not caused by a new event, we should not re-render
-        // perhaps add some tracking to context?
-        rcx.subscribe_to_events(cx.scope_id());
-
-        rcx
-    });
-
-    {
-        if let Some(evet) = rcx.get_event(cx.scope_id()) {
-            match evet {
-                TermEvent::Key(key) => {
-                    cx.props.onkeydown.call(key.clone());
-                    // let mut handler = cx.props.keydown.borrow_mut();
-                    // handler(*key);
-                    // if let Some(handler) = cx.props.onkeydown {
-                    //     handler(*key);
-                    // }
+pub struct RinkInputHandler {
+    state: Rc<RefCell<InnerInputState>>,
+    queued_events: Rc<RefCell<Vec<EventCore>>>,
+}
+
+impl RinkInputHandler {
+    /// global context that handles events
+    /// limitations: GUI key modifier is never detected, key up events are not detected, and only two mouse buttons may be pressed at once
+    pub fn new(
+        mut receiver: UnboundedReceiver<TermEvent>,
+        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);
+
+        cx.push_future(async move {
+            while let Some(evt) = receiver.next().await {
+                if let Some(evt) = get_event(evt) {
+                    if let Some(v) = queued_events2.upgrade() {
+                        (*v).borrow_mut().push(evt);
+                    } else {
+                        break;
+                    }
                 }
                 }
-                TermEvent::Mouse(mouse) => {
-                    cx.props.onmousedown.call(mouse.clone());
+            }
+        });
+
+        let state = Rc::new(RefCell::new(InnerInputState::new()));
+
+        (
+            Self {
+                state: state.clone(),
+                queued_events,
+            },
+            state,
+        )
+    }
+
+    pub fn get_events<'a>(
+        &self,
+        dom: &'a VirtualDom,
+        layout: &Stretch,
+        layouts: &mut HashMap<ElementId, TuiNode<'a>>,
+        node: &'a VNode<'a>,
+    ) -> Vec<UserEvent> {
+        // todo: currently resolves events in all nodes, but once the focus system is added it should filter by focus
+        fn inner(
+            queue: &Vec<(&'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);
+                    }
                 }
                 }
-                TermEvent::Resize(x, y) => {
-                    cx.props.onresize.call((x, y));
+                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,
+        );
+
+        let events: Vec<_> = 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();
+
+        inner(&events, &mut resolved_events, node);
+
+        resolved_events
     }
     }
+}
+
+// translate crossterm events into dioxus events
+fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> {
+    let (name, data): (&str, EventData) = match evt {
+        TermEvent::Key(k) => {
+            let key = translate_key_code(k.code)?;
+            (
+                "keydown",
+                // from https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
+                EventData::Keyboard(KeyboardData {
+                    char_code: key.raw_code(),
+                    key: format!("{key:?}"),
+                    key_code: key,
+                    alt_key: k.modifiers.contains(KeyModifiers::ALT),
+                    ctrl_key: k.modifiers.contains(KeyModifiers::CONTROL),
+                    meta_key: false,
+                    shift_key: k.modifiers.contains(KeyModifiers::SHIFT),
+                    locale: Default::default(),
+                    location: 0x00,
+                    repeat: Default::default(),
+                    which: Default::default(),
+                }),
+            )
+        }
+        TermEvent::Mouse(m) => {
+            let (x, y) = (m.column.into(), m.row.into());
+            let alt = m.modifiers.contains(KeyModifiers::ALT);
+            let shift = m.modifiers.contains(KeyModifiers::SHIFT);
+            let ctrl = m.modifiers.contains(KeyModifiers::CONTROL);
+            let meta = false;
+
+            let get_mouse_data = |b| {
+                let buttons = match b {
+                    None => 0,
+                    Some(MouseButton::Left) => 1,
+                    Some(MouseButton::Right) => 2,
+                    Some(MouseButton::Middle) => 4,
+                };
+                let button_state = match b {
+                    None => 0,
+                    Some(MouseButton::Left) => 0,
+                    Some(MouseButton::Middle) => 1,
+                    Some(MouseButton::Right) => 2,
+                };
+                // from https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent
+                EventData::Mouse(MouseData {
+                    alt_key: alt,
+                    button: button_state,
+                    buttons,
+                    client_x: x,
+                    client_y: y,
+                    ctrl_key: ctrl,
+                    meta_key: meta,
+                    page_x: x,
+                    page_y: y,
+                    screen_x: x,
+                    screen_y: y,
+                    shift_key: shift,
+                })
+            };
+
+            let get_wheel_data = |up| {
+                // from https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent
+                EventData::Wheel(WheelData {
+                    delta_mode: 0x01,
+                    delta_x: 0.0,
+                    delta_y: if up { -1.0 } else { 1.0 },
+                    delta_z: 0.0,
+                })
+            };
+
+            match m.kind {
+                MouseEventKind::Down(b) => ("mousedown", get_mouse_data(Some(b))),
+                MouseEventKind::Up(b) => ("mouseup", get_mouse_data(Some(b))),
+                MouseEventKind::Drag(b) => ("drag", get_mouse_data(Some(b))),
+                MouseEventKind::Moved => ("mousemove", get_mouse_data(None)),
+                MouseEventKind::ScrollDown => ("wheel", get_wheel_data(false)),
+                MouseEventKind::ScrollUp => ("wheel", get_wheel_data(true)),
+            }
+        }
+        TermEvent::Resize(x, y) => ("resize", EventData::Screen((x, y))),
+    };
+
+    Some((name, data))
+}
 
 
-    None
+fn translate_key_code(c: TermKeyCode) -> Option<KeyCode> {
+    match c {
+        TermKeyCode::Backspace => Some(KeyCode::Backspace),
+        TermKeyCode::Enter => Some(KeyCode::Enter),
+        TermKeyCode::Left => Some(KeyCode::LeftArrow),
+        TermKeyCode::Right => Some(KeyCode::RightArrow),
+        TermKeyCode::Up => Some(KeyCode::UpArrow),
+        TermKeyCode::Down => Some(KeyCode::DownArrow),
+        TermKeyCode::Home => Some(KeyCode::Home),
+        TermKeyCode::End => Some(KeyCode::End),
+        TermKeyCode::PageUp => Some(KeyCode::PageUp),
+        TermKeyCode::PageDown => Some(KeyCode::PageDown),
+        TermKeyCode::Tab => Some(KeyCode::Tab),
+        TermKeyCode::BackTab => None,
+        TermKeyCode::Delete => Some(KeyCode::Delete),
+        TermKeyCode::Insert => Some(KeyCode::Insert),
+        TermKeyCode::F(fn_num) => match fn_num {
+            1 => Some(KeyCode::F1),
+            2 => Some(KeyCode::F2),
+            3 => Some(KeyCode::F3),
+            4 => Some(KeyCode::F4),
+            5 => Some(KeyCode::F5),
+            6 => Some(KeyCode::F6),
+            7 => Some(KeyCode::F7),
+            8 => Some(KeyCode::F8),
+            9 => Some(KeyCode::F9),
+            10 => Some(KeyCode::F10),
+            11 => Some(KeyCode::F11),
+            12 => Some(KeyCode::F12),
+            _ => None,
+        },
+        TermKeyCode::Char(c) => match c.to_uppercase().next().unwrap() {
+            'A' => Some(KeyCode::A),
+            'B' => Some(KeyCode::B),
+            'C' => Some(KeyCode::C),
+            'D' => Some(KeyCode::D),
+            'E' => Some(KeyCode::E),
+            'F' => Some(KeyCode::F),
+            'G' => Some(KeyCode::G),
+            'H' => Some(KeyCode::H),
+            'I' => Some(KeyCode::I),
+            'J' => Some(KeyCode::J),
+            'K' => Some(KeyCode::K),
+            'L' => Some(KeyCode::L),
+            'M' => Some(KeyCode::M),
+            'N' => Some(KeyCode::N),
+            'O' => Some(KeyCode::O),
+            'P' => Some(KeyCode::P),
+            'Q' => Some(KeyCode::Q),
+            'R' => Some(KeyCode::R),
+            'S' => Some(KeyCode::S),
+            'T' => Some(KeyCode::T),
+            'U' => Some(KeyCode::U),
+            'V' => Some(KeyCode::V),
+            'W' => Some(KeyCode::W),
+            'X' => Some(KeyCode::X),
+            'Y' => Some(KeyCode::Y),
+            'Z' => Some(KeyCode::Z),
+            _ => None,
+        },
+        TermKeyCode::Null => None,
+        TermKeyCode::Esc => Some(KeyCode::Escape),
+    }
+}
+
+fn clone_mouse_data(m: &MouseData) -> MouseData {
+    MouseData {
+        client_x: m.client_x,
+        client_y: m.client_y,
+        page_x: m.page_x,
+        page_y: m.page_y,
+        screen_x: m.screen_x,
+        screen_y: m.screen_y,
+        alt_key: m.alt_key,
+        ctrl_key: m.ctrl_key,
+        meta_key: m.meta_key,
+        shift_key: m.shift_key,
+        button: m.button,
+        buttons: m.buttons,
+    }
+}
+
+fn clone_keyboard_data(k: &KeyboardData) -> KeyboardData {
+    KeyboardData {
+        char_code: k.char_code,
+        key: k.key.clone(),
+        key_code: k.key_code,
+        alt_key: k.alt_key,
+        ctrl_key: k.ctrl_key,
+        meta_key: k.meta_key,
+        shift_key: k.shift_key,
+        locale: k.locale.clone(),
+        location: k.location,
+        repeat: k.repeat,
+        which: k.which,
+    }
+}
+
+fn clone_wheel_data(w: &WheelData) -> WheelData {
+    WheelData {
+        delta_mode: w.delta_mode,
+        delta_x: w.delta_x,
+        delta_y: w.delta_y,
+        delta_z: w.delta_x,
+    }
 }
 }

+ 19 - 4
src/lib.rs

@@ -31,11 +31,13 @@ pub fn launch(app: Component<()>) {
 
 
     let cx = dom.base_scope();
     let cx = dom.base_scope();
 
 
-    cx.provide_root_context(RinkContext::new(rx, cx));
+    let (handler, state) = RinkInputHandler::new(rx, cx);
+
+    cx.provide_root_context(state);
 
 
     dom.rebuild();
     dom.rebuild();
 
 
-    render_vdom(&mut dom, tx).unwrap();
+    render_vdom(&mut dom, tx, handler).unwrap();
 }
 }
 
 
 pub struct TuiNode<'a> {
 pub struct TuiNode<'a> {
@@ -44,7 +46,11 @@ pub struct TuiNode<'a> {
     pub node: &'a VNode<'a>,
     pub node: &'a VNode<'a>,
 }
 }
 
 
-pub fn render_vdom(vdom: &mut VirtualDom, ctx: UnboundedSender<TermEvent>) -> Result<()> {
+pub fn render_vdom(
+    vdom: &mut VirtualDom,
+    ctx: UnboundedSender<TermEvent>,
+    handler: RinkInputHandler,
+) -> Result<()> {
     // Setup input handling
     // Setup input handling
     let (tx, mut rx) = unbounded();
     let (tx, mut rx) = unbounded();
     std::thread::spawn(move || {
     std::thread::spawn(move || {
@@ -87,6 +93,7 @@ pub fn render_vdom(vdom: &mut VirtualDom, ctx: UnboundedSender<TermEvent>) -> Re
                 /*
                 /*
                 -> collect all the nodes with their layout
                 -> collect all the nodes with their layout
                 -> solve their layout
                 -> solve their layout
+                -> resolve events
                 -> render the nodes in the right place with tui/crosstream
                 -> render the nodes in the right place with tui/crosstream
                 -> while rendering, apply styling
                 -> while rendering, apply styling
 
 
@@ -102,10 +109,11 @@ pub fn render_vdom(vdom: &mut VirtualDom, ctx: UnboundedSender<TermEvent>) -> Re
                 let root_node = vdom.base_scope().root_node();
                 let root_node = vdom.base_scope().root_node();
                 layout::collect_layout(&mut layout, &mut nodes, vdom, root_node);
                 layout::collect_layout(&mut layout, &mut nodes, vdom, root_node);
                 /*
                 /*
-                Compute the layout given th terminal size
+                Compute the layout given the terminal size
                 */
                 */
                 let node_id = root_node.try_mounted_id().unwrap();
                 let node_id = root_node.try_mounted_id().unwrap();
                 let root_layout = nodes[&node_id].layout;
                 let root_layout = nodes[&node_id].layout;
+                let mut events = Vec::new();
 
 
                 terminal.draw(|frame| {
                 terminal.draw(|frame| {
                     // size is guaranteed to not change when rendering
                     // size is guaranteed to not change when rendering
@@ -121,10 +129,17 @@ pub fn render_vdom(vdom: &mut VirtualDom, ctx: UnboundedSender<TermEvent>) -> Re
                             },
                             },
                         )
                         )
                         .unwrap();
                         .unwrap();
+
+                    // resolve events before rendering
+                    events = handler.get_events(vdom, &layout, &mut nodes, root_node);
                     render::render_vnode(frame, &layout, &mut nodes, vdom, root_node);
                     render::render_vnode(frame, &layout, &mut nodes, vdom, root_node);
                     assert!(nodes.is_empty());
                     assert!(nodes.is_empty());
                 })?;
                 })?;
 
 
+                for e in events {
+                    vdom.handle_message(SchedulerMsg::Event(e));
+                }
+
                 use futures::future::{select, Either};
                 use futures::future::{select, Either};
                 {
                 {
                     let wait = vdom.wait_for_work();
                     let wait = vdom.wait_for_work();