cranpose_foundation/nodes/input/
focus.rs

1//! Focus management system that integrates with modifier chains.
2//!
3//! This module provides the focus manager that tracks the currently focused
4//! node and handles focus transitions using capability-aware traversal through
5//! modifier chains.
6
7use std::collections::HashMap;
8
9/// Unique identifier for focusable nodes.
10#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub struct FocusId(pub(crate) usize);
12
13impl FocusId {
14    pub fn new(id: usize) -> Self {
15        Self(id)
16    }
17
18    pub fn as_usize(self) -> usize {
19        self.0
20    }
21}
22
23/// Focus manager that tracks and manages focus state across the composition.
24///
25/// This mirrors Jetpack Compose's FocusManager and uses capability-filtered
26/// traversal to find and navigate between focus targets.
27pub struct FocusManager {
28    /// The currently focused node ID, if any.
29    active_focus_id: Option<FocusId>,
30    /// Map of focus IDs to their focus state.
31    focus_states: HashMap<FocusId, crate::modifier::FocusState>,
32    /// Next ID to allocate for new focus targets.
33    next_id: usize,
34}
35
36impl Default for FocusManager {
37    fn default() -> Self {
38        Self::new()
39    }
40}
41
42impl FocusManager {
43    pub fn new() -> Self {
44        Self {
45            active_focus_id: None,
46            focus_states: HashMap::new(),
47            next_id: 1,
48        }
49    }
50
51    /// Allocates a new unique focus ID.
52    pub fn allocate_focus_id(&mut self) -> FocusId {
53        let id = FocusId(self.next_id);
54        self.next_id += 1;
55        id
56    }
57
58    /// Returns the currently focused node ID.
59    pub fn active_focus_id(&self) -> Option<FocusId> {
60        self.active_focus_id
61    }
62
63    /// Requests focus for the given node.
64    ///
65    /// This clears focus from the previously focused node (if any) and
66    /// sets the given node as active.
67    pub fn request_focus(&mut self, id: FocusId) -> bool {
68        // Clear previous focus
69        if let Some(prev_id) = self.active_focus_id {
70            if prev_id != id {
71                self.focus_states
72                    .insert(prev_id, crate::modifier::FocusState::Inactive);
73            }
74        }
75
76        // Set new focus
77        self.active_focus_id = Some(id);
78        self.focus_states
79            .insert(id, crate::modifier::FocusState::Active);
80        true
81    }
82
83    /// Clears focus from the currently focused node.
84    pub fn clear_focus(&mut self) {
85        if let Some(id) = self.active_focus_id.take() {
86            self.focus_states
87                .insert(id, crate::modifier::FocusState::Inactive);
88        }
89    }
90
91    /// Captures focus, preventing other nodes from taking focus.
92    pub fn capture_focus(&mut self) -> bool {
93        if let Some(id) = self.active_focus_id {
94            self.focus_states
95                .insert(id, crate::modifier::FocusState::Captured);
96            true
97        } else {
98            false
99        }
100    }
101
102    /// Releases captured focus.
103    pub fn free_focus(&mut self) -> bool {
104        if let Some(id) = self.active_focus_id {
105            let state = self.focus_states.get(&id);
106            if matches!(state, Some(crate::modifier::FocusState::Captured)) {
107                self.focus_states
108                    .insert(id, crate::modifier::FocusState::Active);
109                return true;
110            }
111        }
112        false
113    }
114
115    /// Gets the focus state for a given node.
116    pub fn focus_state(&self, id: FocusId) -> crate::modifier::FocusState {
117        self.focus_states
118            .get(&id)
119            .copied()
120            .unwrap_or(crate::modifier::FocusState::Inactive)
121    }
122
123    /// Returns whether the given node is currently focused.
124    pub fn is_focused(&self, id: FocusId) -> bool {
125        self.active_focus_id == Some(id)
126    }
127}