cranpose_ui/
focus_order.rs1use std::cell::RefCell;
2
3use cranpose_core::NodeId;
4use cranpose_ui_graphics::Rect;
5
6use crate::layout::{LayoutBox, LayoutTree};
7
8#[derive(Clone, Copy, Debug, PartialEq)]
10pub struct FocusEntry {
11 pub node_id: NodeId,
12 pub rect: Rect,
13}
14
15impl FocusEntry {
16 pub fn center(&self) -> (f32, f32) {
17 (
18 self.rect.x + self.rect.width * 0.5,
19 self.rect.y + self.rect.height * 0.5,
20 )
21 }
22}
23
24thread_local! {
25 static FOCUS_ORDER: RefCell<Vec<FocusEntry>> = const { RefCell::new(Vec::new()) };
26}
27
28pub fn set_focus_order(entries: Vec<FocusEntry>) {
31 FOCUS_ORDER.with(|cell| *cell.borrow_mut() = entries);
32}
33
34pub fn with_focus_order<T>(reader: impl FnOnce(&[FocusEntry]) -> T) -> T {
36 FOCUS_ORDER.with(|cell| reader(&cell.borrow()))
37}
38
39pub fn focus_order_len() -> usize {
41 FOCUS_ORDER.with(|cell| cell.borrow().len())
42}
43
44pub fn collect_focus_order(tree: &LayoutTree) -> Vec<FocusEntry> {
47 let mut entries = Vec::new();
48 collect_from_box(tree.root(), &mut entries);
49 entries
50}
51
52fn collect_from_box(layout_box: &LayoutBox, entries: &mut Vec<FocusEntry>) {
53 if crate::focus_dispatch::has_focus_target(layout_box.node_id) && takes_space(layout_box.rect) {
54 entries.push(FocusEntry {
55 node_id: layout_box.node_id,
56 rect: layout_box.rect,
57 });
58 }
59 for child in &layout_box.children {
60 collect_from_box(child, entries);
61 }
62}
63
64fn takes_space(rect: Rect) -> bool {
65 rect.width > 0.0
66 && rect.height > 0.0
67 && rect.x.is_finite()
68 && rect.y.is_finite()
69 && rect.width.is_finite()
70 && rect.height.is_finite()
71}