use alloc::vec::Vec;
use crate::object::{ObjectFlags, ObjectNode, ObjectStates};
#[derive(Debug, Clone)]
pub struct FocusPolicy {
pub wrap: bool,
pub editing: bool,
}
impl Default for FocusPolicy {
fn default() -> Self {
Self {
wrap: true,
editing: false,
}
}
}
#[derive(Debug, Default, Clone)]
pub struct FocusGroup {
pub policy: FocusPolicy,
}
impl FocusGroup {
pub fn new() -> Self {
Self::default()
}
pub const fn policy(&self) -> &FocusPolicy {
&self.policy
}
pub fn policy_mut(&mut self) -> &mut FocusPolicy {
&mut self.policy
}
pub fn set_wrap(&mut self, wrap: bool) {
self.policy.wrap = wrap;
}
pub fn set_editing(&mut self, root: &mut ObjectNode, editing: bool) {
self.policy.editing = editing;
if let Some(path) = find_focused_path(root) {
let node = node_at_path_mut(root, &path);
node.set_state(ObjectStates::EDITED, editing);
}
}
pub fn focus_next(&self, root: &mut ObjectNode) -> bool {
focus_step(root, Direction::Next, self.policy.wrap)
}
pub fn focus_prev(&self, root: &mut ObjectNode) -> bool {
focus_step(root, Direction::Prev, self.policy.wrap)
}
pub fn focus_path(&self, root: &mut ObjectNode, path: &[usize]) -> bool {
{
let candidate = node_at_path(root, path);
match candidate {
None => return false,
Some(n) if !is_focus_eligible(n) => return false,
_ => {}
}
}
let old_path = find_focused_path(root);
move_focus(root, old_path.as_deref(), path);
true
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Direction {
Next,
Prev,
}
fn collect_eligible_paths(root: &ObjectNode) -> Vec<Vec<usize>> {
let mut out = Vec::new();
collect_eligible_recursive(root, &mut Vec::new(), &mut out, false);
out
}
fn collect_eligible_recursive(
node: &ObjectNode,
current_path: &mut Vec<usize>,
out: &mut Vec<Vec<usize>>,
ancestor_invisible: bool,
) {
let invisible = ancestor_invisible || node.is_hidden_or_detached_pub();
if invisible {
return;
}
if is_focus_eligible_local(node) {
out.push(current_path.clone());
}
for (i, child) in node.children().iter().enumerate() {
current_path.push(i);
collect_eligible_recursive(child, current_path, out, invisible);
current_path.pop();
}
}
fn is_focus_eligible_local(node: &ObjectNode) -> bool {
let flags = node.flags();
let states = node.states();
flags.contains(ObjectFlags::FOCUSABLE)
&& !flags.contains(ObjectFlags::HIDDEN)
&& !flags.contains(ObjectFlags::DISABLED)
&& !states.contains(ObjectStates::DISABLED)
&& !node.is_detached()
}
fn is_focus_eligible(node: &ObjectNode) -> bool {
is_focus_eligible_local(node)
}
fn find_focused_path(root: &ObjectNode) -> Option<Vec<usize>> {
find_focused_recursive(root, &mut Vec::new())
}
fn find_focused_recursive(node: &ObjectNode, path: &mut Vec<usize>) -> Option<Vec<usize>> {
if node.states().contains(ObjectStates::FOCUSED) {
return Some(path.clone());
}
for (i, child) in node.children().iter().enumerate() {
path.push(i);
if let Some(p) = find_focused_recursive(child, path) {
return Some(p);
}
path.pop();
}
None
}
fn node_at_path<'a>(root: &'a ObjectNode, path: &[usize]) -> Option<&'a ObjectNode> {
let mut current = root;
for &idx in path {
if idx >= current.children().len() {
return None;
}
current = ¤t.children()[idx];
}
Some(current)
}
fn node_at_path_mut<'a>(root: &'a mut ObjectNode, path: &[usize]) -> &'a mut ObjectNode {
let mut current = root;
for &idx in path {
current = &mut current.children_mut()[idx];
}
current
}
fn focus_step(root: &mut ObjectNode, dir: Direction, wrap: bool) -> bool {
let eligible = collect_eligible_paths(root);
if eligible.is_empty() {
return false;
}
let old_path = find_focused_path(root);
let current_idx = old_path
.as_ref()
.and_then(|op| eligible.iter().position(|ep| ep == op));
let next_idx = match (current_idx, dir) {
(None, Direction::Next) => Some(0),
(None, Direction::Prev) => Some(eligible.len() - 1),
(Some(i), Direction::Next) => {
if i + 1 < eligible.len() {
Some(i + 1)
} else if wrap {
Some(0)
} else {
None
}
}
(Some(i), Direction::Prev) => {
if i > 0 {
Some(i - 1)
} else if wrap {
Some(eligible.len() - 1)
} else {
None
}
}
};
match next_idx {
None => false,
Some(ni) => {
let new_path = eligible[ni].clone();
if old_path.as_deref() == Some(new_path.as_slice()) {
return false;
}
move_focus(root, old_path.as_deref(), &new_path);
true
}
}
}
pub(crate) fn move_focus(root: &mut ObjectNode, old_path: Option<&[usize]>, new_path: &[usize]) {
use crate::object::ObjectEvent;
if let Some(op) = old_path {
let old_node = node_at_path_mut(root, op);
old_node.set_state(ObjectStates::FOCUSED, false);
old_node.set_state(ObjectStates::EDITED, false);
}
{
let new_node = node_at_path_mut(root, new_path);
new_node.set_state(ObjectStates::FOCUSED, true);
}
if let Some(op) = old_path {
let old_node = node_at_path_mut(root, op);
old_node.invoke_handlers_for(&ObjectEvent::Defocused);
}
{
let new_node = node_at_path_mut(root, new_path);
new_node.invoke_handlers_for(&ObjectEvent::Focused);
}
}
pub fn drop_focus(root: &mut ObjectNode) {
use crate::object::ObjectEvent;
let path = match find_focused_path(root) {
Some(p) => p,
None => return,
};
{
let node = node_at_path_mut(root, &path);
node.set_state(ObjectStates::FOCUSED, false);
node.set_state(ObjectStates::EDITED, false);
}
{
let node = node_at_path_mut(root, &path);
node.invoke_handlers_for(&ObjectEvent::Defocused);
}
}
#[cfg(test)]
mod tests {
use alloc::rc::Rc;
use alloc::vec;
use alloc::vec::Vec;
use core::cell::RefCell;
use super::*;
use crate::event::Event;
use crate::object::{ObjectEvent, ObjectFlags, ObjectNode, ObjectStates};
use crate::renderer::Renderer;
use crate::widget::{Rect, Widget};
struct FW {
bounds: Rect,
}
impl Widget for FW {
fn bounds(&self) -> Rect {
self.bounds
}
fn draw(&self, _renderer: &mut dyn Renderer) {}
fn handle_event(&mut self, _event: &Event) -> bool {
false
}
}
fn make_node(tag: &'static str, r: Rect) -> ObjectNode {
ObjectNode::new(Rc::new(RefCell::new(FW { bounds: r }))).with_tag(tag)
}
fn r(x: i32, y: i32, w: i32, h: i32) -> Rect {
Rect {
x,
y,
width: w,
height: h,
}
}
fn focusable(mut n: ObjectNode) -> ObjectNode {
n.set_flag(ObjectFlags::FOCUSABLE, true);
n
}
fn focused_tag(root: &ObjectNode) -> Option<&'static str> {
if root.states().contains(ObjectStates::FOCUSED) {
return root.tag();
}
for c in root.children() {
if let Some(t) = focused_tag(c) {
return Some(t);
}
}
None
}
#[test]
fn focus_next_visits_in_tree_order() {
let mut root = make_node("root", r(0, 0, 100, 100));
root.append_child(focusable(make_node("a", r(0, 0, 10, 10))));
root.append_child(focusable(make_node("b", r(10, 0, 10, 10))));
root.append_child(focusable(make_node("c", r(20, 0, 10, 10))));
let fg = FocusGroup::new();
assert!(fg.focus_next(&mut root));
assert_eq!(focused_tag(&root), Some("a"));
assert!(fg.focus_next(&mut root));
assert_eq!(focused_tag(&root), Some("b"));
assert!(fg.focus_next(&mut root));
assert_eq!(focused_tag(&root), Some("c"));
assert!(fg.focus_next(&mut root));
assert_eq!(focused_tag(&root), Some("a"));
}
#[test]
fn focus_prev_visits_in_reverse_tree_order() {
let mut root = make_node("root", r(0, 0, 100, 100));
root.append_child(focusable(make_node("a", r(0, 0, 10, 10))));
root.append_child(focusable(make_node("b", r(10, 0, 10, 10))));
let fg = FocusGroup::new();
assert!(fg.focus_next(&mut root)); assert!(fg.focus_prev(&mut root)); assert_eq!(focused_tag(&root), Some("b"));
}
#[test]
fn focus_next_no_wrap_stops_at_end() {
let mut root = make_node("root", r(0, 0, 100, 100));
root.append_child(focusable(make_node("a", r(0, 0, 10, 10))));
root.append_child(focusable(make_node("b", r(10, 0, 10, 10))));
let mut fg = FocusGroup::new();
fg.set_wrap(false);
fg.focus_next(&mut root); fg.focus_next(&mut root); let moved = fg.focus_next(&mut root); assert!(!moved, "should not move past end without wrap");
assert_eq!(focused_tag(&root), Some("b"));
}
#[test]
fn focus_next_skips_hidden_nodes() {
let mut root = make_node("root", r(0, 0, 100, 100));
root.append_child(focusable(make_node("a", r(0, 0, 10, 10))));
let mut hidden = focusable(make_node("b", r(10, 0, 10, 10)));
hidden.set_flag(ObjectFlags::HIDDEN, true);
root.append_child(hidden);
root.append_child(focusable(make_node("c", r(20, 0, 10, 10))));
let fg = FocusGroup::new();
fg.focus_next(&mut root); fg.focus_next(&mut root); assert_eq!(focused_tag(&root), Some("c"));
}
#[test]
fn focus_next_skips_disabled_flag_nodes() {
let mut root = make_node("root", r(0, 0, 100, 100));
root.append_child(focusable(make_node("a", r(0, 0, 10, 10))));
let mut dis = focusable(make_node("b", r(10, 0, 10, 10)));
dis.set_flag(ObjectFlags::DISABLED, true);
root.append_child(dis);
root.append_child(focusable(make_node("c", r(20, 0, 10, 10))));
let fg = FocusGroup::new();
fg.focus_next(&mut root); fg.focus_next(&mut root); assert_eq!(focused_tag(&root), Some("c"));
}
#[test]
fn focus_next_skips_disabled_state_nodes() {
let mut root = make_node("root", r(0, 0, 100, 100));
root.append_child(focusable(make_node("a", r(0, 0, 10, 10))));
let mut dis = focusable(make_node("b", r(10, 0, 10, 10)));
dis.set_state(ObjectStates::DISABLED, true);
root.append_child(dis);
root.append_child(focusable(make_node("c", r(20, 0, 10, 10))));
let fg = FocusGroup::new();
fg.focus_next(&mut root); fg.focus_next(&mut root); assert_eq!(focused_tag(&root), Some("c"));
}
#[test]
fn focus_next_skips_detached_nodes() {
let mut root = make_node("root", r(0, 0, 100, 100));
root.append_child(focusable(make_node("a", r(0, 0, 10, 10))));
root.append_child(focusable(make_node("b", r(10, 0, 10, 10))));
root.append_child(focusable(make_node("c", r(20, 0, 10, 10))));
root.detach_child(1);
let fg = FocusGroup::new();
fg.focus_next(&mut root); fg.focus_next(&mut root); assert_eq!(focused_tag(&root), Some("c"));
}
#[test]
fn single_focused_invariant() {
let mut root = make_node("root", r(0, 0, 100, 100));
root.append_child(focusable(make_node("a", r(0, 0, 10, 10))));
root.append_child(focusable(make_node("b", r(10, 0, 10, 10))));
let fg = FocusGroup::new();
fg.focus_next(&mut root);
fg.focus_next(&mut root);
let count = count_focused(&root);
assert_eq!(count, 1, "exactly one node should have FOCUSED");
}
fn count_focused(node: &ObjectNode) -> usize {
let own = if node.states().contains(ObjectStates::FOCUSED) {
1
} else {
0
};
own + node.children().iter().map(count_focused).sum::<usize>()
}
#[test]
fn focus_move_delivers_defocused_then_focused_events() {
let mut root = make_node("root", r(0, 0, 100, 100));
let events_a: Rc<RefCell<Vec<&'static str>>> = Rc::new(RefCell::new(Vec::new()));
let events_b: Rc<RefCell<Vec<&'static str>>> = Rc::new(RefCell::new(Vec::new()));
let ea = events_a.clone();
let eb = events_b.clone();
let mut node_a = focusable(make_node("a", r(0, 0, 10, 10)));
node_a.add_target_handler(move |ev, _ctx| {
match ev {
ObjectEvent::Focused => ea.borrow_mut().push("a:focused"),
ObjectEvent::Defocused => ea.borrow_mut().push("a:defocused"),
_ => {}
}
false
});
let mut node_b = focusable(make_node("b", r(10, 0, 10, 10)));
node_b.add_target_handler(move |ev, _ctx| {
match ev {
ObjectEvent::Focused => eb.borrow_mut().push("b:focused"),
ObjectEvent::Defocused => eb.borrow_mut().push("b:defocused"),
_ => {}
}
false
});
root.append_child(node_a);
root.append_child(node_b);
let fg = FocusGroup::new();
fg.focus_next(&mut root); fg.focus_next(&mut root);
assert_eq!(*events_a.borrow(), vec!["a:focused", "a:defocused"]);
assert_eq!(*events_b.borrow(), vec!["b:focused"]);
}
#[test]
fn focus_path_addresses_correct_node() {
let mut root = make_node("root", r(0, 0, 100, 100));
root.append_child(focusable(make_node("a", r(0, 0, 10, 10))));
let mut b = make_node("b", r(10, 0, 20, 20));
b.append_child(focusable(make_node("b0", r(12, 2, 5, 5))));
root.append_child(b);
let fg = FocusGroup::new();
assert!(fg.focus_path(&mut root, &[1, 0]));
assert_eq!(focused_tag(&root), Some("b0"));
}
#[test]
fn focus_path_rejects_out_of_bounds() {
let mut root = make_node("root", r(0, 0, 100, 100));
root.append_child(focusable(make_node("a", r(0, 0, 10, 10))));
let fg = FocusGroup::new();
assert!(!fg.focus_path(&mut root, &[5]));
assert_eq!(focused_tag(&root), None);
}
#[test]
fn focus_path_rejects_non_eligible_target() {
let mut root = make_node("root", r(0, 0, 100, 100));
root.append_child(make_node("a", r(0, 0, 10, 10)));
let fg = FocusGroup::new();
assert!(!fg.focus_path(&mut root, &[0]));
}
#[test]
fn drop_focus_clears_focused_state() {
let mut root = make_node("root", r(0, 0, 100, 100));
root.append_child(focusable(make_node("a", r(0, 0, 10, 10))));
let fg = FocusGroup::new();
fg.focus_next(&mut root);
assert_eq!(focused_tag(&root), Some("a"));
drop_focus(&mut root);
assert_eq!(focused_tag(&root), None);
}
#[test]
fn drop_focus_delivers_defocused_event() {
let events: Rc<RefCell<Vec<&'static str>>> = Rc::new(RefCell::new(Vec::new()));
let ev = events.clone();
let mut root = make_node("root", r(0, 0, 100, 100));
let mut node_a = focusable(make_node("a", r(0, 0, 10, 10)));
node_a.add_target_handler(move |event, _ctx| {
if matches!(event, ObjectEvent::Defocused) {
ev.borrow_mut().push("defocused");
}
false
});
root.append_child(node_a);
let fg = FocusGroup::new();
fg.focus_next(&mut root);
drop_focus(&mut root);
assert_eq!(*events.borrow(), vec!["defocused"]);
}
#[test]
fn set_editing_sets_edited_state_on_focused_node() {
let mut root = make_node("root", r(0, 0, 100, 100));
root.append_child(focusable(make_node("a", r(0, 0, 10, 10))));
let mut fg = FocusGroup::new();
fg.focus_next(&mut root);
fg.set_editing(&mut root, true);
assert!(root.children()[0].states().contains(ObjectStates::EDITED));
fg.set_editing(&mut root, false);
assert!(!root.children()[0].states().contains(ObjectStates::EDITED));
}
}