Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1821,6 +1821,9 @@ node_id_vec_property_methods! {
}

node_id_property_methods! {
/// For a composite widget such as a listbox, tree, or grid, identifies
/// the currently active descendant. Used when focus remains on the container
/// while the active item changes.
(ActiveDescendant, active_descendant, set_active_descendant, clear_active_descendant),
(ErrorMessage, error_message, set_error_message, clear_error_message),
(InPageLinkTarget, in_page_link_target, set_in_page_link_target, clear_in_page_link_target),
Expand Down
137 changes: 136 additions & 1 deletion consumer/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,21 @@ impl<'a> Node<'a> {
}

pub fn is_focused(&self) -> bool {
self.tree_state.focus_id() == Some(self.id())
let dominated_by_active_descendant = |node_id| {
self.tree_state
.node_by_id(node_id)
.and_then(|node| node.active_descendant())
.is_some()
};
match self.tree_state.focus_id() {
Some(focus_id) if focus_id == self.id() => !dominated_by_active_descendant(focus_id),
Some(focus_id) => self
.tree_state
.node_by_id(focus_id)
.and_then(|focused| focused.active_descendant())
.is_some_and(|active_descendant| active_descendant.id() == self.id()),
None => false,
}
}

pub fn is_focused_in_tree(&self) -> bool {
Expand Down Expand Up @@ -896,6 +910,13 @@ impl<'a> Node<'a> {
.map(move |control_id| state.node_by_id(id.with_same_tree(*control_id)).unwrap())
}

pub fn active_descendant(&self) -> Option<Node<'a>> {
self.state
.data
.active_descendant()
.and_then(|id| self.tree_state.node_by_id(self.id.with_same_tree(id)))
}

pub fn raw_text_selection(&self) -> Option<&TextSelection> {
self.data().text_selection()
}
Expand Down Expand Up @@ -1791,4 +1812,118 @@ mod tests {
assert_ne!(id1, id4);
}
}

#[test]
fn is_focused_when_node_has_focus() {
const ROOT_ID: NodeId = NodeId(0);
const BUTTON_ID: NodeId = NodeId(1);

let update = TreeUpdate {
nodes: vec![
(ROOT_ID, {
let mut node = Node::new(Role::Window);
node.set_children(vec![BUTTON_ID]);
node
}),
(BUTTON_ID, Node::new(Role::Button)),
],
tree: Some(Tree::new(ROOT_ID)),
tree_id: TreeId::ROOT,
focus: BUTTON_ID,
};
let tree = crate::Tree::new(update, true);
assert!(tree
.state()
.node_by_id(nid(BUTTON_ID))
.unwrap()
.is_focused());
}

#[test]
fn is_focused_when_node_does_not_have_focus() {
const ROOT_ID: NodeId = NodeId(0);
const BUTTON_ID: NodeId = NodeId(1);

let update = TreeUpdate {
nodes: vec![
(ROOT_ID, {
let mut node = Node::new(Role::Window);
node.set_children(vec![BUTTON_ID]);
node
}),
(BUTTON_ID, Node::new(Role::Button)),
],
tree: Some(Tree::new(ROOT_ID)),
tree_id: TreeId::ROOT,
focus: ROOT_ID,
};
let tree = crate::Tree::new(update, true);
assert!(!tree
.state()
.node_by_id(nid(BUTTON_ID))
.unwrap()
.is_focused());
}

#[test]
fn is_focused_active_descendant_is_focused() {
const ROOT_ID: NodeId = NodeId(0);
const LISTBOX_ID: NodeId = NodeId(1);
const ITEM_ID: NodeId = NodeId(2);

let update = TreeUpdate {
nodes: vec![
(ROOT_ID, {
let mut node = Node::new(Role::Window);
node.set_children(vec![LISTBOX_ID]);
node
}),
(LISTBOX_ID, {
let mut node = Node::new(Role::ListBox);
node.set_children(vec![ITEM_ID]);
node.set_active_descendant(ITEM_ID);
node
}),
(ITEM_ID, Node::new(Role::ListBoxOption)),
],
tree: Some(Tree::new(ROOT_ID)),
tree_id: TreeId::ROOT,
focus: LISTBOX_ID,
};
let tree = crate::Tree::new(update, true);
assert!(tree.state().node_by_id(nid(ITEM_ID)).unwrap().is_focused());
}

#[test]
fn is_focused_node_with_active_descendant_is_not_focused() {
const ROOT_ID: NodeId = NodeId(0);
const LISTBOX_ID: NodeId = NodeId(1);
const ITEM_ID: NodeId = NodeId(2);

let update = TreeUpdate {
nodes: vec![
(ROOT_ID, {
let mut node = Node::new(Role::Window);
node.set_children(vec![LISTBOX_ID]);
node
}),
(LISTBOX_ID, {
let mut node = Node::new(Role::ListBox);
node.set_children(vec![ITEM_ID]);
node.set_active_descendant(ITEM_ID);
node
}),
(ITEM_ID, Node::new(Role::ListBoxOption)),
],
tree: Some(Tree::new(ROOT_ID)),
tree_id: TreeId::ROOT,
focus: LISTBOX_ID,
};
let tree = crate::Tree::new(update, true);
assert!(!tree
.state()
.node_by_id(nid(LISTBOX_ID))
.unwrap()
.is_focused());
}
}
125 changes: 118 additions & 7 deletions consumer/src/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,10 @@ impl State {
}

pub fn focus(&self) -> Option<Node<'_>> {
self.focus_id().map(|id| self.node_by_id(id).unwrap())
self.focus_id().map(|id| {
let focused = self.node_by_id(id).unwrap();
focused.active_descendant().unwrap_or(focused)
})
}

pub fn active_dialog(&self) -> Option<Node<'_>> {
Expand Down Expand Up @@ -641,9 +644,10 @@ impl Tree {
let new_node = self.next_state.node_by_id(*id).unwrap();
handler.node_updated(&old_node, &new_node);
}
if self.state.focus_id() != self.next_state.focus_id() {
let old_node = self.state.focus();
if let Some(old_node) = &old_node {
let old_focus = self.state.focus();
let new_focus = self.next_state.focus();
if old_focus.as_ref().map(|n| n.id()) != new_focus.as_ref().map(|n| n.id()) {
if let Some(old_node) = &old_focus {
let id = old_node.id();
if !changes.updated_node_ids.contains(&id)
&& !changes.removed_node_ids.contains(&id)
Expand All @@ -653,8 +657,7 @@ impl Tree {
}
}
}
let new_node = self.next_state.focus();
if let Some(new_node) = &new_node {
if let Some(new_node) = &new_focus {
let id = new_node.id();
if !changes.added_node_ids.contains(&id) && !changes.updated_node_ids.contains(&id)
{
Expand All @@ -663,7 +666,7 @@ impl Tree {
}
}
}
handler.focus_moved(old_node.as_ref(), new_node.as_ref());
handler.focus_moved(old_focus.as_ref(), new_focus.as_ref());
}
for id in &changes.removed_node_ids {
let node = self.state.node_by_id(*id).unwrap();
Expand Down Expand Up @@ -2867,4 +2870,112 @@ mod tests {
assert!(handler.updated_nodes.contains(&subtree_node_id(1)),);
assert!(!handler.updated_nodes.contains(&subtree_node_id(0)),);
}

#[test]
fn focus_returns_focused_node() {
let update = TreeUpdate {
nodes: vec![
(LocalNodeId(0), {
let mut node = Node::new(Role::Window);
node.set_children(vec![LocalNodeId(1)]);
node
}),
(LocalNodeId(1), Node::new(Role::Button)),
],
tree: Some(Tree::new(LocalNodeId(0))),
tree_id: TreeId::ROOT,
focus: LocalNodeId(1),
};
let tree = super::Tree::new(update, true);
assert_eq!(tree.state().focus().unwrap().id(), node_id(1));
}

#[test]
fn focus_returns_active_descendant() {
let update = TreeUpdate {
nodes: vec![
(LocalNodeId(0), {
let mut node = Node::new(Role::Window);
node.set_children(vec![LocalNodeId(1)]);
node
}),
(LocalNodeId(1), {
let mut node = Node::new(Role::ListBox);
node.set_children(vec![LocalNodeId(2)]);
node.set_active_descendant(LocalNodeId(2));
node
}),
(LocalNodeId(2), Node::new(Role::ListBoxOption)),
],
tree: Some(Tree::new(LocalNodeId(0))),
tree_id: TreeId::ROOT,
focus: LocalNodeId(1),
};
let tree = super::Tree::new(update, true);
assert_eq!(tree.state().focus().unwrap().id(), node_id(2));
}

#[test]
fn focus_moved_when_active_descendant_changes() {
let update = TreeUpdate {
nodes: vec![
(LocalNodeId(0), {
let mut node = Node::new(Role::Window);
node.set_children(vec![LocalNodeId(1)]);
node
}),
(LocalNodeId(1), {
let mut node = Node::new(Role::ListBox);
node.set_children(vec![LocalNodeId(2), LocalNodeId(3)]);
node.set_active_descendant(LocalNodeId(2));
node
}),
(LocalNodeId(2), Node::new(Role::ListBoxOption)),
(LocalNodeId(3), Node::new(Role::ListBoxOption)),
],
tree: Some(Tree::new(LocalNodeId(0))),
tree_id: TreeId::ROOT,
focus: LocalNodeId(1),
};
let mut tree = super::Tree::new(update, true);

struct Handler {
focus_moved_called: bool,
old_focus: Option<NodeId>,
new_focus: Option<NodeId>,
}
impl super::ChangeHandler for Handler {
fn node_added(&mut self, _: &crate::Node) {}
fn node_updated(&mut self, _: &crate::Node, _: &crate::Node) {}
fn focus_moved(&mut self, old: Option<&crate::Node>, new: Option<&crate::Node>) {
self.focus_moved_called = true;
self.old_focus = old.map(|n| n.id());
self.new_focus = new.map(|n| n.id());
}
fn node_removed(&mut self, _: &crate::Node) {}
}

let mut handler = Handler {
focus_moved_called: false,
old_focus: None,
new_focus: None,
};

let update = TreeUpdate {
nodes: vec![(LocalNodeId(1), {
let mut node = Node::new(Role::ListBox);
node.set_children(vec![LocalNodeId(2), LocalNodeId(3)]);
node.set_active_descendant(LocalNodeId(3));
node
})],
tree: None,
tree_id: TreeId::ROOT,
focus: LocalNodeId(1),
};
tree.update_and_process_changes(update, &mut handler);

assert!(handler.focus_moved_called);
assert_eq!(handler.old_focus, Some(node_id(2)));
assert_eq!(handler.new_focus, Some(node_id(3)));
}
}