diff --git a/examples/tui_buttons.rs b/examples/tui_buttons.rs index 3581a0d7c..39c458919 100644 --- a/examples/tui_buttons.rs +++ b/examples/tui_buttons.rs @@ -20,6 +20,7 @@ fn Button(cx: Scope) -> Element { justify_content: "center", align_items: "center", onkeydown: |_| state.modify(|s| !s), + onclick: |_| state.modify(|s| !s), "{text}" } diff --git a/packages/tui/src/focus.rs b/packages/tui/src/focus.rs index cc89e9660..3590075dd 100644 --- a/packages/tui/src/focus.rs +++ b/packages/tui/src/focus.rs @@ -2,6 +2,7 @@ use crate::Dom; use dioxus_core::ElementId; use dioxus_native_core::utils::{ElementProduced, PersistantElementIter}; +use dioxus_native_core_macro::sorted_str_slice; use std::num::NonZeroU16; @@ -100,9 +101,8 @@ impl NodeDepState for Focus { } } -// must be sorted -const FOCUS_EVENTS: &[&str] = &["keydown", "keypress", "keyup"]; -const FOCUS_ATTRIBUTES: &[&str] = &["dioxus-prevent-default", "tabindex"]; +const FOCUS_EVENTS: &[&str] = &sorted_str_slice!(["keydown", "keypress", "keyup"]); +const FOCUS_ATTRIBUTES: &[&str] = &sorted_str_slice!(["dioxus-prevent-default", "tabindex"]); #[derive(Default)] pub(crate) struct FocusState { @@ -123,6 +123,7 @@ impl FocusState { let focus_level = &mut self.focus_level; let mut next_focus = None; let starting_focus_level = *focus_level; + let mut focus_level_changed = false; loop { let new = if forward { @@ -131,22 +132,21 @@ impl FocusState { 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 { + let node_level = n.state.focus.level; + if node_level != *focus_level && node_level.focusable() { + if node_level > *focus_level { if let Some(level) = &mut closest_level { - if current_level < *level { - *level = current_level; + if node_level < *level { + *level = node_level; } } else { - closest_level = Some(current_level); + closest_level = Some(node_level); } } } @@ -154,15 +154,15 @@ impl FocusState { } 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 { + let node_level = n.state.focus.level; + if node_level != *focus_level && node_level.focusable() { + if node_level < *focus_level { if let Some(level) = &mut closest_level { - if current_level > *level { - *level = current_level; + if node_level > *level { + *level = node_level; } } else { - closest_level = Some(current_level); + closest_level = Some(node_level); } } } @@ -174,6 +174,9 @@ impl FocusState { if let Some(level) = closest_level { *focus_level = level; + if *focus_level != starting_focus_level { + focus_level_changed = true; + } } else { if forward { *focus_level = FocusLevel::Unfocusable; @@ -183,7 +186,7 @@ impl FocusState { } // if the focus level looped, we are done - if *focus_level == starting_focus_level { + if *focus_level == starting_focus_level && focus_level_changed { break; } } @@ -197,6 +200,7 @@ impl FocusState { 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 { diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index d36682750..e045a8f25 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -14,6 +14,7 @@ use std::{ }; use stretch2::{prelude::Layout, Stretch}; +use crate::FocusState; use crate::{Dom, Node}; // a wrapper around the input state for easier access @@ -78,6 +79,7 @@ pub struct InnerInputState { wheel: Option, last_key_pressed: Option<(KeyboardData, Instant)>, screen: Option<(u16, u16)>, + focus_state: FocusState, // subscribers: Vec>, } @@ -89,6 +91,7 @@ impl InnerInputState { last_key_pressed: None, screen: None, // subscribers: Vec::new(), + focus_state: FocusState::default(), } } @@ -96,54 +99,56 @@ impl InnerInputState { fn apply_event(&mut self, evt: &mut EventCore) { match evt.1 { // limitations: only two buttons may be held at once - EventData::Mouse(ref mut m) => match &mut self.mouse { - Some(state) => { - let mut buttons = state.0.buttons; - state.0 = m.clone(); - match evt.0 { - // this code only runs when there are no buttons down - "mouseup" => { - buttons = 0; - state.1 = Vec::new(); - } - "mousedown" => { - if state.1.contains(&m.buttons) { - // if we already pressed a button and there is another button released the button crossterm sends is the button remaining - if state.1.len() > 1 { - evt.0 = "mouseup"; - state.1 = vec![m.buttons]; - } - // otherwise some other button was pressed. In testing it was consistantly this mapping - else { - match m.buttons { - 0x01 => state.1.push(0x02), - 0x02 => state.1.push(0x01), - 0x04 => state.1.push(0x01), - _ => (), - } - } - } else { - state.1.push(m.buttons); + EventData::Mouse(ref mut m) => { + match &mut self.mouse { + Some(state) => { + let mut buttons = state.0.buttons; + state.0 = m.clone(); + match evt.0 { + // this code only runs when there are no buttons down + "mouseup" => { + buttons = 0; + state.1 = Vec::new(); } + "mousedown" => { + if state.1.contains(&m.buttons) { + // if we already pressed a button and there is another button released the button crossterm sends is the button remaining + if state.1.len() > 1 { + evt.0 = "mouseup"; + state.1 = vec![m.buttons]; + } + // otherwise some other button was pressed. In testing it was consistantly this mapping + else { + match m.buttons { + 0x01 => state.1.push(0x02), + 0x02 => state.1.push(0x01), + 0x04 => state.1.push(0x01), + _ => (), + } + } + } else { + state.1.push(m.buttons); + } - buttons = state.1.iter().copied().reduce(|a, b| a | b).unwrap(); + buttons = state.1.iter().copied().reduce(|a, b| a | b).unwrap(); + } + _ => (), } - _ => (), + state.0.buttons = buttons; + m.buttons = buttons; + } + None => { + self.mouse = Some(( + m.clone(), + if m.buttons == 0 { + Vec::new() + } else { + vec![m.buttons] + }, + )); } - state.0.buttons = buttons; - m.buttons = buttons; } - None => { - self.mouse = Some(( - m.clone(), - if m.buttons == 0 { - Vec::new() - } else { - vec![m.buttons] - }, - )); - } - }, + } EventData::Wheel(ref w) => self.wheel = Some(w.clone()), EventData::Screen(ref s) => self.screen = Some(*s), EventData::Keyboard(ref mut k) => { @@ -161,14 +166,27 @@ impl InnerInputState { fn update( &mut self, - evts: &mut [EventCore], + evts: &mut Vec, resolved_events: &mut Vec, layout: &Stretch, dom: &mut Dom, - ) { + ) -> bool { let previous_mouse = self.mouse.as_ref().map(|m| (m.0.clone(), m.1.clone())); self.wheel = None; + let mut force_redraw = false; + + evts.retain(|e| match &e.1 { + EventData::Keyboard(k) => match k.key_code { + KeyCode::Tab => { + let focus_event = self.focus_state.progress(dom, !k.shift_key); + force_redraw |= focus_event; + !focus_event + } + _ => true, + }, + _ => true, + }); for e in evts.iter_mut() { self.apply_event(e); @@ -179,6 +197,7 @@ impl InnerInputState { // for s in &self.subscribers { // s(); // } + force_redraw } fn resolve_mouse_events( @@ -517,10 +536,15 @@ impl RinkInputHandler { ) } - pub(crate) fn get_events(&self, layout: &Stretch, dom: &mut Dom) -> Vec { + pub(crate) fn prune(&self, mutations: &dioxus_core::Mutations, rdom: &Dom) { + self.state.borrow_mut().focus_state.prune(mutations, rdom); + } + + // returns a list of events and if a event will force a rerender + pub(crate) fn get_events(&self, layout: &Stretch, dom: &mut Dom) -> (Vec, bool) { let mut resolved_events = Vec::new(); - (*self.state).borrow_mut().update( + let rerender = (*self.state).borrow_mut().update( &mut (*self.queued_events).borrow_mut(), &mut resolved_events, layout, @@ -574,7 +598,7 @@ impl RinkInputHandler { } } - resolved_events + (resolved_events, rerender) } } @@ -759,7 +783,7 @@ fn translate_key_event(event: crossterm::event::KeyEvent) -> Option { 12 => KeyCode::F12, _ => return None, }, - TermKeyCode::BackTab => return None, + TermKeyCode::BackTab => KeyCode::Tab, TermKeyCode::Null => return None, _ => return None, }; diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index d8dec50e9..cb1628206 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -139,8 +139,6 @@ fn render_vdom( let to_rerender: fxhash::FxHashSet = vec![0].into_iter().collect(); let mut updated = true; - let mut focus_state = FocusState::default(); - loop { /* -> render the nodes in the right place with tui/crossterm @@ -201,8 +199,6 @@ fn render_vdom( // } Either::Right((evt, _o)) => { - let mut evt_intersepted = false; - match evt.as_ref().unwrap() { InputEvent::UserInput(event) => match event { TermEvent::Key(key) => { @@ -212,13 +208,6 @@ fn render_vdom( { break; } - if let KeyCode::BackTab = key.code { - evt_intersepted = - focus_state.progress(&mut rdom, false); - } - if let KeyCode::Tab = key.code { - evt_intersepted = focus_state.progress(&mut rdom, true); - } } TermEvent::Resize(_, _) => updated = true, TermEvent::Mouse(_) => {} @@ -226,23 +215,22 @@ fn render_vdom( InputEvent::Close => break, }; - if !evt_intersepted { - if let InputEvent::UserInput(evt) = evt.unwrap() { - register_event(evt); - } + if let InputEvent::UserInput(evt) = evt.unwrap() { + register_event(evt); } } } } { - let evts = handler.get_events(&stretch.borrow(), &mut rdom); + let (evts, rerender) = handler.get_events(&stretch.borrow(), &mut rdom); + updated |= rerender; for e in evts { vdom.handle_message(SchedulerMsg::Event(e)); } let mutations = vdom.work_with_deadline(|| false); for m in &mutations { - focus_state.prune(m, &rdom); + handler.prune(m, &rdom); } // updates the dom's nodes let to_update = rdom.apply_mutations(mutations);