use crate::widget_id::WidgetId;
use super::WidgetTree;
impl WidgetTree {
fn is_region(&self, id: WidgetId) -> bool {
self.arena.is_active(id)
&& self
.arena
.get(id)
.is_some_and(|node| node.focus_region.is_some())
}
pub(crate) fn innermost_region_of(&self, id: WidgetId) -> Option<WidgetId> {
let mut current = Some(id);
while let Some(at) = current {
if self.is_region(at) {
return Some(at);
}
current = self.arena.parent(at);
}
None
}
pub(crate) fn regions_in_scope(&self, source: Option<WidgetId>) -> Vec<WidgetId> {
if !self.arena.focus_regions_seen {
return Vec::new();
}
let modal = match source {
Some(source) => self.intent_walk(source, &[]).2,
None => self
.overlay_manager
.topmost_modal()
.map(|modal| modal.content_id),
};
let roots: Vec<WidgetId> = match modal {
Some(content) => vec![content],
None => self
.arena
.roots()
.iter()
.copied()
.filter(|&root| self.overlay_manager.find_by_content(root).is_none())
.collect(),
};
let mut out = Vec::new();
for &root in &roots {
self.collect_regions(root, &mut out);
}
let tab_order: Vec<WidgetId> = roots
.iter()
.flat_map(|&root| self.tab_stops_in_order(root))
.collect();
let first_stop = |region: WidgetId| {
tab_order
.iter()
.position(|&id| self.is_descendant_of(id, region))
.unwrap_or(usize::MAX)
};
out.sort_by_cached_key(|®ion| first_stop(region));
out
}
fn collect_regions(&self, id: WidgetId, out: &mut Vec<WidgetId>) {
if !self.arena.is_active(id) {
return;
}
if self.is_region(id) {
out.push(id);
}
for &child in self.arena.children(id) {
self.collect_regions(child, out);
}
}
fn is_divider(&self, id: WidgetId) -> bool {
self.build_overridden_builder(id).role() == accesskit::Role::Splitter
}
pub(crate) fn region_own_focusables(&self, region: WidgetId) -> Vec<WidgetId> {
self.tab_stops_in_order(region)
.into_iter()
.filter(|&id| self.innermost_region_of(id) == Some(region))
.collect()
}
fn region_of_source(&self, source: WidgetId) -> Option<WidgetId> {
self.intent_walk(source, &[])
.0
.into_iter()
.find(|&id| self.is_region(id))
}
pub(crate) fn region_cycle_target_from(
&self,
source: Option<WidgetId>,
forward: bool,
) -> Option<(WidgetId, WidgetId)> {
let regions = self.regions_in_scope(source);
if regions.is_empty() {
return None;
}
let n = regions.len();
let current = source
.filter(|_| self.focused.is_some())
.and_then(|source| self.region_of_source(source))
.and_then(|region| regions.iter().position(|&r| r == region));
let order: Vec<usize> = match current {
Some(at) => (1..=n)
.map(|k| {
if forward {
(at + k) % n
} else {
(at + n - k) % n
}
})
.collect(),
None if forward => (0..n).collect(),
None => (0..n).rev().collect(),
};
for index in order {
let region = regions[index];
let own = self.region_own_focusables(region);
let Some(&first) = own.first() else {
continue;
};
if Some(index) == current
&& let Some(focused) = self.focused
&& self.region_of_source(focused) == Some(region)
{
return Some((region, focused));
}
let remembered = self
.arena
.get(region)
.and_then(|node| node.focus_region_last_focus)
.filter(|id| own.contains(id));
let control = |id: &WidgetId| !self.is_divider(*id);
let landing = remembered
.filter(control)
.or_else(|| self.region_hinted_landing(region, &own))
.or_else(|| own.iter().copied().find(control))
.unwrap_or(first);
return Some((region, landing));
}
None
}
fn region_hinted_landing(&self, region: WidgetId, own: &[WidgetId]) -> Option<WidgetId> {
let hint = self.arena.get(region)?.widget.focus_region_landing()?;
if hint == region || !self.arena.is_active(hint) || !self.is_descendant_of(hint, region) {
return None;
}
let under: Vec<WidgetId> = own
.iter()
.copied()
.filter(|&id| self.is_descendant_of(id, hint))
.collect();
under
.iter()
.copied()
.find(|&id| !self.is_divider(id))
.or_else(|| under.first().copied())
}
pub(crate) fn cycle_focus_region(
&mut self,
source: Option<WidgetId>,
forward: bool,
ops: &mut dyn crate::window::WindowOps,
) -> bool {
let Some((region, landing)) = self.region_cycle_target_from(source, forward) else {
return false;
};
if self.focused != Some(landing) {
self.focus_with_origin_ops(landing, crate::focus::FocusOrigin::Keyboard, ops);
}
let name = self
.arena
.get(region)
.and_then(|node| node.focus_region.as_ref())
.map(|name| name.get())
.unwrap_or_default();
if !name.trim().is_empty() {
self.announce(name);
}
true
}
pub(crate) fn remember_region_focus(&mut self, focused: WidgetId) {
if let Some(region) = self.innermost_region_of(focused)
&& let Some(node) = self.arena.get_mut(region)
{
node.focus_region_last_focus = Some(focused);
}
}
}