use crate::base::Point;
use crate::reactive::request_frame;
use super::event::{EventCtx, Phase, UiEvent};
use super::tree::{UiTree, ViewId};
impl UiTree {
pub fn focus_next(&mut self) {
self.cycle_focus(1);
}
pub fn focus_prev(&mut self) {
self.cycle_focus(-1);
}
pub fn set_focus(&mut self, id: Option<ViewId>) {
let old = self.core.borrow().focus;
if old == id {
return;
}
if let Some(old_id) = old {
self.core.borrow_mut().focus = None;
let mut ctx = EventCtx::default();
self.run_handlers(old_id, Phase::Target, &UiEvent::FocusOut, &mut ctx);
let rect = self.rect_of(old_id);
self.core.borrow_mut().damage_rect(rect);
}
if let Some(new_id) = id {
if self.core.borrow().insts.contains(new_id.0) {
self.core.borrow_mut().focus = Some(new_id);
self.record_focus_memory(new_id);
let mut ctx = EventCtx::default();
self.run_handlers(new_id, Phase::Target, &UiEvent::FocusIn, &mut ctx);
let rect = self.rect_of(new_id);
self.core.borrow_mut().damage_rect(rect);
}
}
request_frame();
}
pub fn is_focused(&self, id: ViewId) -> bool {
self.core.borrow().focus == Some(id)
}
pub(super) fn update_hover(&mut self, pos: Point) {
{
let core = self.core.borrow();
if core.last_hover == Some((pos, core.layout_epoch)) {
return;
}
}
let new_path = match self.hit_test(pos) {
Some(t) => self.path_to(t),
None => Vec::new(),
};
let (old_path, epoch) = {
let core = self.core.borrow();
(core.hovered_path.clone(), core.layout_epoch)
};
self.core.borrow_mut().last_hover = Some((pos, epoch));
if new_path == old_path {
return;
}
for id in old_path.iter().rev().filter(|id| !new_path.contains(id)) {
let mut ctx = EventCtx {
target: Some(*id),
target_rect: self.rect_of(*id),
..EventCtx::default()
};
self.run_handlers(*id, Phase::Target, &UiEvent::MouseLeave, &mut ctx);
}
for id in new_path.iter().filter(|id| !old_path.contains(id)) {
let mut ctx = EventCtx {
target: Some(*id),
target_rect: self.rect_of(*id),
..EventCtx::default()
};
self.run_handlers(*id, Phase::Target, &UiEvent::MouseEnter, &mut ctx);
}
self.core.borrow_mut().hovered_path = new_path;
}
fn cycle_focus(&mut self, dir: i32) {
let current = self.core.borrow().focus;
let scope = current.and_then(|c| self.trap_root_of(c));
let order = self.focusables_within(scope);
if order.is_empty() {
return;
}
let next = match current.and_then(|c| order.iter().position(|x| *x == c)) {
Some(pos) => {
let n = order.len() as i32;
let idx = ((pos as i32 + dir) % n + n) % n; order[idx as usize]
}
None => {
if dir > 0 {
order[0]
} else {
*order.last().expect("non-empty")
}
}
};
let next = self.restore_memory_target(current, next);
self.set_focus(Some(next));
}
fn restore_memory_target(&self, from: Option<ViewId>, entering: ViewId) -> ViewId {
let core = self.core.borrow();
let mut containers: Vec<ViewId> = Vec::new();
let mut cur = Some(entering);
while let Some(node) = cur {
let Some(inst) = core.insts.get(node.0) else {
break;
};
if inst.focus_memory {
containers.push(node);
}
cur = inst.parent;
}
let contains = |container: ViewId, id: Option<ViewId>| -> bool {
let mut cur = id;
while let Some(node) = cur {
if node == container {
return true;
}
cur = core.insts.get(node.0).and_then(|i| i.parent);
}
false
};
for container in containers.into_iter().rev() {
if contains(container, from) {
continue; }
if let Some(&remembered) = core.focus_memory.get(&container) {
let alive_focusable = core
.insts
.get(remembered.0)
.map(|i| i.focusable)
.unwrap_or(false);
if alive_focusable && contains(container, Some(remembered)) {
return remembered;
}
}
}
entering
}
pub(super) fn record_focus_memory(&mut self, focused: ViewId) {
let containers: Vec<ViewId> = {
let core = self.core.borrow();
let mut out = Vec::new();
let mut cur = Some(focused);
while let Some(node) = cur {
let Some(inst) = core.insts.get(node.0) else {
break;
};
if inst.focus_memory {
out.push(node);
}
cur = inst.parent;
}
out
};
if !containers.is_empty() {
let mut core = self.core.borrow_mut();
for c in containers {
core.focus_memory.insert(c, focused);
}
}
}
pub fn focus_first(&mut self) {
let order = self.focusables_within(None);
if let Some(&first) = order.first() {
self.set_focus(Some(first));
}
}
pub fn focus_next_in(&mut self, dir: super::event::Key) -> bool {
use super::event::Key;
let current = { self.core.borrow().focus };
let current = match current {
Some(c) => c,
None => {
self.focus_first();
return self.core.borrow().focus.is_some();
}
};
let scope = self.trap_root_of(current);
let order = self.focusables_within(scope);
let from = self.rect_of(current);
let (fcx, fcy) = (from.x + from.w / 2, from.y + from.h / 2);
let mut best: Option<(i64, ViewId)> = None;
for cand in order {
if cand == current {
continue;
}
let r = self.rect_of(cand);
if r.is_empty() {
continue;
}
let (cx, cy) = (r.x + r.w / 2, r.y + r.h / 2);
let (primary, ortho) = match dir {
Key::Up => (fcy - cy, (cx - fcx).abs()),
Key::Down => (cy - fcy, (cx - fcx).abs()),
Key::Left => (fcx - cx, (cy - fcy).abs()),
Key::Right => (cx - fcx, (cy - fcy).abs()),
_ => return false,
};
if primary <= 0 {
continue; }
let score = primary as i64 + 2 * ortho as i64;
if best.map(|(s, _)| score < s).unwrap_or(true) {
best = Some((score, cand));
}
}
match best {
Some((_, target)) => {
self.set_focus(Some(target));
true
}
None => false,
}
}
pub(super) fn focusable_ancestor_of(&self, id: ViewId) -> Option<ViewId> {
let core = self.core.borrow();
let mut cur = Some(id);
while let Some(node) = cur {
let inst = core.insts.get(node.0)?;
if inst.focusable {
return Some(node);
}
cur = inst.parent;
}
None
}
fn trap_root_of(&self, id: ViewId) -> Option<ViewId> {
let core = self.core.borrow();
let mut cur = Some(id);
while let Some(node) = cur {
let inst = core.insts.get(node.0)?;
if inst.focus_trap {
return Some(node);
}
cur = inst.parent;
}
None
}
fn focusables_within(&self, scope: Option<ViewId>) -> Vec<ViewId> {
let core = self.core.borrow();
let start = match scope.or(core.root) {
Some(s) => s,
None => return Vec::new(),
};
let mut out = Vec::new();
let mut stack = vec![start];
while let Some(id) = stack.pop() {
let Some(inst) = core.insts.get(id.0) else {
continue;
};
if inst.focusable {
out.push(id);
}
for &child in inst.children.iter().rev() {
stack.push(child);
}
}
out
}
}