Bläddra i källkod

Merge pull request #390 from Demonthos/tui_focus

add tui focus system
Jon Kelley 3 år sedan
förälder
incheckning
d117e5c470

+ 93 - 0
examples/tui_buttons.rs

@@ -0,0 +1,93 @@
+use dioxus::{events::KeyCode, prelude::*};
+
+fn main() {
+    dioxus::tui::launch(app);
+}
+
+#[derive(PartialEq, Props)]
+struct ButtonProps {
+    color_offset: u32,
+    layer: u16,
+}
+
+#[allow(non_snake_case)]
+fn Button(cx: Scope<ButtonProps>) -> Element {
+    let toggle = use_state(&cx, || false);
+    let hovered = use_state(&cx, || false);
+
+    let hue = cx.props.color_offset % 255;
+    let saturation = if *toggle.get() { 50 } else { 25 } + if *hovered.get() { 50 } else { 25 };
+    let brightness = saturation / 2;
+    let color = format!("hsl({hue}, {saturation}, {brightness})");
+
+    cx.render(rsx! {
+        div{
+            width: "100%",
+            height: "100%",
+            background_color: "{color}",
+            tabindex: "{cx.props.layer}",
+            onkeydown: |e| {
+                if let KeyCode::Space = e.data.key_code{
+                    toggle.modify(|f| !f);
+                }
+            },
+            onclick: |_| {
+                toggle.modify(|f| !f);
+            },
+            onmouseenter: |_|{
+                hovered.set(true);
+            },
+            onmouseleave: |_|{
+                hovered.set(false);
+            },
+            justify_content: "center",
+            align_items: "center",
+            display: "flex",
+            flex_direction: "column",
+
+            p{"tabindex: {cx.props.layer}"}
+        }
+    })
+}
+
+fn app(cx: Scope) -> Element {
+    cx.render(rsx! {
+        div {
+            display: "flex",
+            flex_direction: "column",
+            width: "100%",
+            height: "100%",
+
+            (1..8).map(|y|
+                cx.render(rsx!{
+                    div{
+                        display: "flex",
+                        flex_direction: "row",
+                        width: "100%",
+                        height: "100%",
+                        (1..8).map(|x|{
+                            if (x + y) % 2 == 0{
+                                cx.render(rsx!{
+                                    div{
+                                        width: "100%",
+                                        height: "100%",
+                                        background_color: "rgb(100, 100, 100)",
+                                    }
+                                })
+                            }
+                            else{
+                                let layer = (x + y) % 3;
+                                cx.render(rsx!{
+                                    Button{
+                                        color_offset: x * y,
+                                        layer: layer as u16,
+                                    }
+                                })
+                            }
+                        })
+                    }
+                })
+            )
+        }
+    })
+}

+ 342 - 0
packages/native-core-macro/tests/peristant_iterator.rs

@@ -0,0 +1,342 @@
+use dioxus_native_core::{
+    real_dom::{NodeType, RealDom},
+    state::State,
+    utils::PersistantElementIter,
+};
+use dioxus_native_core_macro::State;
+
+#[derive(State, Default, Clone)]
+struct Empty {}
+
+#[test]
+#[allow(unused_variables)]
+fn traverse() {
+    use dioxus_core::*;
+    use dioxus_core_macro::*;
+    use dioxus_html as dioxus_elements;
+
+    #[allow(non_snake_case)]
+    fn Base(cx: Scope) -> Element {
+        rsx!(cx, div {})
+    }
+    let vdom = VirtualDom::new(Base);
+    let mutations = vdom.create_vnodes(rsx! {
+        div{
+            div{
+                "hello"
+                p{
+                    "world"
+                }
+                "hello world"
+            }
+        }
+    });
+
+    let mut rdom: RealDom<Empty> = RealDom::new();
+
+    let _to_update = rdom.apply_mutations(vec![mutations]);
+
+    let mut iter = PersistantElementIter::new();
+    let div_tag = "div".to_string();
+    assert!(matches!(
+        &rdom[iter.next(&rdom).id()].node_type,
+        NodeType::Element { tag: div_tag, .. }
+    ));
+    assert!(matches!(
+        &rdom[iter.next(&rdom).id()].node_type,
+        NodeType::Element { tag: div_tag, .. }
+    ));
+    let text1 = "hello".to_string();
+    assert!(matches!(
+        &rdom[iter.next(&rdom).id()].node_type,
+        NodeType::Text { text: text1, .. }
+    ));
+    let p_tag = "p".to_string();
+    assert!(matches!(
+        &rdom[iter.next(&rdom).id()].node_type,
+        NodeType::Element { tag: p_tag, .. }
+    ));
+    let text2 = "world".to_string();
+    assert!(matches!(
+        &rdom[iter.next(&rdom).id()].node_type,
+        NodeType::Text { text: text2, .. }
+    ));
+    let text3 = "hello world".to_string();
+    assert!(matches!(
+        &rdom[iter.next(&rdom).id()].node_type,
+        NodeType::Text { text: text3, .. }
+    ));
+    assert!(matches!(
+        &rdom[iter.next(&rdom).id()].node_type,
+        NodeType::Element { tag: div_tag, .. }
+    ));
+
+    assert!(matches!(
+        &rdom[iter.prev(&rdom).id()].node_type,
+        NodeType::Text { text: text3, .. }
+    ));
+    assert!(matches!(
+        &rdom[iter.prev(&rdom).id()].node_type,
+        NodeType::Text { text: text2, .. }
+    ));
+    assert!(matches!(
+        &rdom[iter.prev(&rdom).id()].node_type,
+        NodeType::Element { tag: p_tag, .. }
+    ));
+    assert!(matches!(
+        &rdom[iter.prev(&rdom).id()].node_type,
+        NodeType::Text { text: text1, .. }
+    ));
+    assert!(matches!(
+        &rdom[iter.prev(&rdom).id()].node_type,
+        NodeType::Element { tag: div_tag, .. }
+    ));
+    assert!(matches!(
+        &rdom[iter.prev(&rdom).id()].node_type,
+        NodeType::Element { tag: div_tag, .. }
+    ));
+    assert!(matches!(
+        &rdom[iter.prev(&rdom).id()].node_type,
+        NodeType::Element { tag: div_tag, .. }
+    ));
+    assert!(matches!(
+        &rdom[iter.prev(&rdom).id()].node_type,
+        NodeType::Text { text: text3, .. }
+    ));
+}
+
+#[test]
+#[allow(unused_variables)]
+fn persist_removes() {
+    use dioxus_core::VNode;
+    use dioxus_core::*;
+    use dioxus_core_macro::*;
+    use dioxus_html as dioxus_elements;
+
+    #[allow(non_snake_case)]
+    fn Base(cx: Scope) -> Element {
+        rsx!(cx, div {})
+    }
+    let vdom = VirtualDom::new(Base);
+    let (build, update) = vdom.diff_lazynodes(
+        rsx! {
+            div{
+                p{
+                    key: "1",
+                    "hello"
+                }
+                p{
+                    key: "2",
+                    "world"
+                }
+                p{
+                    key: "3",
+                    "hello world"
+                }
+            }
+        },
+        rsx! {
+            div{
+                p{
+                    key: "1",
+                    "hello"
+                }
+                p{
+                    key: "3",
+                    "hello world"
+                }
+            }
+        },
+    );
+
+    let mut rdom: RealDom<Empty> = RealDom::new();
+
+    let _to_update = rdom.apply_mutations(vec![build]);
+
+    // this will end on the node that is removed
+    let mut iter1 = PersistantElementIter::new();
+    // this will end on the after node that is removed
+    let mut iter2 = PersistantElementIter::new();
+    // div
+    iter1.next(&rdom).id();
+    iter2.next(&rdom).id();
+    // p
+    iter1.next(&rdom).id();
+    iter2.next(&rdom).id();
+    // "hello"
+    iter1.next(&rdom).id();
+    iter2.next(&rdom).id();
+    // p
+    iter1.next(&rdom).id();
+    iter2.next(&rdom).id();
+    // "world"
+    iter1.next(&rdom).id();
+    iter2.next(&rdom).id();
+    // p
+    iter2.next(&rdom).id();
+    // "hello world"
+    iter2.next(&rdom).id();
+
+    iter1.prune(&update, &rdom);
+    iter2.prune(&update, &rdom);
+    let _to_update = rdom.apply_mutations(vec![update]);
+
+    let p_tag = "p".to_string();
+    let idx = iter1.next(&rdom).id();
+    assert!(matches!(
+        &rdom[idx].node_type,
+        NodeType::Element { tag: p_tag, .. }
+    ));
+    let text = "hello world".to_string();
+    let idx = iter1.next(&rdom).id();
+    assert!(matches!(&rdom[idx].node_type, NodeType::Text { text, .. }));
+    let div_tag = "div".to_string();
+    let idx = iter2.next(&rdom).id();
+    assert!(matches!(
+        &rdom[idx].node_type,
+        NodeType::Element { tag: div_tag, .. }
+    ));
+}
+
+#[test]
+#[allow(unused_variables)]
+fn persist_instertions_before() {
+    use dioxus_core::*;
+    use dioxus_core_macro::*;
+    use dioxus_html as dioxus_elements;
+
+    #[allow(non_snake_case)]
+    fn Base(cx: Scope) -> Element {
+        rsx!(cx, div {})
+    }
+    let vdom = VirtualDom::new(Base);
+    let (build, update) = vdom.diff_lazynodes(
+        rsx! {
+            div{
+                p{
+                    key: "1",
+                    "hello"
+                }
+                p{
+                    key: "3",
+                    "hello world"
+                }
+            }
+        },
+        rsx! {
+            div{
+                p{
+                    key: "1",
+                    "hello"
+                }
+                p{
+                    key: "2",
+                    "world"
+                }
+                p{
+                    key: "3",
+                    "hello world"
+                }
+            }
+        },
+    );
+
+    let mut rdom: RealDom<Empty> = RealDom::new();
+
+    let _to_update = rdom.apply_mutations(vec![build]);
+
+    let mut iter = PersistantElementIter::new();
+    // div
+    iter.next(&rdom).id();
+    // p
+    iter.next(&rdom).id();
+    // "hello"
+    iter.next(&rdom).id();
+    // p
+    iter.next(&rdom).id();
+    // "hello world"
+    iter.next(&rdom).id();
+
+    iter.prune(&update, &rdom);
+    let _to_update = rdom.apply_mutations(vec![update]);
+
+    let p_tag = "div".to_string();
+    let idx = iter.next(&rdom).id();
+    assert!(matches!(
+        &rdom[idx].node_type,
+        NodeType::Element { tag: p_tag, .. }
+    ));
+}
+
+#[test]
+#[allow(unused_variables)]
+fn persist_instertions_after() {
+    use dioxus_core::*;
+    use dioxus_core_macro::*;
+    use dioxus_html as dioxus_elements;
+
+    #[allow(non_snake_case)]
+    fn Base(cx: Scope) -> Element {
+        rsx!(cx, div {})
+    }
+    let vdom = VirtualDom::new(Base);
+    let (build, update) = vdom.diff_lazynodes(
+        rsx! {
+            div{
+                p{
+                    key: "1",
+                    "hello"
+                }
+                p{
+                    key: "2",
+                    "world"
+                }
+            }
+        },
+        rsx! {
+            div{
+                p{
+                    key: "1",
+                    "hello"
+                }
+                p{
+                    key: "2",
+                    "world"
+                }
+                p{
+                    key: "3",
+                    "hello world"
+                }
+            }
+        },
+    );
+
+    let mut rdom: RealDom<Empty> = RealDom::new();
+
+    let _to_update = rdom.apply_mutations(vec![build]);
+
+    let mut iter = PersistantElementIter::new();
+    // div
+    iter.next(&rdom).id();
+    // p
+    iter.next(&rdom).id();
+    // "hello"
+    iter.next(&rdom).id();
+    // p
+    iter.next(&rdom).id();
+    // "world"
+    iter.next(&rdom).id();
+
+    iter.prune(&update, &rdom);
+    let _to_update = rdom.apply_mutations(vec![update]);
+
+    let p_tag = "p".to_string();
+    let idx = iter.next(&rdom).id();
+    assert!(matches!(
+        &rdom[idx].node_type,
+        NodeType::Element { tag: p_tag, .. }
+    ));
+    let text = "hello world".to_string();
+    let idx = iter.next(&rdom).id();
+    assert!(matches!(&rdom[idx].node_type, NodeType::Text { text, .. }));
+}

+ 1 - 0
packages/native-core/src/lib.rs

@@ -2,3 +2,4 @@ pub mod layout_attributes;
 pub mod node_ref;
 pub mod node_ref;
 pub mod real_dom;
 pub mod real_dom;
 pub mod state;
 pub mod state;
+pub mod utils;

+ 230 - 0
packages/native-core/src/utils.rs

@@ -0,0 +1,230 @@
+use crate::{
+    real_dom::{NodeType, RealDom},
+    state::State,
+};
+use dioxus_core::{DomEdit, ElementId, Mutations};
+
+pub enum ElementProduced {
+    Progressed(ElementId),
+    Looped(ElementId),
+}
+impl ElementProduced {
+    pub fn id(&self) -> ElementId {
+        match self {
+            ElementProduced::Progressed(id) => *id,
+            ElementProduced::Looped(id) => *id,
+        }
+    }
+}
+
+#[derive(Debug)]
+enum NodePosition {
+    AtNode,
+    InChild(usize),
+}
+
+impl NodePosition {
+    fn map(&self, mut f: impl FnMut(usize) -> usize) -> Self {
+        match self {
+            Self::AtNode => Self::AtNode,
+            Self::InChild(i) => Self::InChild(f(*i)),
+        }
+    }
+
+    fn get_or_insert(&mut self, child_idx: usize) -> usize {
+        match self {
+            Self::AtNode => {
+                *self = Self::InChild(child_idx);
+                child_idx
+            }
+            Self::InChild(i) => *i,
+        }
+    }
+}
+
+/// The focus system needs a iterator that can persist through changes in the [VirtualDom].
+/// Iterate through it with [ElementIter::next] [ElementIter::prev], and update it with [ElementIter::update] (with data from [`VirtualDom::work_with_deadline`]).
+/// The iterator loops around when it reaches the end or the beginning.
+pub struct PersistantElementIter {
+    // stack of elements and fragments
+    stack: smallvec::SmallVec<[(ElementId, NodePosition); 5]>,
+}
+
+impl Default for PersistantElementIter {
+    fn default() -> Self {
+        PersistantElementIter {
+            stack: smallvec::smallvec![(ElementId(0), NodePosition::AtNode)],
+        }
+    }
+}
+
+impl PersistantElementIter {
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    /// remove stale element refreneces
+    /// returns true if the focused element is removed
+    pub fn prune<S: State>(&mut self, mutations: &Mutations, rdom: &RealDom<S>) -> bool {
+        let mut changed = false;
+        let ids_removed: Vec<_> = mutations
+            .edits
+            .iter()
+            .filter_map(|e| {
+                if let DomEdit::Remove { root } = e {
+                    Some(*root)
+                } else {
+                    None
+                }
+            })
+            .collect();
+        // if any element is removed in the chain, remove it and its children from the stack
+        if let Some(r) = self
+            .stack
+            .iter()
+            .position(|(el_id, _)| ids_removed.iter().any(|id| el_id.as_u64() == *id))
+        {
+            self.stack.truncate(r);
+            changed = true;
+        }
+        // if a child is removed or inserted before or at the current element, update the child index
+        for (el_id, child_idx) in self.stack.iter_mut() {
+            if let NodePosition::InChild(child_idx) = child_idx {
+                if let NodeType::Element { children, .. } = &rdom[*el_id].node_type {
+                    for m in &mutations.edits {
+                        match m {
+                            DomEdit::Remove { root } => {
+                                if children
+                                    .iter()
+                                    .take(*child_idx + 1)
+                                    .any(|c| c.as_u64() == *root)
+                                {
+                                    *child_idx -= 1;
+                                }
+                            }
+                            DomEdit::InsertBefore { root, n } => {
+                                if children
+                                    .iter()
+                                    .take(*child_idx + 1)
+                                    .any(|c| c.as_u64() == *root)
+                                {
+                                    *child_idx += *n as usize;
+                                }
+                            }
+                            DomEdit::InsertAfter { root, n } => {
+                                if children
+                                    .iter()
+                                    .take(*child_idx)
+                                    .any(|c| c.as_u64() == *root)
+                                {
+                                    *child_idx += *n as usize;
+                                }
+                            }
+                            _ => (),
+                        }
+                    }
+                }
+            }
+        }
+        changed
+    }
+
+    /// get the next element
+    pub fn next<S: State>(&mut self, rdom: &RealDom<S>) -> ElementProduced {
+        if self.stack.is_empty() {
+            let id = ElementId(0);
+            let new = (id, NodePosition::AtNode);
+            self.stack.push(new);
+            ElementProduced::Looped(id)
+        } else {
+            let (last, o_child_idx) = self.stack.last_mut().unwrap();
+            let node = &rdom[*last];
+            match &node.node_type {
+                NodeType::Element { children, .. } => {
+                    *o_child_idx = o_child_idx.map(|i| i + 1);
+                    // if we have children, go to the next child
+                    let child_idx = o_child_idx.get_or_insert(0);
+                    if child_idx >= children.len() {
+                        self.pop();
+                        self.next(rdom)
+                    } else {
+                        let id = children[child_idx];
+                        if let NodeType::Element { .. } = &rdom[id].node_type {
+                            self.stack.push((id, NodePosition::AtNode));
+                        }
+                        ElementProduced::Progressed(id)
+                    }
+                }
+
+                NodeType::Text { .. } | NodeType::Placeholder { .. } => {
+                    // we are at a leaf, so we are done
+                    ElementProduced::Progressed(self.pop())
+                }
+            }
+        }
+    }
+
+    /// get the previous element
+    pub fn prev<S: State>(&mut self, rdom: &RealDom<S>) -> ElementProduced {
+        // recursively add the last child element to the stack
+        fn push_back<S: State>(
+            stack: &mut smallvec::SmallVec<[(ElementId, NodePosition); 5]>,
+            new_node: ElementId,
+            rdom: &RealDom<S>,
+        ) -> ElementId {
+            match &rdom[new_node].node_type {
+                NodeType::Element { children, .. } => {
+                    if children.is_empty() {
+                        new_node
+                    } else {
+                        stack.push((new_node, NodePosition::InChild(children.len() - 1)));
+                        push_back(stack, *children.last().unwrap(), rdom)
+                    }
+                }
+                _ => new_node,
+            }
+        }
+        if self.stack.is_empty() {
+            let new_node = ElementId(0);
+            ElementProduced::Looped(push_back(&mut self.stack, new_node, rdom))
+        } else {
+            let (last, o_child_idx) = self.stack.last_mut().unwrap();
+            let node = &rdom[*last];
+            match &node.node_type {
+                NodeType::Element { children, .. } => {
+                    // if we have children, go to the next child
+                    if let NodePosition::InChild(0) = o_child_idx {
+                        ElementProduced::Progressed(self.pop())
+                    } else {
+                        *o_child_idx = o_child_idx.map(|i| i - 1);
+                        if let NodePosition::InChild(child_idx) = o_child_idx {
+                            if *child_idx >= children.len() || children.is_empty() {
+                                self.pop();
+                                self.prev(rdom)
+                            } else {
+                                let new_node = children[*child_idx];
+                                ElementProduced::Progressed(push_back(
+                                    &mut self.stack,
+                                    new_node,
+                                    rdom,
+                                ))
+                            }
+                        } else {
+                            self.pop();
+                            self.prev(rdom)
+                        }
+                    }
+                }
+
+                NodeType::Text { .. } | NodeType::Placeholder { .. } => {
+                    // we are at a leaf, so we are done
+                    ElementProduced::Progressed(self.pop())
+                }
+            }
+        }
+    }
+
+    fn pop(&mut self) -> ElementId {
+        self.stack.pop().unwrap().0
+    }
+}

+ 5 - 6
packages/tui/README.md

@@ -85,11 +85,10 @@ Rink features:
 - [x] Flexbox based layout system
 - [x] Flexbox based layout system
 - [ ] CSS selectors
 - [ ] CSS selectors
 - [x] inline CSS support
 - [x] inline CSS support
-- [ ] Built-in focusing system
+- [x] Built-in focusing system
 - [ ] high-quality keyboard support
 - [ ] high-quality keyboard support
-- [ ] Support for events, hooks, and callbacks
-* [ ] Html tags<sup>1</sup>
-
-<sup>1</sup> Currently, HTML tags don't translate into any meaning inside of rink. So an `input` won't really mean anything nor does it have any additional functionality.
-
+* [ ] Support for events, hooks, and callbacks<sup>1</sup>
+* [ ] Html tags<sup>2</sup>
 
 
+<sup>1</sup> Basic keyboard and mouse events are implemented.
+<sup>2</sup> Currently, HTML tags don't translate into any meaning inside of rink. So an `input` won't really mean anything nor does it have any additional functionality.

+ 282 - 0
packages/tui/src/focus.rs

@@ -0,0 +1,282 @@
+use crate::{node::PreventDefault, Dom};
+
+use dioxus_core::ElementId;
+use dioxus_native_core::utils::{ElementProduced, PersistantElementIter};
+use dioxus_native_core_macro::sorted_str_slice;
+
+use std::{cmp::Ordering, num::NonZeroU16};
+
+use dioxus_native_core::{
+    node_ref::{AttributeMask, NodeMask, NodeView},
+    real_dom::NodeType,
+    state::NodeDepState,
+};
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub(crate) enum FocusLevel {
+    Unfocusable,
+    Focusable,
+    Ordered(std::num::NonZeroU16),
+}
+
+impl FocusLevel {
+    pub fn focusable(&self) -> bool {
+        match self {
+            FocusLevel::Unfocusable => false,
+            FocusLevel::Focusable => true,
+            FocusLevel::Ordered(_) => true,
+        }
+    }
+}
+
+impl PartialOrd for FocusLevel {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        match (self, other) {
+            (FocusLevel::Unfocusable, FocusLevel::Unfocusable) => Some(std::cmp::Ordering::Equal),
+            (FocusLevel::Unfocusable, FocusLevel::Focusable) => Some(std::cmp::Ordering::Less),
+            (FocusLevel::Unfocusable, FocusLevel::Ordered(_)) => Some(std::cmp::Ordering::Less),
+            (FocusLevel::Focusable, FocusLevel::Unfocusable) => Some(std::cmp::Ordering::Greater),
+            (FocusLevel::Focusable, FocusLevel::Focusable) => Some(std::cmp::Ordering::Equal),
+            (FocusLevel::Focusable, FocusLevel::Ordered(_)) => Some(std::cmp::Ordering::Greater),
+            (FocusLevel::Ordered(_), FocusLevel::Unfocusable) => Some(std::cmp::Ordering::Greater),
+            (FocusLevel::Ordered(_), FocusLevel::Focusable) => Some(std::cmp::Ordering::Less),
+            (FocusLevel::Ordered(a), FocusLevel::Ordered(b)) => a.partial_cmp(b),
+        }
+    }
+}
+
+impl Ord for FocusLevel {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.partial_cmp(other).unwrap()
+    }
+}
+
+impl Default for FocusLevel {
+    fn default() -> Self {
+        FocusLevel::Unfocusable
+    }
+}
+
+#[derive(Clone, PartialEq, Debug, Default)]
+pub(crate) struct Focus {
+    pub level: FocusLevel,
+}
+
+impl NodeDepState for Focus {
+    type Ctx = ();
+    type DepState = ();
+    const NODE_MASK: NodeMask =
+        NodeMask::new_with_attrs(AttributeMask::Static(FOCUS_ATTRIBUTES)).with_listeners();
+
+    fn reduce(&mut self, node: NodeView<'_>, _sibling: &Self::DepState, _: &Self::Ctx) -> bool {
+        let new = Focus {
+            level: if let Some(a) = node.attributes().find(|a| a.name == "tabindex") {
+                if let Some(index) = a
+                    .value
+                    .as_int32()
+                    .or(a.value.as_text().and_then(|v| v.parse::<i32>().ok()))
+                {
+                    match index.cmp(&0) {
+                        Ordering::Less => FocusLevel::Unfocusable,
+                        Ordering::Equal => FocusLevel::Focusable,
+                        Ordering::Greater => {
+                            FocusLevel::Ordered(NonZeroU16::new(index as u16).unwrap())
+                        }
+                    }
+                } else {
+                    FocusLevel::Unfocusable
+                }
+            } else if node
+                .listeners()
+                .iter()
+                .any(|l| FOCUS_EVENTS.binary_search(&l.event).is_ok())
+            {
+                FocusLevel::Focusable
+            } else {
+                FocusLevel::Unfocusable
+            },
+        };
+        if *self != new {
+            *self = new;
+            true
+        } else {
+            false
+        }
+    }
+}
+
+const FOCUS_EVENTS: &[&str] = &sorted_str_slice!(["keydown", "keypress", "keyup"]);
+const FOCUS_ATTRIBUTES: &[&str] = &sorted_str_slice!(["tabindex"]);
+
+#[derive(Default)]
+pub(crate) struct FocusState {
+    pub(crate) focus_iter: PersistantElementIter,
+    pub(crate) last_focused_id: Option<ElementId>,
+    pub(crate) focus_level: FocusLevel,
+    pub(crate) dirty: bool,
+}
+
+impl FocusState {
+    /// Returns true if the focus has changed.
+    pub fn progress(&mut self, rdom: &mut Dom, forward: bool) -> bool {
+        if let Some(last) = self.last_focused_id {
+            if rdom[last].state.prevent_default == PreventDefault::KeyDown {
+                return false;
+            }
+        }
+        // the id that started focused to track when a loop has happened
+        let mut loop_marker_id = self.last_focused_id;
+        let focus_level = &mut self.focus_level;
+        let mut next_focus = None;
+
+        loop {
+            let new = if forward {
+                self.focus_iter.next(rdom)
+            } else {
+                self.focus_iter.prev(rdom)
+            };
+            let new_id = new.id();
+            if let ElementProduced::Looped(_) = new {
+                let mut closest_level = None;
+
+                if forward {
+                    // find the closest focusable element after the current level
+                    rdom.traverse_depth_first(|n| {
+                        let node_level = n.state.focus.level;
+                        if node_level != *focus_level
+                            && node_level.focusable()
+                            && node_level > *focus_level
+                        {
+                            if let Some(level) = &mut closest_level {
+                                if node_level < *level {
+                                    *level = node_level;
+                                }
+                            } else {
+                                closest_level = Some(node_level);
+                            }
+                        }
+                    });
+                } else {
+                    // find the closest focusable element before the current level
+                    rdom.traverse_depth_first(|n| {
+                        let node_level = n.state.focus.level;
+                        if node_level != *focus_level
+                            && node_level.focusable()
+                            && node_level < *focus_level
+                        {
+                            if let Some(level) = &mut closest_level {
+                                if node_level > *level {
+                                    *level = node_level;
+                                }
+                            } else {
+                                closest_level = Some(node_level);
+                            }
+                        }
+                    });
+                }
+
+                // extend the loop_marker_id to allow for another pass
+                loop_marker_id = None;
+
+                if let Some(level) = closest_level {
+                    *focus_level = level;
+                } else if forward {
+                    *focus_level = FocusLevel::Unfocusable;
+                } else {
+                    *focus_level = FocusLevel::Focusable;
+                }
+            }
+
+            // once we have looked at all the elements exit the loop
+            if let Some(last) = loop_marker_id {
+                if new_id == last {
+                    break;
+                }
+            } else {
+                loop_marker_id = Some(new_id);
+            }
+
+            let current_level = rdom[new_id].state.focus.level;
+            let after_previous_focused = if forward {
+                current_level >= *focus_level
+            } else {
+                current_level <= *focus_level
+            };
+            if after_previous_focused && current_level.focusable() && current_level == *focus_level
+            {
+                next_focus = Some(new_id);
+                break;
+            }
+        }
+
+        if let Some(id) = next_focus {
+            rdom[id].state.focused = true;
+            if let Some(old) = self.last_focused_id.replace(id) {
+                rdom[old].state.focused = false;
+            }
+            // reset the position to the currently focused element
+            while self.focus_iter.next(rdom).id() != id {}
+            self.dirty = true;
+            return true;
+        }
+
+        false
+    }
+
+    pub(crate) fn prune(&mut self, mutations: &dioxus_core::Mutations, rdom: &Dom) {
+        fn remove_children(
+            to_prune: &mut [&mut Option<ElementId>],
+            rdom: &Dom,
+            removed: ElementId,
+        ) {
+            for opt in to_prune.iter_mut() {
+                if let Some(id) = opt {
+                    if *id == removed {
+                        **opt = None;
+                    }
+                }
+            }
+            if let NodeType::Element { children, .. } = &rdom[removed].node_type {
+                for child in children {
+                    remove_children(to_prune, rdom, *child);
+                }
+            }
+        }
+        if self.focus_iter.prune(mutations, rdom) {
+            self.dirty = true;
+        }
+        for m in &mutations.edits {
+            match m {
+                dioxus_core::DomEdit::ReplaceWith { root, .. } => remove_children(
+                    &mut [&mut self.last_focused_id],
+                    rdom,
+                    ElementId(*root as usize),
+                ),
+                dioxus_core::DomEdit::Remove { root } => remove_children(
+                    &mut [&mut self.last_focused_id],
+                    rdom,
+                    ElementId(*root as usize),
+                ),
+                _ => (),
+            }
+        }
+    }
+
+    pub(crate) fn set_focus(&mut self, rdom: &mut Dom, id: ElementId) {
+        if let Some(old) = self.last_focused_id.replace(id) {
+            rdom[old].state.focused = false;
+        }
+        let state = &mut rdom[id].state;
+        state.focused = true;
+        self.focus_level = state.focus.level;
+        // reset the position to the currently focused element
+        while self.focus_iter.next(rdom).id() != id {}
+        self.dirty = true;
+    }
+
+    pub(crate) fn clean(&mut self) -> bool {
+        let old = self.dirty;
+        self.dirty = false;
+        old
+    }
+}

+ 131 - 49
packages/tui/src/hooks.rs

@@ -12,7 +12,7 @@ use dioxus_html::input_data::{MouseButton as DioxusMouseButton, MouseButtonSet};
 use dioxus_html::{on::*, KeyCode};
 use dioxus_html::{on::*, KeyCode};
 use std::{
 use std::{
     any::Any,
     any::Any,
-    cell::RefCell,
+    cell::{RefCell, RefMut},
     rc::Rc,
     rc::Rc,
     sync::Arc,
     sync::Arc,
     time::{Duration, Instant},
     time::{Duration, Instant},
@@ -20,6 +20,7 @@ use std::{
 use taffy::geometry::{Point, Size};
 use taffy::geometry::{Point, Size};
 use taffy::{prelude::Layout, Taffy};
 use taffy::{prelude::Layout, Taffy};
 
 
+use crate::FocusState;
 use crate::{Dom, Node};
 use crate::{Dom, Node};
 
 
 // a wrapper around the input state for easier access
 // a wrapper around the input state for easier access
@@ -36,22 +37,22 @@ use crate::{Dom, Node};
 
 
 //     pub fn mouse(&self) -> Option<MouseData> {
 //     pub fn mouse(&self) -> Option<MouseData> {
 //         let data = (**self.0).borrow();
 //         let data = (**self.0).borrow();
-//         data.mouse.as_ref().map(|m| m.clone())
+//         mouse.as_ref().map(|m| m.clone())
 //     }
 //     }
 
 
 //     pub fn wheel(&self) -> Option<WheelData> {
 //     pub fn wheel(&self) -> Option<WheelData> {
 //         let data = (**self.0).borrow();
 //         let data = (**self.0).borrow();
-//         data.wheel.as_ref().map(|w| w.clone())
+//         wheel.as_ref().map(|w| w.clone())
 //     }
 //     }
 
 
 //     pub fn screen(&self) -> Option<(u16, u16)> {
 //     pub fn screen(&self) -> Option<(u16, u16)> {
 //         let data = (**self.0).borrow();
 //         let data = (**self.0).borrow();
-//         data.screen.as_ref().map(|m| m.clone())
+//         screen.as_ref().map(|m| m.clone())
 //     }
 //     }
 
 
 //     pub fn last_key_pressed(&self) -> Option<KeyboardData> {
 //     pub fn last_key_pressed(&self) -> Option<KeyboardData> {
 //         let data = (**self.0).borrow();
 //         let data = (**self.0).borrow();
-//         data.last_key_pressed
+//         last_key_pressed
 //             .as_ref()
 //             .as_ref()
 //             .map(|k| &k.0.clone())
 //             .map(|k| &k.0.clone())
 //     }
 //     }
@@ -84,6 +85,7 @@ pub struct InnerInputState {
     wheel: Option<WheelData>,
     wheel: Option<WheelData>,
     last_key_pressed: Option<(KeyboardData, Instant)>,
     last_key_pressed: Option<(KeyboardData, Instant)>,
     screen: Option<(u16, u16)>,
     screen: Option<(u16, u16)>,
+    pub(crate) focus_state: FocusState,
     // subscribers: Vec<Rc<dyn Fn() + 'static>>,
     // subscribers: Vec<Rc<dyn Fn() + 'static>>,
 }
 }
 
 
@@ -95,6 +97,7 @@ impl InnerInputState {
             last_key_pressed: None,
             last_key_pressed: None,
             screen: None,
             screen: None,
             // subscribers: Vec::new(),
             // subscribers: Vec::new(),
+            focus_state: FocusState::default(),
         }
         }
     }
     }
 
 
@@ -151,7 +154,7 @@ impl InnerInputState {
 
 
     fn update(
     fn update(
         &mut self,
         &mut self,
-        evts: &mut [EventCore],
+        evts: &mut Vec<EventCore>,
         resolved_events: &mut Vec<UserEvent>,
         resolved_events: &mut Vec<UserEvent>,
         layout: &Taffy,
         layout: &Taffy,
         dom: &mut Dom,
         dom: &mut Dom,
@@ -160,19 +163,57 @@ impl InnerInputState {
 
 
         self.wheel = None;
         self.wheel = None;
 
 
+        let old_focus = self.focus_state.last_focused_id;
+
+        evts.retain(|e| match &e.1 {
+            EventData::Keyboard(k) => match k.key_code {
+                KeyCode::Tab => !self.focus_state.progress(dom, !k.shift_key),
+                _ => true,
+            },
+            _ => true,
+        });
+
         for e in evts.iter_mut() {
         for e in evts.iter_mut() {
             self.apply_event(e);
             self.apply_event(e);
         }
         }
 
 
         self.resolve_mouse_events(previous_mouse, resolved_events, layout, dom);
         self.resolve_mouse_events(previous_mouse, resolved_events, layout, dom);
 
 
+        if old_focus != self.focus_state.last_focused_id {
+            if let Some(id) = self.focus_state.last_focused_id {
+                resolved_events.push(UserEvent {
+                    scope_id: None,
+                    priority: EventPriority::Medium,
+                    name: "focus",
+                    element: Some(id),
+                    data: Arc::new(FocusData {}),
+                });
+                resolved_events.push(UserEvent {
+                    scope_id: None,
+                    priority: EventPriority::Medium,
+                    name: "focusin",
+                    element: Some(id),
+                    data: Arc::new(FocusData {}),
+                });
+            }
+            if let Some(id) = old_focus {
+                resolved_events.push(UserEvent {
+                    scope_id: None,
+                    priority: EventPriority::Medium,
+                    name: "focusout",
+                    element: Some(id),
+                    data: Arc::new(FocusData {}),
+                });
+            }
+        }
+
         // for s in &self.subscribers {
         // for s in &self.subscribers {
         //     s();
         //     s();
         // }
         // }
     }
     }
 
 
     fn resolve_mouse_events(
     fn resolve_mouse_events(
-        &self,
+        &mut self,
         previous_mouse: Option<MouseData>,
         previous_mouse: Option<MouseData>,
         resolved_events: &mut Vec<UserEvent>,
         resolved_events: &mut Vec<UserEvent>,
         layout: &Taffy,
         layout: &Taffy,
@@ -256,16 +297,16 @@ impl InnerInputState {
                 if old_pos != Some(new_pos) {
                 if old_pos != Some(new_pos) {
                     let mut will_bubble = FxHashSet::default();
                     let mut will_bubble = FxHashSet::default();
                     for node in dom.get_listening_sorted("mousemove") {
                     for node in dom.get_listening_sorted("mousemove") {
-                        let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
+                        let node_layout = get_abs_layout(node, dom, layout);
                         let previously_contained = old_pos
                         let previously_contained = old_pos
-                            .filter(|pos| layout_contains_point(node_layout, *pos))
+                            .filter(|pos| layout_contains_point(&node_layout, *pos))
                             .is_some();
                             .is_some();
-                        let currently_contains = layout_contains_point(node_layout, new_pos);
+                        let currently_contains = layout_contains_point(&node_layout, new_pos);
 
 
                         if currently_contains && previously_contained {
                         if currently_contains && previously_contained {
                             try_create_event(
                             try_create_event(
                                 "mousemove",
                                 "mousemove",
-                                Arc::new(prepare_mouse_data(mouse_data, node_layout)),
+                                Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
                                 &mut will_bubble,
                                 &mut will_bubble,
                                 resolved_events,
                                 resolved_events,
                                 node,
                                 node,
@@ -280,11 +321,11 @@ impl InnerInputState {
                 // mouseenter
                 // mouseenter
                 let mut will_bubble = FxHashSet::default();
                 let mut will_bubble = FxHashSet::default();
                 for node in dom.get_listening_sorted("mouseenter") {
                 for node in dom.get_listening_sorted("mouseenter") {
-                    let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
+                    let node_layout = get_abs_layout(node, dom, layout);
                     let previously_contained = old_pos
                     let previously_contained = old_pos
-                        .filter(|pos| layout_contains_point(node_layout, *pos))
+                        .filter(|pos| layout_contains_point(&node_layout, *pos))
                         .is_some();
                         .is_some();
-                    let currently_contains = layout_contains_point(node_layout, new_pos);
+                    let currently_contains = layout_contains_point(&node_layout, new_pos);
 
 
                     if currently_contains && !previously_contained {
                     if currently_contains && !previously_contained {
                         try_create_event(
                         try_create_event(
@@ -303,16 +344,16 @@ impl InnerInputState {
                 // mouseover
                 // mouseover
                 let mut will_bubble = FxHashSet::default();
                 let mut will_bubble = FxHashSet::default();
                 for node in dom.get_listening_sorted("mouseover") {
                 for node in dom.get_listening_sorted("mouseover") {
-                    let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
+                    let node_layout = get_abs_layout(node, dom, layout);
                     let previously_contained = old_pos
                     let previously_contained = old_pos
-                        .filter(|pos| layout_contains_point(node_layout, *pos))
+                        .filter(|pos| layout_contains_point(&node_layout, *pos))
                         .is_some();
                         .is_some();
-                    let currently_contains = layout_contains_point(node_layout, new_pos);
+                    let currently_contains = layout_contains_point(&node_layout, new_pos);
 
 
                     if currently_contains && !previously_contained {
                     if currently_contains && !previously_contained {
                         try_create_event(
                         try_create_event(
                             "mouseover",
                             "mouseover",
-                            Arc::new(prepare_mouse_data(mouse_data, node_layout)),
+                            Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
                             &mut will_bubble,
                             &mut will_bubble,
                             resolved_events,
                             resolved_events,
                             node,
                             node,
@@ -326,13 +367,13 @@ impl InnerInputState {
             if was_pressed {
             if was_pressed {
                 let mut will_bubble = FxHashSet::default();
                 let mut will_bubble = FxHashSet::default();
                 for node in dom.get_listening_sorted("mousedown") {
                 for node in dom.get_listening_sorted("mousedown") {
-                    let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
-                    let currently_contains = layout_contains_point(node_layout, new_pos);
+                    let node_layout = get_abs_layout(node, dom, layout);
+                    let currently_contains = layout_contains_point(&node_layout, new_pos);
 
 
                     if currently_contains {
                     if currently_contains {
                         try_create_event(
                         try_create_event(
                             "mousedown",
                             "mousedown",
-                            Arc::new(prepare_mouse_data(mouse_data, node_layout)),
+                            Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
                             &mut will_bubble,
                             &mut will_bubble,
                             resolved_events,
                             resolved_events,
                             node,
                             node,
@@ -347,13 +388,13 @@ impl InnerInputState {
                 if was_released {
                 if was_released {
                     let mut will_bubble = FxHashSet::default();
                     let mut will_bubble = FxHashSet::default();
                     for node in dom.get_listening_sorted("mouseup") {
                     for node in dom.get_listening_sorted("mouseup") {
-                        let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
-                        let currently_contains = layout_contains_point(node_layout, new_pos);
+                        let node_layout = get_abs_layout(node, dom, layout);
+                        let currently_contains = layout_contains_point(&node_layout, new_pos);
 
 
                         if currently_contains {
                         if currently_contains {
                             try_create_event(
                             try_create_event(
                                 "mouseup",
                                 "mouseup",
-                                Arc::new(prepare_mouse_data(mouse_data, node_layout)),
+                                Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
                                 &mut will_bubble,
                                 &mut will_bubble,
                                 resolved_events,
                                 resolved_events,
                                 node,
                                 node,
@@ -369,13 +410,13 @@ impl InnerInputState {
                 if mouse_data.trigger_button() == Some(DioxusMouseButton::Primary) && was_released {
                 if mouse_data.trigger_button() == Some(DioxusMouseButton::Primary) && was_released {
                     let mut will_bubble = FxHashSet::default();
                     let mut will_bubble = FxHashSet::default();
                     for node in dom.get_listening_sorted("click") {
                     for node in dom.get_listening_sorted("click") {
-                        let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
-                        let currently_contains = layout_contains_point(node_layout, new_pos);
+                        let node_layout = get_abs_layout(node, dom, layout);
+                        let currently_contains = layout_contains_point(&node_layout, new_pos);
 
 
                         if currently_contains {
                         if currently_contains {
                             try_create_event(
                             try_create_event(
                                 "click",
                                 "click",
-                                Arc::new(prepare_mouse_data(mouse_data, node_layout)),
+                                Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
                                 &mut will_bubble,
                                 &mut will_bubble,
                                 resolved_events,
                                 resolved_events,
                                 node,
                                 node,
@@ -392,13 +433,13 @@ impl InnerInputState {
                 {
                 {
                     let mut will_bubble = FxHashSet::default();
                     let mut will_bubble = FxHashSet::default();
                     for node in dom.get_listening_sorted("contextmenu") {
                     for node in dom.get_listening_sorted("contextmenu") {
-                        let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
-                        let currently_contains = layout_contains_point(node_layout, new_pos);
+                        let node_layout = get_abs_layout(node, dom, layout);
+                        let currently_contains = layout_contains_point(&node_layout, new_pos);
 
 
                         if currently_contains {
                         if currently_contains {
                             try_create_event(
                             try_create_event(
                                 "contextmenu",
                                 "contextmenu",
-                                Arc::new(prepare_mouse_data(mouse_data, node_layout)),
+                                Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
                                 &mut will_bubble,
                                 &mut will_bubble,
                                 resolved_events,
                                 resolved_events,
                                 node,
                                 node,
@@ -415,9 +456,9 @@ impl InnerInputState {
                     if wheel_delta != 0.0 {
                     if wheel_delta != 0.0 {
                         let mut will_bubble = FxHashSet::default();
                         let mut will_bubble = FxHashSet::default();
                         for node in dom.get_listening_sorted("wheel") {
                         for node in dom.get_listening_sorted("wheel") {
-                            let node_layout =
-                                layout.layout(node.state.layout.node.unwrap()).unwrap();
-                            let currently_contains = layout_contains_point(node_layout, new_pos);
+                            let node_layout = get_abs_layout(node, dom, layout);
+
+                            let currently_contains = layout_contains_point(&node_layout, new_pos);
 
 
                             if currently_contains {
                             if currently_contains {
                                 try_create_event(
                                 try_create_event(
@@ -438,16 +479,16 @@ impl InnerInputState {
                 // mouseleave
                 // mouseleave
                 let mut will_bubble = FxHashSet::default();
                 let mut will_bubble = FxHashSet::default();
                 for node in dom.get_listening_sorted("mouseleave") {
                 for node in dom.get_listening_sorted("mouseleave") {
-                    let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
+                    let node_layout = get_abs_layout(node, dom, layout);
                     let previously_contained = old_pos
                     let previously_contained = old_pos
-                        .filter(|pos| layout_contains_point(node_layout, *pos))
+                        .filter(|pos| layout_contains_point(&node_layout, *pos))
                         .is_some();
                         .is_some();
-                    let currently_contains = layout_contains_point(node_layout, new_pos);
+                    let currently_contains = layout_contains_point(&node_layout, new_pos);
 
 
                     if !currently_contains && previously_contained {
                     if !currently_contains && previously_contained {
                         try_create_event(
                         try_create_event(
                             "mouseleave",
                             "mouseleave",
-                            Arc::new(prepare_mouse_data(mouse_data, node_layout)),
+                            Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
                             &mut will_bubble,
                             &mut will_bubble,
                             resolved_events,
                             resolved_events,
                             node,
                             node,
@@ -461,16 +502,16 @@ impl InnerInputState {
                 // mouseout
                 // mouseout
                 let mut will_bubble = FxHashSet::default();
                 let mut will_bubble = FxHashSet::default();
                 for node in dom.get_listening_sorted("mouseout") {
                 for node in dom.get_listening_sorted("mouseout") {
-                    let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
+                    let node_layout = get_abs_layout(node, dom, layout);
                     let previously_contained = old_pos
                     let previously_contained = old_pos
-                        .filter(|pos| layout_contains_point(node_layout, *pos))
+                        .filter(|pos| layout_contains_point(&node_layout, *pos))
                         .is_some();
                         .is_some();
-                    let currently_contains = layout_contains_point(node_layout, new_pos);
+                    let currently_contains = layout_contains_point(&node_layout, new_pos);
 
 
                     if !currently_contains && previously_contained {
                     if !currently_contains && previously_contained {
                         try_create_event(
                         try_create_event(
                             "mouseout",
                             "mouseout",
-                            Arc::new(prepare_mouse_data(mouse_data, node_layout)),
+                            Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
                             &mut will_bubble,
                             &mut will_bubble,
                             resolved_events,
                             resolved_events,
                             node,
                             node,
@@ -479,6 +520,22 @@ impl InnerInputState {
                     }
                     }
                 }
                 }
             }
             }
+
+            // update focus
+            if was_released {
+                let mut focus_id = None;
+                dom.traverse_depth_first(|node| {
+                    let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
+                    let currently_contains = layout_contains_point(node_layout, new_pos);
+
+                    if currently_contains && node.state.focus.level.focusable() {
+                        focus_id = Some(node.id);
+                    }
+                });
+                if let Some(id) = focus_id {
+                    self.focus_state.set_focus(dom, id);
+                }
+            }
         }
         }
     }
     }
 
 
@@ -487,6 +544,22 @@ impl InnerInputState {
     // }
     // }
 }
 }
 
 
+fn get_abs_layout(node: &Node, dom: &Dom, taffy: &Taffy) -> Layout {
+    let mut node_layout = taffy
+        .layout(node.state.layout.node.unwrap())
+        .unwrap()
+        .clone();
+    let mut current = node;
+    while let Some(parent_id) = current.parent {
+        let parent = &dom[parent_id];
+        current = parent;
+        let parent_layout = taffy.layout(parent.state.layout.node.unwrap()).unwrap();
+        node_layout.location.x += parent_layout.location.x;
+        node_layout.location.y += parent_layout.location.y;
+    }
+    node_layout
+}
+
 pub struct RinkInputHandler {
 pub struct RinkInputHandler {
     state: Rc<RefCell<InnerInputState>>,
     state: Rc<RefCell<InnerInputState>>,
     queued_events: Rc<RefCell<Vec<EventCore>>>,
     queued_events: Rc<RefCell<Vec<EventCore>>>,
@@ -523,6 +596,10 @@ impl RinkInputHandler {
         )
         )
     }
     }
 
 
+    pub(crate) fn prune(&self, mutations: &dioxus_core::Mutations, rdom: &Dom) {
+        self.state.borrow_mut().focus_state.prune(mutations, rdom);
+    }
+
     pub(crate) fn get_events(&self, layout: &Taffy, dom: &mut Dom) -> Vec<UserEvent> {
     pub(crate) fn get_events(&self, layout: &Taffy, dom: &mut Dom) -> Vec<UserEvent> {
         let mut resolved_events = Vec::new();
         let mut resolved_events = Vec::new();
 
 
@@ -556,7 +633,6 @@ impl RinkInputHandler {
             })
             })
             .map(|evt| (evt.0, evt.1.into_any()));
             .map(|evt| (evt.0, evt.1.into_any()));
 
 
-        // todo: currently resolves events in all nodes, but once the focus system is added it should filter by focus
         let mut hm: FxHashMap<&'static str, Vec<Arc<dyn Any + Send + Sync>>> = FxHashMap::default();
         let mut hm: FxHashMap<&'static str, Vec<Arc<dyn Any + Send + Sync>>> = FxHashMap::default();
         for (event, data) in events {
         for (event, data) in events {
             if let Some(v) = hm.get_mut(event) {
             if let Some(v) = hm.get_mut(event) {
@@ -568,19 +644,25 @@ impl RinkInputHandler {
         for (event, datas) in hm {
         for (event, datas) in hm {
             for node in dom.get_listening_sorted(event) {
             for node in dom.get_listening_sorted(event) {
                 for data in &datas {
                 for data in &datas {
-                    resolved_events.push(UserEvent {
-                        scope_id: None,
-                        priority: EventPriority::Medium,
-                        name: event,
-                        element: Some(node.id),
-                        data: data.clone(),
-                    });
+                    if node.state.focused {
+                        resolved_events.push(UserEvent {
+                            scope_id: None,
+                            priority: EventPriority::Medium,
+                            name: event,
+                            element: Some(node.id),
+                            data: data.clone(),
+                        });
+                    }
                 }
                 }
             }
             }
         }
         }
 
 
         resolved_events
         resolved_events
     }
     }
+
+    pub(crate) fn state(&self) -> RefMut<InnerInputState> {
+        self.state.borrow_mut()
+    }
 }
 }
 
 
 // translate crossterm events into dioxus events
 // translate crossterm events into dioxus events

+ 15 - 20
packages/tui/src/lib.rs

@@ -7,23 +7,23 @@ use crossterm::{
 };
 };
 use dioxus_core::exports::futures_channel::mpsc::unbounded;
 use dioxus_core::exports::futures_channel::mpsc::unbounded;
 use dioxus_core::*;
 use dioxus_core::*;
-use dioxus_native_core::{real_dom::RealDom, state::*};
-use dioxus_native_core_macro::State;
+use dioxus_native_core::real_dom::RealDom;
+use focus::FocusState;
 use futures::{
 use futures::{
     channel::mpsc::{UnboundedReceiver, UnboundedSender},
     channel::mpsc::{UnboundedReceiver, UnboundedSender},
     pin_mut, StreamExt,
     pin_mut, StreamExt,
 };
 };
-use layout::TaffyLayout;
 use std::cell::RefCell;
 use std::cell::RefCell;
 use std::rc::Rc;
 use std::rc::Rc;
 use std::{io, time::Duration};
 use std::{io, time::Duration};
-use style_attributes::StyleModifier;
 use taffy::{geometry::Point, prelude::Size, Taffy};
 use taffy::{geometry::Point, prelude::Size, Taffy};
 use tui::{backend::CrosstermBackend, layout::Rect, Terminal};
 use tui::{backend::CrosstermBackend, layout::Rect, Terminal};
 
 
 mod config;
 mod config;
+mod focus;
 mod hooks;
 mod hooks;
 mod layout;
 mod layout;
+mod node;
 mod render;
 mod render;
 mod style;
 mod style;
 mod style_attributes;
 mod style_attributes;
@@ -31,18 +31,7 @@ mod widget;
 
 
 pub use config::*;
 pub use config::*;
 pub use hooks::*;
 pub use hooks::*;
-
-type Dom = RealDom<NodeState>;
-type Node = dioxus_native_core::real_dom::Node<NodeState>;
-
-#[derive(Debug, Clone, State, Default)]
-struct NodeState {
-    #[child_dep_state(layout, RefCell<Taffy>)]
-    layout: TaffyLayout,
-    // depends on attributes, the C component of it's parent and a u8 context
-    #[parent_dep_state(style)]
-    style: StyleModifier,
-}
+pub(crate) use node::*;
 
 
 #[derive(Clone)]
 #[derive(Clone)]
 pub struct TuiContext {
 pub struct TuiContext {
@@ -130,7 +119,7 @@ fn render_vdom(
             }
             }
 
 
             let mut to_rerender: fxhash::FxHashSet<usize> = vec![0].into_iter().collect();
             let mut to_rerender: fxhash::FxHashSet<usize> = vec![0].into_iter().collect();
-            let mut resized = true;
+            let mut updated = true;
 
 
             loop {
             loop {
                 /*
                 /*
@@ -144,8 +133,8 @@ fn render_vdom(
                 todo: lazy re-rendering
                 todo: lazy re-rendering
                 */
                 */
 
 
-                if !to_rerender.is_empty() || resized {
-                    resized = false;
+                if !to_rerender.is_empty() || updated {
+                    updated = false;
                     fn resize(dims: Rect, taffy: &mut Taffy, rdom: &Dom) {
                     fn resize(dims: Rect, taffy: &mut Taffy, rdom: &Dom) {
                         let width = dims.width;
                         let width = dims.width;
                         let height = dims.height;
                         let height = dims.height;
@@ -209,7 +198,7 @@ fn render_vdom(
                                             break;
                                             break;
                                         }
                                         }
                                     }
                                     }
-                                    TermEvent::Resize(_, _) => resized = true,
+                                    TermEvent::Resize(_, _) => updated = true,
                                     TermEvent::Mouse(_) => {}
                                     TermEvent::Mouse(_) => {}
                                 },
                                 },
                                 InputEvent::Close => break,
                                 InputEvent::Close => break,
@@ -224,10 +213,16 @@ fn render_vdom(
 
 
                 {
                 {
                     let evts = handler.get_events(&taffy.borrow(), &mut rdom);
                     let evts = handler.get_events(&taffy.borrow(), &mut rdom);
+                    {
+                        updated |= handler.state().focus_state.clean();
+                    }
                     for e in evts {
                     for e in evts {
                         vdom.handle_message(SchedulerMsg::Event(e));
                         vdom.handle_message(SchedulerMsg::Event(e));
                     }
                     }
                     let mutations = vdom.work_with_deadline(|| false);
                     let mutations = vdom.work_with_deadline(|| false);
+                    for m in &mutations {
+                        handler.prune(m, &rdom);
+                    }
                     // updates the dom's nodes
                     // updates the dom's nodes
                     let to_update = rdom.apply_mutations(mutations);
                     let to_update = rdom.apply_mutations(mutations);
                     // update the style and layout
                     // update the style and layout

+ 94 - 0
packages/tui/src/node.rs

@@ -0,0 +1,94 @@
+use crate::focus::Focus;
+use crate::layout::TaffyLayout;
+use crate::style_attributes::StyleModifier;
+use dioxus_native_core::{real_dom::RealDom, state::*};
+use dioxus_native_core_macro::{sorted_str_slice, State};
+
+pub(crate) type Dom = RealDom<NodeState>;
+pub(crate) type Node = dioxus_native_core::real_dom::Node<NodeState>;
+
+#[derive(Debug, Clone, State, Default)]
+pub(crate) struct NodeState {
+    #[child_dep_state(layout, RefCell<Stretch>)]
+    pub layout: TaffyLayout,
+    #[parent_dep_state(style)]
+    pub style: StyleModifier,
+    #[node_dep_state()]
+    pub prevent_default: PreventDefault,
+    #[node_dep_state()]
+    pub focus: Focus,
+    pub focused: bool,
+}
+
+#[derive(PartialEq, Debug, Clone)]
+pub(crate) enum PreventDefault {
+    Focus,
+    KeyPress,
+    KeyRelease,
+    KeyDown,
+    KeyUp,
+    MouseDown,
+    Click,
+    MouseEnter,
+    MouseLeave,
+    MouseOut,
+    Unknown,
+    MouseOver,
+    ContextMenu,
+    Wheel,
+    MouseUp,
+}
+
+impl Default for PreventDefault {
+    fn default() -> Self {
+        PreventDefault::Unknown
+    }
+}
+
+impl NodeDepState for PreventDefault {
+    type Ctx = ();
+
+    type DepState = ();
+
+    const NODE_MASK: dioxus_native_core::node_ref::NodeMask =
+        dioxus_native_core::node_ref::NodeMask::new_with_attrs(
+            dioxus_native_core::node_ref::AttributeMask::Static(&sorted_str_slice!([
+                "dioxus-prevent-default"
+            ])),
+        );
+
+    fn reduce(
+        &mut self,
+        node: dioxus_native_core::node_ref::NodeView,
+        _sibling: &Self::DepState,
+        _ctx: &Self::Ctx,
+    ) -> bool {
+        let new = match node
+            .attributes()
+            .find(|a| a.name == "dioxus-prevent-default")
+            .and_then(|a| a.value.as_text())
+        {
+            Some("onfocus") => PreventDefault::Focus,
+            Some("onkeypress") => PreventDefault::KeyPress,
+            Some("onkeyrelease") => PreventDefault::KeyRelease,
+            Some("onkeydown") => PreventDefault::KeyDown,
+            Some("onkeyup") => PreventDefault::KeyUp,
+            Some("onclick") => PreventDefault::Click,
+            Some("onmousedown") => PreventDefault::MouseDown,
+            Some("onmouseup") => PreventDefault::MouseUp,
+            Some("onmouseenter") => PreventDefault::MouseEnter,
+            Some("onmouseover") => PreventDefault::MouseOver,
+            Some("onmouseleave") => PreventDefault::MouseLeave,
+            Some("onmouseout") => PreventDefault::MouseOut,
+            Some("onwheel") => PreventDefault::Wheel,
+            Some("oncontextmenu") => PreventDefault::ContextMenu,
+            _ => return false,
+        };
+        if new == *self {
+            false
+        } else {
+            *self = new;
+            true
+        }
+    }
+}

+ 5 - 1
packages/tui/src/render.rs

@@ -5,7 +5,7 @@ use taffy::{
     prelude::{Layout, Size},
     prelude::{Layout, Size},
     Taffy,
     Taffy,
 };
 };
-use tui::{backend::CrosstermBackend, layout::Rect};
+use tui::{backend::CrosstermBackend, layout::Rect, style::Color};
 
 
 use crate::{
 use crate::{
     style::{RinkColor, RinkStyle},
     style::{RinkColor, RinkStyle},
@@ -272,6 +272,10 @@ impl RinkWidget for &Node {
                 if let Some(c) = self.state.style.core.bg {
                 if let Some(c) = self.state.style.core.bg {
                     new_cell.bg = c;
                     new_cell.bg = c;
                 }
                 }
+                if self.state.focused {
+                    new_cell.bg.alpha = 100;
+                    new_cell.bg.color = new_cell.bg.blend(Color::White);
+                }
                 buf.set(x, y, new_cell);
                 buf.set(x, y, new_cell);
             }
             }
         }
         }

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

@@ -57,7 +57,7 @@ impl<T: RinkWidget> WidgetWithContext<T> {
 
 
 impl<T: RinkWidget> Widget for WidgetWithContext<T> {
 impl<T: RinkWidget> Widget for WidgetWithContext<T> {
     fn render(self, area: Rect, buf: &mut Buffer) {
     fn render(self, area: Rect, buf: &mut Buffer) {
-        self.widget.render(area, RinkBuffer::new(buf, self.config))
+        self.widget.render(area, RinkBuffer::new(buf, self.config));
     }
     }
 }
 }