Skip to content

Commit

Permalink
Add arrow key navigation
Browse files Browse the repository at this point in the history
  • Loading branch information
Zoxc committed Nov 7, 2023
1 parent afc5f5f commit 45e6556
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 25 deletions.
26 changes: 15 additions & 11 deletions examples/widget-gallery/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,24 @@ fn app_view() -> impl View {
.on_event(EventListener::KeyDown, move |e| {
if let Event::KeyDown(key_event) = e {
let active = active_tab.get();
match key_event.key.logical_key {
Key::Named(NamedKey::ArrowUp) => {
if active > 0 {
set_active_tab.update(|v| *v -= 1)
if key_event.modifiers.is_empty() {
match key_event.key.logical_key {
Key::Named(NamedKey::ArrowUp) => {
if active > 0 {
set_active_tab.update(|v| *v -= 1)
}
true
}
true
}
Key::Named(NamedKey::ArrowDown) => {
if active < tabs.get().len() - 1 {
set_active_tab.update(|v| *v += 1)
Key::Named(NamedKey::ArrowDown) => {
if active < tabs.get().len() - 1 {
set_active_tab.update(|v| *v += 1)
}
true
}
true
_ => false,
}
_ => false,
} else {
false
}
} else {
false
Expand Down
11 changes: 11 additions & 0 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,17 @@ impl AppState {
false
}

pub(crate) fn can_focus(&self, id: Id) -> bool {
self.keyboard_navigable.contains(&id)
&& !self.is_disabled(&id)
&& !id
.id_path()
.unwrap()
.dispatch()
.iter()
.any(|id| self.is_hidden(*id))
}

Check warning on line 366 in src/context.rs

View check run for this annotation

Codecov / codecov/patch

src/context.rs#L357-L366

Added lines #L357 - L366 were not covered by tests

pub fn is_hovered(&self, id: &Id) -> bool {
self.hovered.contains(id)
}
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ pub mod id;
mod inspector;
pub mod keyboard;
pub mod menu;
mod nav;
pub mod pointer;
pub mod renderer;
pub mod responsive;
Expand Down
77 changes: 77 additions & 0 deletions src/nav.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use kurbo::{Point, Rect};
use winit::keyboard::NamedKey;

use crate::{
context::AppState,
id::Id,
view::{view_tab_navigation, View},
};

pub(crate) fn view_arrow_navigation(key: NamedKey, app_state: &mut AppState, view: &dyn View) {
let focused = match app_state.focus {
Some(id) => id,

Check warning on line 12 in src/nav.rs

View check run for this annotation

Codecov / codecov/patch

src/nav.rs#L10-L12

Added lines #L10 - L12 were not covered by tests
None => {
view_tab_navigation(
view,
app_state,
matches!(key, NamedKey::ArrowUp | NamedKey::ArrowLeft),

Check warning on line 17 in src/nav.rs

View check run for this annotation

Codecov / codecov/patch

src/nav.rs#L15-L17

Added lines #L15 - L17 were not covered by tests
);
return;

Check warning on line 19 in src/nav.rs

View check run for this annotation

Codecov / codecov/patch

src/nav.rs#L19

Added line #L19 was not covered by tests
}
};
let rect = app_state.get_layout_rect(focused).inflate(10.0, 10.0);
let center = rect.center();
let intersect_target = match key {
NamedKey::ArrowUp => Rect::new(rect.x0, f64::NEG_INFINITY, rect.x1, center.y),
NamedKey::ArrowDown => Rect::new(rect.x0, center.y, rect.x1, f64::INFINITY),
NamedKey::ArrowLeft => Rect::new(f64::NEG_INFINITY, rect.y0, center.x, rect.y1),
NamedKey::ArrowRight => Rect::new(center.x, rect.y0, f64::INFINITY, rect.y1),
_ => panic!(),

Check warning on line 29 in src/nav.rs

View check run for this annotation

Codecov / codecov/patch

src/nav.rs#L22-L29

Added lines #L22 - L29 were not covered by tests
};
let center_target = match key {

Check warning on line 31 in src/nav.rs

View check run for this annotation

Codecov / codecov/patch

src/nav.rs#L31

Added line #L31 was not covered by tests
NamedKey::ArrowUp => {
Rect::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::INFINITY, rect.y0)

Check warning on line 33 in src/nav.rs

View check run for this annotation

Codecov / codecov/patch

src/nav.rs#L33

Added line #L33 was not covered by tests
}
NamedKey::ArrowDown => Rect::new(f64::NEG_INFINITY, rect.y1, f64::INFINITY, f64::INFINITY),

Check warning on line 35 in src/nav.rs

View check run for this annotation

Codecov / codecov/patch

src/nav.rs#L35

Added line #L35 was not covered by tests
NamedKey::ArrowLeft => {
Rect::new(f64::NEG_INFINITY, f64::NEG_INFINITY, rect.x0, f64::INFINITY)

Check warning on line 37 in src/nav.rs

View check run for this annotation

Codecov / codecov/patch

src/nav.rs#L37

Added line #L37 was not covered by tests
}
NamedKey::ArrowRight => Rect::new(rect.x1, f64::NEG_INFINITY, f64::INFINITY, f64::INFINITY),
_ => panic!(),

Check warning on line 40 in src/nav.rs

View check run for this annotation

Codecov / codecov/patch

src/nav.rs#L39-L40

Added lines #L39 - L40 were not covered by tests
};
let mut keyboard_navigable: Vec<Id> = app_state.keyboard_navigable.iter().copied().collect();
keyboard_navigable.retain(|id| {
let layout = app_state.get_layout_rect(*id);

!layout.intersect(intersect_target).is_empty()
&& center_target.contains(layout.center())
&& app_state.can_focus(*id)
&& *id != focused
});

let mut new_focus = None;
for id in keyboard_navigable {
let id_rect = app_state.get_layout_rect(id);
let id_center = id_rect.center();
let id_edge = match key {
NamedKey::ArrowUp => Point::new(id_center.x, id_rect.y1),
NamedKey::ArrowDown => Point::new(id_center.x, id_rect.y0),
NamedKey::ArrowLeft => Point::new(id_rect.x1, id_center.y),
NamedKey::ArrowRight => Point::new(id_rect.x0, id_center.y),
_ => panic!(),

Check warning on line 61 in src/nav.rs

View check run for this annotation

Codecov / codecov/patch

src/nav.rs#L42-L61

Added lines #L42 - L61 were not covered by tests
};
let id_distance = center.distance_squared(id_edge);
if let Some((_, distance)) = new_focus {
if id_distance < distance {
new_focus = Some((id, id_distance));
}
} else {
new_focus = Some((id, id_distance));
}

Check warning on line 70 in src/nav.rs

View check run for this annotation

Codecov / codecov/patch

src/nav.rs#L63-L70

Added lines #L63 - L70 were not covered by tests
}

if let Some((id, _)) = new_focus {
app_state.clear_focus();
app_state.update_focus(id, true);
}
}

Check warning on line 77 in src/nav.rs

View check run for this annotation

Codecov / codecov/patch

src/nav.rs#L73-L77

Added lines #L73 - L77 were not covered by tests
14 changes: 1 addition & 13 deletions src/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,20 +396,8 @@ pub(crate) fn view_tab_navigation(root_view: &dyn View, app_state: &mut AppState
}
};

let hidden = |app_state: &mut AppState, id: Id| {
id.id_path()
.unwrap()
.dispatch()
.iter()
.any(|id| app_state.is_hidden(*id))
};

let mut new_focus = tree_iter(start);
while new_focus != start
&& (!app_state.keyboard_navigable.contains(&new_focus)
|| app_state.is_disabled(&new_focus)
|| hidden(app_state, new_focus))
{
while new_focus != start && !app_state.can_focus(new_focus) {

Check warning on line 400 in src/view.rs

View check run for this annotation

Codecov / codecov/patch

src/view.rs#L400

Added line #L400 was not covered by tests
new_focus = tree_iter(new_focus);
}

Expand Down
15 changes: 14 additions & 1 deletion src/window_handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use crate::{
inspector::{self, Capture, CaptureState, CapturedView},
keyboard::KeyEvent,
menu::Menu,
nav::view_arrow_navigation,
pointer::{PointerButton, PointerInputEvent, PointerMoveEvent, PointerWheelEvent},
style::{CursorStyle, StyleSelector},
update::{
Expand Down Expand Up @@ -189,7 +190,9 @@ impl WindowHandle {

if !processed {
if let Event::KeyDown(KeyEvent { key, modifiers }) = &event {
if key.logical_key == Key::Named(NamedKey::Tab) {
if key.logical_key == Key::Named(NamedKey::Tab)
&& (modifiers.is_empty() || *modifiers == ModifiersState::SHIFT)
{

Check warning on line 195 in src/window_handle.rs

View check run for this annotation

Codecov / codecov/patch

src/window_handle.rs#L193-L195

Added lines #L193 - L195 were not covered by tests
let backwards = modifiers.contains(ModifiersState::SHIFT);
view_tab_navigation(&self.view, cx.app_state, backwards);
// view_debug_tree(&self.view);
Expand All @@ -198,6 +201,16 @@ impl WindowHandle {
if character.eq_ignore_ascii_case("i") {
// view_debug_tree(&self.view);
}
} else if *modifiers == ModifiersState::ALT {

Check warning on line 204 in src/window_handle.rs

View check run for this annotation

Codecov / codecov/patch

src/window_handle.rs#L204

Added line #L204 was not covered by tests
if let Key::Named(
name @ (NamedKey::ArrowUp

Check warning on line 206 in src/window_handle.rs

View check run for this annotation

Codecov / codecov/patch

src/window_handle.rs#L206

Added line #L206 was not covered by tests
| NamedKey::ArrowDown
| NamedKey::ArrowLeft
| NamedKey::ArrowRight),
) = key.logical_key
{
view_arrow_navigation(name, cx.app_state, &self.view);
}

Check warning on line 213 in src/window_handle.rs

View check run for this annotation

Codecov / codecov/patch

src/window_handle.rs#L210-L213

Added lines #L210 - L213 were not covered by tests
}
}

Expand Down

0 comments on commit 45e6556

Please sign in to comment.