1use crate::{
2 ui_transform::UiGlobalTransform, ComputedNode, ComputedUiTargetCamera, Node, OverrideClip,
3 UiStack,
4};
5use bevy_camera::{visibility::InheritedVisibility, Camera, NormalizedRenderTarget};
6use bevy_ecs::{
7 change_detection::DetectChangesMut,
8 entity::{ContainsEntity, Entity},
9 hierarchy::ChildOf,
10 prelude::{Component, With},
11 query::{QueryData, Without},
12 reflect::ReflectComponent,
13 system::{Local, Query, Res},
14};
15use bevy_input::{mouse::MouseButton, touch::Touches, ButtonInput};
16use bevy_math::Vec2;
17use bevy_platform::collections::HashMap;
18use bevy_reflect::{std_traits::ReflectDefault, Reflect};
19use bevy_window::{PrimaryWindow, Window};
20
21use smallvec::SmallVec;
22
23#[cfg(feature = "serialize")]
24use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
25
26#[derive(Component, Copy, Clone, Eq, PartialEq, Debug, Reflect)]
46#[reflect(Component, Default, PartialEq, Debug, Clone)]
47#[cfg_attr(
48 feature = "serialize",
49 derive(serde::Serialize, serde::Deserialize),
50 reflect(Serialize, Deserialize)
51)]
52pub enum Interaction {
53 Pressed,
57 Hovered,
59 None,
61}
62
63impl Interaction {
64 const DEFAULT: Self = Self::None;
65}
66
67impl Default for Interaction {
68 fn default() -> Self {
69 Self::DEFAULT
70 }
71}
72
73#[derive(Component, Copy, Clone, Default, PartialEq, Debug, Reflect)]
80#[reflect(Component, Default, PartialEq, Debug, Clone)]
81#[cfg_attr(
82 feature = "serialize",
83 derive(serde::Serialize, serde::Deserialize),
84 reflect(Serialize, Deserialize)
85)]
86pub struct RelativeCursorPosition {
87 pub cursor_over: bool,
89 pub normalized: Option<Vec2>,
92}
93
94impl RelativeCursorPosition {
95 pub fn cursor_over(&self) -> bool {
97 self.cursor_over
98 }
99}
100
101#[derive(Component, Copy, Clone, Eq, PartialEq, Debug, Reflect)]
103#[reflect(Component, Default, PartialEq, Debug, Clone)]
104#[cfg_attr(
105 feature = "serialize",
106 derive(serde::Serialize, serde::Deserialize),
107 reflect(Serialize, Deserialize)
108)]
109pub enum FocusPolicy {
110 Block,
112 Pass,
114}
115
116impl FocusPolicy {
117 const DEFAULT: Self = Self::Pass;
118}
119
120impl Default for FocusPolicy {
121 fn default() -> Self {
122 Self::DEFAULT
123 }
124}
125
126#[derive(Default)]
128pub struct State {
129 entities_to_reset: SmallVec<[Entity; 1]>,
130}
131
132#[derive(QueryData)]
134#[query_data(mutable)]
135pub struct NodeQuery {
136 entity: Entity,
137 node: &'static ComputedNode,
138 transform: &'static UiGlobalTransform,
139 interaction: Option<&'static mut Interaction>,
140 relative_cursor_position: Option<&'static mut RelativeCursorPosition>,
141 focus_policy: Option<&'static FocusPolicy>,
142 inherited_visibility: Option<&'static InheritedVisibility>,
143 target_camera: &'static ComputedUiTargetCamera,
144}
145
146pub fn ui_focus_system(
150 mut state: Local<State>,
151 camera_query: Query<(Entity, &Camera)>,
152 primary_window: Query<Entity, With<PrimaryWindow>>,
153 windows: Query<&Window>,
154 mouse_button_input: Res<ButtonInput<MouseButton>>,
155 touches_input: Res<Touches>,
156 ui_stack: Res<UiStack>,
157 mut node_query: Query<NodeQuery>,
158 clipping_query: Query<(&ComputedNode, &UiGlobalTransform, &Node)>,
159 child_of_query: Query<&ChildOf, Without<OverrideClip>>,
160) {
161 let primary_window = primary_window.iter().next();
162
163 for entity in state.entities_to_reset.drain(..) {
165 if let Ok(NodeQueryItem {
166 interaction: Some(mut interaction),
167 ..
168 }) = node_query.get_mut(entity)
169 {
170 *interaction = Interaction::None;
171 }
172 }
173
174 let mouse_released =
175 mouse_button_input.just_released(MouseButton::Left) || touches_input.any_just_released();
176 if mouse_released {
177 for node in &mut node_query {
178 if let Some(mut interaction) = node.interaction
179 && *interaction == Interaction::Pressed
180 {
181 *interaction = Interaction::None;
182 }
183 }
184 }
185
186 let mouse_clicked =
187 mouse_button_input.just_pressed(MouseButton::Left) || touches_input.any_just_pressed();
188
189 let camera_cursor_positions: HashMap<Entity, Vec2> = camera_query
190 .iter()
191 .filter_map(|(entity, camera)| {
192 let Some(NormalizedRenderTarget::Window(window_ref)) =
194 camera.target.normalize(primary_window)
195 else {
196 return None;
197 };
198 let window = windows.get(window_ref.entity()).ok()?;
199
200 let viewport_position = camera
201 .physical_viewport_rect()
202 .map(|rect| rect.min.as_vec2())
203 .unwrap_or_default();
204 window
205 .physical_cursor_position()
206 .or_else(|| {
207 touches_input
208 .first_pressed_position()
209 .map(|pos| pos * window.scale_factor())
210 })
211 .map(|cursor_position| (entity, cursor_position - viewport_position))
212 })
213 .collect();
214
215 let mut hovered_nodes = ui_stack
219 .uinodes
220 .iter()
221 .rev()
223 .filter_map(|entity| {
224 let Ok(node) = node_query.get_mut(*entity) else {
225 return None;
226 };
227
228 let inherited_visibility = node.inherited_visibility?;
229 if !inherited_visibility.get() {
231 if let Some(mut interaction) = node.interaction {
233 interaction.set_if_neq(Interaction::None);
235 }
236 return None;
237 }
238 let camera_entity = node.target_camera.get()?;
239
240 let cursor_position = camera_cursor_positions.get(&camera_entity);
241
242 let contains_cursor = cursor_position.is_some_and(|point| {
243 node.node.contains_point(*node.transform, *point)
244 && clip_check_recursive(*point, *entity, &clipping_query, &child_of_query)
245 });
246
247 let normalized_cursor_position = cursor_position.and_then(|cursor_position| {
251 node.node.normalize_point(*node.transform, *cursor_position)
255 });
256
257 let relative_cursor_position_component = RelativeCursorPosition {
260 cursor_over: contains_cursor,
261 normalized: normalized_cursor_position,
262 };
263
264 if let Some(mut node_relative_cursor_position_component) = node.relative_cursor_position
266 {
267 node_relative_cursor_position_component
269 .set_if_neq(relative_cursor_position_component);
270 }
271
272 if contains_cursor {
273 Some(*entity)
274 } else {
275 if let Some(mut interaction) = node.interaction
276 && (*interaction == Interaction::Hovered
277 || (normalized_cursor_position.is_none()))
278 {
279 interaction.set_if_neq(Interaction::None);
280 }
281 None
282 }
283 })
284 .collect::<Vec<Entity>>()
285 .into_iter();
286
287 let mut iter = node_query.iter_many_mut(hovered_nodes.by_ref());
290 while let Some(node) = iter.fetch_next() {
291 if let Some(mut interaction) = node.interaction {
292 if mouse_clicked {
293 if *interaction != Interaction::Pressed {
295 *interaction = Interaction::Pressed;
296 if mouse_released {
299 state.entities_to_reset.push(node.entity);
300 }
301 }
302 } else if *interaction == Interaction::None {
303 *interaction = Interaction::Hovered;
304 }
305 }
306
307 match node.focus_policy.unwrap_or(&FocusPolicy::Block) {
308 FocusPolicy::Block => {
309 break;
310 }
311 FocusPolicy::Pass => { }
312 }
313 }
314 let mut iter = node_query.iter_many_mut(hovered_nodes);
317 while let Some(node) = iter.fetch_next() {
318 if let Some(mut interaction) = node.interaction {
319 if *interaction != Interaction::Pressed {
321 interaction.set_if_neq(Interaction::None);
322 }
323 }
324 }
325}
326
327pub fn clip_check_recursive(
330 point: Vec2,
331 entity: Entity,
332 clipping_query: &Query<'_, '_, (&ComputedNode, &UiGlobalTransform, &Node)>,
333 child_of_query: &Query<&ChildOf, Without<OverrideClip>>,
334) -> bool {
335 if let Ok(child_of) = child_of_query.get(entity) {
336 let parent = child_of.0;
337 if let Ok((computed_node, transform, node)) = clipping_query.get(parent)
338 && !computed_node
339 .resolve_clip_rect(node.overflow, node.overflow_clip_margin)
340 .contains(transform.inverse().transform_point2(point))
341 {
342 return false;
344 }
345 return clip_check_recursive(point, parent, clipping_query, child_of_query);
346 }
347 true
349}