11 files changed
+1199
-77
No files matched your search
@@ -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,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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, .. }));
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
* [ ] Support for events, hooks, and callbacks<sup>1</sup>
|
||||||
* [ ] Html tags<sup>1</sup>
|
* [ ] Html tags<sup>2</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.
|
|
||||||
|
|
||||||
|
|
||||||
|
<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.
|
||||||
@@ -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
@@ -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 node_layout = get_abs_layout(node, dom, layout);
|
||||||
let currently_contains = layout_contains_point(node_layout, new_pos);
|
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 node_layout = get_abs_layout(node, dom, layout);
|
||||||
let currently_contains = layout_contains_point(node_layout, new_pos);
|
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 node_layout = get_abs_layout(node, dom, layout);
|
||||||
let currently_contains = layout_contains_point(node_layout, new_pos);
|
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 node_layout = get_abs_layout(node, dom, layout);
|
||||||
let currently_contains = layout_contains_point(node_layout, new_pos);
|
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 =
|
let node_layout = get_abs_layout(node, dom, layout);
|
||||||
layout.layout(node.state.layout.node.unwrap()).unwrap();
|
|
||||||
let currently_contains = layout_contains_point(node_layout, new_pos);
|
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 {
|
if node.state.focused {
|
||||||
scope_id: None,
|
resolved_events.push(UserEvent {
|
||||||
priority: EventPriority::Medium,
|
scope_id: None,
|
||||||
name: event,
|
priority: EventPriority::Medium,
|
||||||
element: Some(node.id),
|
name: event,
|
||||||
data: data.clone(),
|
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
@@ -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::real_dom::RealDom;
|
||||||
use dioxus_native_core_macro::State;
|
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::*;
|
||||||
|
pub(crate) use node::*;
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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 {
|
if !to_rerender.is_empty() || updated {
|
||||||
resized = false;
|
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
|
||||||
|
|||||||
@@ -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,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user