Explicit focus order is now fully supported

This commit is contained in:
Jonathan Johnson 2023-11-14 14:12:12 -08:00
parent 89c8805924
commit eb063c82f0
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
5 changed files with 168 additions and 36 deletions

84
examples/focus-order.rs Normal file
View file

@ -0,0 +1,84 @@
use std::process::exit;
use gooey::value::{Dynamic, MapEach, StringValue};
use gooey::widget::{MakeWidget, MakeWidgetWithId, WidgetTag};
use gooey::widgets::Expand;
use gooey::Run;
use kludgine::figures::units::Lp;
/// This example is the same as login, but it has an explicit tab order to
/// change from the default order (username, password, cancel, log in) to
/// username, password, log in, cancel.
fn main() -> gooey::Result {
let username = Dynamic::default();
let password = Dynamic::default();
let valid =
(&username, &password).map_each(|(username, password)| validate(username, password));
let (login_tag, login_id) = WidgetTag::new();
let (cancel_tag, cancel_id) = WidgetTag::new();
let (username_tag, username_id) = WidgetTag::new();
// TODO this should be a grid layout to ensure proper visual alignment.
let username_row = "Username"
.and(
username
.clone()
.into_input()
.make_with_id(username_tag)
.expand(),
)
.into_columns();
let password_row = "Password"
.and(
// TODO secure input
password
.clone()
.into_input()
.with_next_focus(login_id)
.expand(),
)
.into_columns();
let buttons = "Cancel"
.into_button()
.on_click(|_| {
eprintln!("Login cancelled");
exit(0)
})
.make_with_id(cancel_tag)
.into_escape()
.with_next_focus(username_id)
.and(Expand::empty())
.and(
"Log In"
.into_button()
.enabled(valid)
.on_click(move |_| {
println!("Welcome, {}", username.get());
exit(0);
})
.make_with_id(login_tag)
.into_default()
.with_next_focus(cancel_id),
)
.into_columns();
username_row
.pad()
.and(password_row.pad())
.and(buttons.pad())
.into_rows()
.contain()
.width(Lp::points(300)..Lp::points(600))
.scroll()
.centered()
.expand()
.run()
}
fn validate(username: &String, password: &String) -> bool {
!username.is_empty() && !password.is_empty()
}

View file

@ -15,8 +15,8 @@ use kludgine::shapes::{Shape, StrokeOptions};
use kludgine::{Color, Kludgine};
use crate::graphics::Graphics;
use crate::styles::components::{HighlightColor, WidgetBackground};
use crate::styles::{ComponentDefinition, Styles, Theme, ThemePair, VisualOrder};
use crate::styles::components::{HighlightColor, LayoutOrder, WidgetBackground};
use crate::styles::{ComponentDefinition, Styles, Theme, ThemePair};
use crate::utils::IgnorePoison;
use crate::value::{Dynamic, IntoValue, Value};
use crate::widget::{EventHandling, ManagedWidget, WidgetId, WidgetInstance, WidgetRef};
@ -235,10 +235,12 @@ impl<'context, 'window> EventContext<'context, 'window> {
.accept_focus(&mut self.for_other(&focus))
{
break Some(focus);
} else if let Some(next_focus) = focus.next_focus() {
} else if let Some(next_focus) =
focus.explicit_focus_target(self.pending_state.focus_is_advancing)
{
focus = next_focus;
} else {
break self.next_focus_after(focus, VisualOrder::left_to_right());
break self.next_focus_after(focus, self.pending_state.focus_is_advancing);
}
});
let new = match self
@ -272,17 +274,17 @@ impl<'context, 'window> EventContext<'context, 'window> {
fn next_focus_after(
&mut self,
mut focus: ManagedWidget,
order: VisualOrder,
advance: bool,
) -> Option<ManagedWidget> {
// First, look within the current focus for any focusable children.
let stop_at = focus.id();
if let Some(focus) = self.next_focus_within(&focus, None, stop_at, order) {
if let Some(focus) = self.next_focus_within(&focus, None, stop_at, advance) {
return Some(focus);
}
// Now, look for the next widget in each hierarchy
let root = loop {
if let Some(focus) = self.next_focus_sibling(&focus, stop_at, order) {
if let Some(focus) = self.next_focus_sibling(&focus, stop_at, advance) {
return Some(focus);
}
let Some(parent) = focus.parent() else {
@ -293,16 +295,16 @@ impl<'context, 'window> EventContext<'context, 'window> {
// We've exhausted a forward scan, we can now start searching the final
// parent, which is the root.
self.next_focus_within(&root, None, stop_at, order)
self.next_focus_within(&root, None, stop_at, advance)
}
fn next_focus_sibling(
&mut self,
focus: &ManagedWidget,
stop_at: WidgetId,
order: VisualOrder,
advance: bool,
) -> Option<ManagedWidget> {
self.next_focus_within(&focus.parent()?, Some(focus.id()), stop_at, order)
self.next_focus_within(&focus.parent()?, Some(focus.id()), stop_at, advance)
}
/// Searches for the next focus inside of `focus`, returning `None` if
@ -313,10 +315,14 @@ impl<'context, 'window> EventContext<'context, 'window> {
focus: &ManagedWidget,
start_at: Option<WidgetId>,
stop_at: WidgetId,
order: VisualOrder,
advance: bool,
) -> Option<ManagedWidget> {
let mut visual_order = self.get(&LayoutOrder);
if !advance {
visual_order = visual_order.rev();
}
let mut children = focus
.visually_ordered_children(order)
.visually_ordered_children(visual_order)
.into_iter()
.peekable();
if let Some(start_at) = start_at {
@ -340,7 +346,9 @@ impl<'context, 'window> EventContext<'context, 'window> {
.accept_focus(&mut self.for_other(&child))
{
return Some(child);
} else if let Some(focus) = self.next_focus_within(&child, None, stop_at, order) {
} else if let Some(next_focus) = self.widget().explicit_focus_target(advance) {
return Some(next_focus);
} else if let Some(focus) = self.next_focus_within(&child, None, stop_at, advance) {
return Some(focus);
}
}
@ -348,14 +356,31 @@ impl<'context, 'window> EventContext<'context, 'window> {
None
}
/// Advances the focus from this widget to the next widget in `direction`.
/// Advances the focus to the next widget after this widget in the
/// configured focus order.
///
/// This widget does not need to be focused.
pub fn advance_focus(&mut self, direction: VisualOrder) {
// TODO check to see if the current node has an explicit next_focus (or
// if we're going in the opposite direction, previous_focus).
/// To focus in the reverse order, use [`EventContext::return_focus()`].
pub fn advance_focus(&mut self) {
self.move_focus(true);
}
self.pending_state.focus = self.next_focus_after(self.current_node.clone(), direction);
/// Returns the focus to the previous widget before this widget in the
/// configured fous order.
///
/// To focus in the forward order, use [`EventContext::advance_focus()`].
pub fn return_focus(&mut self) {
self.move_focus(false);
}
fn move_focus(&mut self, advance: bool) {
if let Some(explicit_next_focus) = self.current_node.explicit_focus_target(advance) {
self.for_other(&explicit_next_focus).focus();
} else {
self.pending_state.focus = self.next_focus_after(self.current_node.clone(), advance);
}
// It is important to set focus-is_advancing after `focus()` because it
// sets it to `true` explicitly.
self.pending_state.focus_is_advancing = advance;
}
}
@ -702,6 +727,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
.tree
.active_widget()
.and_then(|id| current_node.tree.widget_from_node(id)),
focus_is_advancing: false,
}),
effective_styles: current_node.effective_styles(),
current_node,
@ -783,6 +809,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
/// Widget events relating to focus changes are deferred until after the all
/// contexts for the currently firing event are dropped.
pub fn focus(&mut self) {
self.pending_state.focus_is_advancing = true;
self.pending_state.focus = Some(self.current_node.clone());
}
@ -1045,6 +1072,7 @@ enum PendingState<'a> {
#[derive(Default)]
struct PendingWidgetState {
focus_is_advancing: bool,
focus: Option<ManagedWidget>,
active: Option<ManagedWidget>,
}

View file

@ -56,12 +56,8 @@ impl Tree {
let parent = &mut data.nodes[parent];
parent.children.push(node_id);
}
if let Some(next_focus) = widget
.next_focus()
.and_then(|id| data.nodes_by_id.get(&id))
.copied()
{
data.previous_focuses.insert(next_focus, node_id);
if let Some(next_focus) = widget.next_focus() {
data.previous_focuses.insert(next_focus, id);
}
ManagedWidget {
node_id,
@ -252,6 +248,12 @@ impl Tree {
data.update_tracked_widget(new_focus, self, |data| &mut data.focus)
}
pub fn previous_focus(&self, focus: WidgetId) -> Option<ManagedWidget> {
let data = self.data.lock().ignore_poison();
let previous = *data.previous_focuses.get(&focus)?;
data.widget_from_id(previous, self)
}
pub fn activate(
&self,
new_active: Option<&ManagedWidget>,
@ -372,7 +374,7 @@ struct TreeData {
defaults: Vec<LotId>,
escapes: Vec<LotId>,
render_order: Vec<LotId>,
previous_focuses: AHashMap<LotId, LotId>,
previous_focuses: AHashMap<WidgetId, WidgetId>,
}
impl TreeData {
@ -435,17 +437,16 @@ impl TreeData {
parent.children.remove(index);
let mut detached_nodes = removed_node.children;
if let Some(next_focus) = removed_node
.widget
.next_focus()
.and_then(|id| self.nodes_by_id.get(&id))
{
self.previous_focuses.remove(next_focus);
if let Some(next_focus) = removed_node.widget.next_focus() {
self.previous_focuses.remove(&next_focus);
}
while let Some(node) = detached_nodes.pop() {
let mut node = self.nodes.remove(node).expect("detached node missing");
self.nodes_by_id.remove(&node.widget.id());
if let Some(next_focus) = node.widget.next_focus() {
self.previous_focuses.remove(&next_focus);
}
detached_nodes.append(&mut node.children);
}
}

View file

@ -1081,6 +1081,26 @@ impl ManagedWidget {
.and_then(|next_focus| self.tree.widget(next_focus))
}
/// Returns the widget to focus before this widget.
///
/// There is no direct way to set this value. This relationship is created
/// automatically using [`MakeWidget::with_next_focus()`].
#[must_use]
pub fn previous_focus(&self) -> Option<ManagedWidget> {
self.tree.previous_focus(self.id())
}
/// Returns the next or previous focus target, if one was set using
/// [`MakeWidget::with_next_focus()`].
#[must_use]
pub fn explicit_focus_target(&self, advance: bool) -> Option<ManagedWidget> {
if advance {
self.next_focus()
} else {
self.previous_focus()
}
}
/// Returns the region that the widget was last rendered at.
#[must_use]
pub fn last_layout(&self) -> Option<Rect<Px>> {

View file

@ -31,7 +31,6 @@ use crate::context::{
WidgetContext,
};
use crate::graphics::Graphics;
use crate::styles::components::LayoutOrder;
use crate::styles::ThemePair;
use crate::tree::Tree;
use crate::utils::ModifiersExt;
@ -736,11 +735,11 @@ where
),
kludgine,
);
let mut visual_order = target.get(&LayoutOrder);
if reverse {
visual_order = visual_order.rev();
target.return_focus();
} else {
target.advance_focus();
}
target.advance_focus(visual_order);
}
}
Key::Enter => {