I'm taking most of my inspiration from this document: https://docs.flutter.dev/development/ui/advanced/focus
Essentially, they describe a system of focus handling as follows:
- Focus Tree: Tracks focus nodes and their relationships
- Focus Node: A single node in the focus tree which can receive focus
- Focus Chain: the focused nodes, starting from the root and going to the primary focus node
- Primary Focus Node:
- The deepest focused node
- Events go first to this node, and propagate up through the focus chain
- Focus Traversal:
- the process of changing focus nodes on
TAB
- Focus Scope: a scope that groups nodes for traversal
To implement this functionality, two key requirements stand out:
- We need to build a focus tree. This can be done in two ways:
- Use a hook-based system that detects the creation of focus nodes, and adds them to hidden global state. By using callbacks on drop, we can detect different of the tree
- Add a function to the widget trait which returns
Option<FocusTree>
- and recursively builds the focus tree
- We need widgets to be able to bubble input upwards
- We can track the current focus node, and drill down to it
- Or, we can start from the current focus node, and bubble up to the parents - this requires passing a reference to the focus node parent to it's child
We can either use Rc / Weak style references, or track IDs which correspond to each focus node:
struct FocusNodeId(i64);
// Needs to be regenerated on each render call
static FOCUS_TREE: Mutex<BTreeMap<FocusNodeId, Vec<FocusNodeId>>> = Mutex::new(...);
static FOCUS_NODES: Mutex<HashMap<FocusNodeId, FocusNode>> = Mutex::new(...);
I think that tracking IDs is the best/simplest solution, though it requires some consideration W.R.T. concurrency.
Focus hook system:
mod focus_hook {
static ROOT: Mutex<Option<Vec<FocusNode>>> = Mutex::new(None);
/// Returns the parent of the current focus node;
fn parent() -> Weak<FocusNode>;
/// Add new focus node as child
fn push_child(FocusNode);
/// Move up one level in the tree. Returns all focus nodes that would be children
/// `push_child` will now add to the parent of the previously pushed focus node
fn bubble() -> Option<Vec<Rc<FocusNode>>>;
/// Assign focus to the current node
fn grab_focus(fn: &mut FocusNode);
}
struct FocusNode {
inner: Option<Widget>,
parent: Option<Weak<FocusNode>>,
focus_children: Option<Vec<Rc<FocusNode>>>
}
impl FocusNode {
fn new(component: _) -> _ {
let mut self_ = Rc::new(FocusNode { parent: None, inner: None, focus_children: None });
self_.parent = Some(focus_hook::parent());
focus_hook::push_child(self_.clone());
/// Render call will populate children
self_.inner = component.render();
self_.focus_children = focus_hook::bubble();
self_
}
}
Trait based focus tree:
pub trait Element {
fn draw(&self, _rect: Rect, _frame: &mut Frame) {}
fn on_key(&self, _event: KeyEvent) {}
fn on_mouse(&self, _rect: Rect, _event: MouseEvent) {}
fn children(&self) -> Vec<Box<dyn Element>>;
fn focus_tree(&self) -> Vec<Rc<FocusNode>> {
// Produce list of focus trees for each child
// Place self at top of focus tree, with child focus trees as children
// eg.
// f0
// / \
// o f1 _
// / \ \
// o f2 f3
// / \ \ | \
// f4 f5 f6 o f7
// Focus tree:
// _ f0 __
// / / \ \
// / / \ \
// f4 f5 f2 f1
// | |
// f6 f3
// |
// f7
let mut focus_trees = vec![];
for child in self.children() {
focus_trees.extend(child.focus_tree());
}
if let Some(s) = self.as_focus_node() {
vec![{ s.focus_children = Some(focus_trees); s}])
} else {
focus_trees
}
}
fn as_focus_node(&self) -> Option<Rc<FocusNode>> { None }
}
struct FocusNode {
inner: Option<Widget>,
parent: Option<Weak<FocusNode>>,
focus_children: Option<Vec<Rc<FocusNode>>>
}
impl FocusNode {
fn new(component: _) -> _ {
let mut self_ = Rc::new(FocusNode { parent: None, inner: None, focus_children: None });
self_.parent = Some(focus_hook::parent());
focus_hook::push_child(self_.clone());
/// Render call will populate children
self_.inner = component.render();
self_.focus_children = self.focus_tree();
self_
}
}
Bubbling approach to handling key events:
mod focus {
static ACTIVE_FOCUS_NODE: Mutex<Option<Rc<FocusNode>>>> = Mutex::new(None);
fn take_focus(focus_node: &FocusNode);
fn handle_key(event: KeyEvent) {
if let Some(node) = ACTIVE_FOCUS_NODE.lock().unwrap() {
node.on_key(event);
}
}
}
impl Element for FocusNode {
fn on_key(&self, event: KeyEvent) {
if self.inner.on_key(event) == Propagate::Next {
self.parent.unwrap().on_key(event);
}
}
}
The snippets I've outlined allow describing the focus tree (explicit) / focus chain (implicit), active focus node (explicit).
The focus scope is relatively easy to implement, as a plain focus node.
Focus traversal could be done using the focus tree and visiting parents in order of increasing ID - again, certain implementation details would need to be figured out, but this should be doable.
Again - I'd be happy to implement this, but I'd like to get an idea for what overall design you think is best before I go ahead and implement this functionality. I should be able to implement an MVP relatively quickly - likely under an experimental_components::focus
module.