Skip to main content

zest_core/
focus.rs

1//! Focus identity and traversal state for transient widget trees.
2
3/// Stable identity of a focusable widget across frames.
4#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
5pub struct WidgetId(u64);
6
7impl WidgetId {
8    /// Construct a widget id from a stable numeric value.
9    #[must_use]
10    pub const fn new(raw: u64) -> Self {
11        Self(raw)
12    }
13
14    /// The underlying numeric value.
15    #[must_use]
16    pub const fn raw(self) -> u64 {
17        self.0
18    }
19}
20
21/// Direction of focus traversal.
22#[derive(Copy, Clone, Debug, PartialEq, Eq)]
23pub enum FocusDirection {
24    /// Move to the next focusable widget.
25    Forward,
26    /// Move to the previous focusable widget.
27    Backward,
28}
29
30/// Cross-frame focus state owned by the runtime.
31#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
32pub struct FocusState {
33    focused: Option<WidgetId>,
34}
35
36impl FocusState {
37    /// A fresh focus state with no focused widget.
38    #[must_use]
39    pub const fn new() -> Self {
40        Self { focused: None }
41    }
42
43    /// The currently-focused widget, if any.
44    #[must_use]
45    pub const fn focused(self) -> Option<WidgetId> {
46        self.focused
47    }
48
49    /// Set the focused widget directly.
50    pub fn set(&mut self, focused: Option<WidgetId>) {
51        self.focused = focused;
52    }
53
54    /// Clear focus.
55    pub fn clear(&mut self) {
56        self.focused = None;
57    }
58
59    /// Drop focus if the currently-focused widget is no longer present.
60    pub fn reconcile(&mut self, order: &[WidgetId]) {
61        if let Some(id) = self.focused
62            && !order.contains(&id)
63        {
64            self.focused = None;
65        }
66    }
67
68    /// Advance focus through `order` in `direction`.
69    pub fn advance(&mut self, order: &[WidgetId], direction: FocusDirection) {
70        if order.is_empty() {
71            self.focused = None;
72            return;
73        }
74
75        let current = self
76            .focused
77            .and_then(|id| order.iter().position(|candidate| *candidate == id));
78
79        let next = match (direction, current) {
80            (FocusDirection::Forward, Some(idx)) => (idx + 1) % order.len(),
81            (FocusDirection::Backward, Some(0)) => order.len() - 1,
82            (FocusDirection::Backward, Some(idx)) => idx - 1,
83            (FocusDirection::Forward | FocusDirection::Backward, None) => 0,
84        };
85
86        self.focused = Some(order[next]);
87    }
88}