add focus state
This commit is contained in:
1 parent
35ee243d0d
commit
ce5ade9fa6
2 files changed
+279
-35
No files matched your search
@@ -0,0 +1,268 @@
|
||||
use crate::Dom;
|
||||
|
||||
use dioxus_core::ElementId;
|
||||
use dioxus_native_core::utils::{ElementProduced, PersistantElementIter};
|
||||
|
||||
use std::num::NonZeroU16;
|
||||
|
||||
use dioxus_native_core::{
|
||||
node_ref::{AttributeMask, NodeMask, NodeView},
|
||||
real_dom::NodeType,
|
||||
state::NodeDepState,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Ord)]
|
||||
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 Default for FocusLevel {
|
||||
fn default() -> Self {
|
||||
FocusLevel::Unfocusable
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Default)]
|
||||
pub(crate) struct Focus {
|
||||
pub pass_focus: bool,
|
||||
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 {
|
||||
pass_focus: !node
|
||||
.attributes()
|
||||
.any(|a| a.name == "dioxus-prevent-default" && a.value.trim() == "true"),
|
||||
level: if let Some(a) = node.attributes().find(|a| a.name == "tabindex") {
|
||||
if let Ok(index) = a.value.parse::<i32>() {
|
||||
if index < 0 {
|
||||
FocusLevel::Unfocusable
|
||||
} else if index == 0 {
|
||||
FocusLevel::Focusable
|
||||
} else {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// must be sorted
|
||||
const FOCUS_EVENTS: &[&str] = &["keydown", "keypress", "keyup"];
|
||||
const FOCUS_ATTRIBUTES: &[&str] = &["dioxus-prevent-default", "tabindex"];
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct FocusState {
|
||||
pub(crate) focus_iter: PersistantElementIter,
|
||||
pub(crate) last_focused_id: Option<ElementId>,
|
||||
pub(crate) focus_level: FocusLevel,
|
||||
}
|
||||
|
||||
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.focus.pass_focus {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
let mut loop_marker_id = self.last_focused_id;
|
||||
let focus_level = &mut self.focus_level;
|
||||
let mut next_focus = None;
|
||||
let starting_focus_level = *focus_level;
|
||||
|
||||
loop {
|
||||
let new = if forward {
|
||||
self.focus_iter.next(&rdom)
|
||||
} else {
|
||||
self.focus_iter.prev(&rdom)
|
||||
};
|
||||
let new_id = new.id();
|
||||
let current_level = rdom[new_id].state.focus.level;
|
||||
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 current_level = n.state.focus.level;
|
||||
if current_level != *focus_level {
|
||||
if current_level > *focus_level {
|
||||
if let Some(level) = &mut closest_level {
|
||||
if current_level < *level {
|
||||
*level = current_level;
|
||||
}
|
||||
} else {
|
||||
closest_level = Some(current_level);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// find the closest focusable element before the current level
|
||||
rdom.traverse_depth_first(|n| {
|
||||
let current_level = n.state.focus.level;
|
||||
if current_level != *focus_level {
|
||||
if current_level < *focus_level {
|
||||
if let Some(level) = &mut closest_level {
|
||||
if current_level > *level {
|
||||
*level = current_level;
|
||||
}
|
||||
} else {
|
||||
closest_level = Some(current_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;
|
||||
}
|
||||
}
|
||||
|
||||
// if the focus level looped, we are done
|
||||
if *focus_level == starting_focus_level {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 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 after_previous_focused = if forward {
|
||||
current_level >= *focus_level
|
||||
} else {
|
||||
current_level <= *focus_level
|
||||
};
|
||||
if after_previous_focused && current_level.focusable() {
|
||||
if current_level == *focus_level {
|
||||
next_focus = Some((new_id, current_level));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((id, order)) = next_focus {
|
||||
if order.focusable() {
|
||||
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 if forward {
|
||||
self.focus_iter.next(&rdom).id()
|
||||
} else {
|
||||
self.focus_iter.prev(&rdom).id()
|
||||
} != id
|
||||
{}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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),
|
||||
),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
-35
@@ -7,10 +7,11 @@ use crossterm::{
|
||||
};
|
||||
use dioxus_core::exports::futures_channel::mpsc::unbounded;
|
||||
use dioxus_core::*;
|
||||
use dioxus_native_core::real_dom::{NodeType, RealDom};
|
||||
use dioxus_native_core::real_dom::RealDom;
|
||||
use dioxus_native_core::state::*;
|
||||
use dioxus_native_core::utils::PersistantElementIter;
|
||||
use dioxus_native_core_macro::State;
|
||||
use focus::Focus;
|
||||
use focus::FocusState;
|
||||
use futures::{
|
||||
channel::mpsc::{UnboundedReceiver, UnboundedSender},
|
||||
pin_mut, StreamExt,
|
||||
@@ -24,6 +25,7 @@ use style_attributes::StyleModifier;
|
||||
use tui::{backend::CrosstermBackend, layout::Rect, Terminal};
|
||||
|
||||
mod config;
|
||||
mod focus;
|
||||
mod hooks;
|
||||
mod layout;
|
||||
mod render;
|
||||
@@ -44,6 +46,8 @@ struct NodeState {
|
||||
// depends on attributes, the C component of it's parent and a u8 context
|
||||
#[parent_dep_state(style)]
|
||||
style: StyleModifier,
|
||||
#[node_dep_state()]
|
||||
focus: Focus,
|
||||
focused: bool,
|
||||
}
|
||||
|
||||
@@ -135,8 +139,7 @@ fn render_vdom(
|
||||
let to_rerender: fxhash::FxHashSet<usize> = vec![0].into_iter().collect();
|
||||
let mut updated = true;
|
||||
|
||||
let mut focus_iter = PersistantElementIter::new();
|
||||
let mut focus_id = ElementId(0);
|
||||
let mut focus_state = FocusState::default();
|
||||
|
||||
loop {
|
||||
/*
|
||||
@@ -210,38 +213,11 @@ fn render_vdom(
|
||||
break;
|
||||
}
|
||||
if let KeyCode::BackTab = key.code {
|
||||
let mut new_focused_id;
|
||||
loop {
|
||||
new_focused_id = focus_iter.prev(&rdom).id();
|
||||
if let NodeType::Element { .. } =
|
||||
&rdom[new_focused_id].node_type
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
rdom[focus_id].state.focused = false;
|
||||
focus_id = new_focused_id;
|
||||
rdom[focus_id].state.focused = true;
|
||||
evt_intersepted = true;
|
||||
updated = true;
|
||||
// println!("{:?}", focus_id);
|
||||
evt_intersepted =
|
||||
focus_state.progress(&mut rdom, false);
|
||||
}
|
||||
if let KeyCode::Tab = key.code {
|
||||
let mut new_focused_id;
|
||||
loop {
|
||||
new_focused_id = focus_iter.next(&rdom).id();
|
||||
if let NodeType::Element { .. } =
|
||||
&rdom[new_focused_id].node_type
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
rdom[focus_id].state.focused = false;
|
||||
focus_id = new_focused_id;
|
||||
rdom[focus_id].state.focused = true;
|
||||
evt_intersepted = true;
|
||||
updated = true;
|
||||
// println!("{:?}", focus_id);
|
||||
evt_intersepted = focus_state.progress(&mut rdom, true);
|
||||
}
|
||||
}
|
||||
TermEvent::Resize(_, _) => updated = true,
|
||||
@@ -266,7 +242,7 @@ fn render_vdom(
|
||||
}
|
||||
let mutations = vdom.work_with_deadline(|| false);
|
||||
for m in &mutations {
|
||||
focus_iter.prune(m, &rdom);
|
||||
focus_state.prune(m, &rdom);
|
||||
}
|
||||
// updates the dom's nodes
|
||||
let to_update = rdom.apply_mutations(mutations);
|
||||
|
||||
Reference in new issue
Block a user