1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
//! A modifier that bounds focus navigation within its subtree.
//!
//! This modifier prevents focus from escaping its child view, either by wrapping
//! around to the other end (Wrap) or by stopping at the boundary (Stop).
use crate::{
environment::LayoutEnvironment,
event::{Event, EventContext, EventResult},
focus::{BoundaryBehavior, DefaultFocus, FocusAction, FocusDirection},
layout::ResolvedLayout,
primitives::{Point, ProposedDimensions},
view::{ViewLayout, ViewMarker},
};
/// A modifier that bounds focus navigation within its subtree.
///
/// When focus tries to exit the bounded region (via Next/Previous navigation),
/// this modifier either wraps focus to the other end or stops at the boundary,
/// depending on the configured [`BoundaryBehavior`].
///
/// Focus events that should pass through:
/// - `Blur` - always deferred to parent
/// - `Select` - always deferred to parent if not handled
///
/// If the subtree contains no focusable elements, all focus events are deferred.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BoundFocus<T> {
child: T,
behavior: BoundaryBehavior,
}
impl<T: ViewMarker> BoundFocus<T> {
/// Creates a new `BoundFocus` modifier with the specified behavior.
#[must_use]
pub const fn new(child: T, behavior: BoundaryBehavior) -> Self {
Self { child, behavior }
}
}
impl<T> ViewMarker for BoundFocus<T>
where
T: ViewMarker,
{
type Renderables = T::Renderables;
type Transition = T::Transition;
}
impl<Captures: ?Sized, T> ViewLayout<Captures> for BoundFocus<T>
where
T: ViewLayout<Captures>,
T::FocusTree: DefaultFocus,
{
type Sublayout = T::Sublayout;
type State = T::State;
type FocusTree = T::FocusTree;
fn priority(&self) -> i8 {
self.child.priority()
}
fn is_empty(&self) -> bool {
self.child.is_empty()
}
fn transition(&self) -> Self::Transition {
self.child.transition()
}
fn build_state(&self, captures: &mut Captures) -> Self::State {
self.child.build_state(captures)
}
fn layout(
&self,
offer: &ProposedDimensions,
env: &impl LayoutEnvironment,
captures: &mut Captures,
state: &mut Self::State,
) -> ResolvedLayout<Self::Sublayout> {
self.child.layout(offer, env, captures, state)
}
fn render_tree(
&self,
layout: &Self::Sublayout,
origin: Point,
env: &impl LayoutEnvironment,
captures: &mut Captures,
state: &mut Self::State,
) -> Self::Renderables {
self.child.render_tree(layout, origin, env, captures, state)
}
fn handle_event(
&self,
event: &Event,
context: &EventContext,
render_tree: &mut Self::Renderables,
captures: &mut Captures,
state: &mut Self::State,
focus: &mut Self::FocusTree,
) -> EventResult {
// Non-focus events pass through transparently
// We don't want to wrap searches for touch events infinitely
let Event::Focus {
action: focus_event,
group,
} = event
else {
return self
.child
.handle_event(event, context, render_tree, captures, state, focus);
};
// Blur and Select always pass through (defer to parent)
if matches!(focus_event, FocusAction::Blur | FocusAction::Select) {
return self
.child
.handle_event(event, context, render_tree, captures, state, focus);
}
// Try to handle the focus event in the child
let result = self
.child
.handle_event(event, context, render_tree, captures, state, focus);
// If handled, we're done
if !matches!(result, EventResult::Deferred) {
return result;
}
// Focus event was deferred, which means we've hit a boundary or there are
// no focusable elements
// Determine if we were moving forward or backward
let is_forward = matches!(
focus_event,
FocusAction::Next | FocusAction::Focus(FocusDirection::Forward)
);
match self.behavior {
BoundaryBehavior::Wrap => {
// Reset focus tree to the opposite end
*focus = if is_forward {
DefaultFocus::default_first()
} else {
DefaultFocus::default_last()
};
// Acquire focus at the wrapped position
let acquire_direction = if is_forward {
FocusDirection::Forward
} else {
FocusDirection::Backward
};
self.child.handle_event(
&Event::Focus {
action: FocusAction::Focus(acquire_direction),
group: *group,
},
context,
render_tree,
captures,
state,
focus,
)
}
BoundaryBehavior::Stop => {
// Try to refocus on the element at the boundary we hit
let refocus_direction = if is_forward {
// We went past the end, refocus backward to get the last element
FocusDirection::Backward
} else {
// We went past the beginning, refocus forward to get the first element
FocusDirection::Forward
};
self.child.handle_event(
&Event::Focus {
action: FocusAction::Focus(refocus_direction),
group: *group,
},
context,
render_tree,
captures,
state,
focus,
)
}
}
}
}