1use super::{
2 XYFocusAlgorithms, XYFocusHelpers, XYFocusManifolds, XYFocusNavigationModes, XYFocusNavigationStrategy,
3 XYFocusOptions,
4};
5use crate::input::{FocusManager, InputElement, KeyDeviceType, NavigationDirection};
6use crate::media::FlowDirection;
7use crate::utilities::MathUtilities;
8use crate::{
9 ferro_property, AttachedProperty, FerroProperty, Rect, Ref, StyledPropertyOptions, Visual,
10};
11use std::cell::{Cell, RefCell};
12use std::cmp::Ordering;
13
14type Element = Ref<InputElement>;
15
16struct XYFocusParams {
18 element: Element,
19 bounds: Rect,
20 score: f64,
21}
22
23crate::ferro_thread_local! {
24 static INSTANCE: XYFocus = XYFocus::new();
25}
26
27pub struct XYFocus {
30 manifolds: Cell<XYFocusManifolds>,
31 pooled_candidates: RefCell<Vec<XYFocusParams>>,
32}
33
34crate::ferro_static_type!(XYFocus);
35
36macro_rules! override_property {
37 ($(#[$meta:meta])* $property:ident, $name:literal, $get:ident, $set:ident) => {
38 ferro_property!(for XYFocus;
39 $(#[$meta])*
40 pub fn $property() -> AttachedProperty<Option<Ref<InputElement>>> {
41 FerroProperty::register_attached::<XYFocus, InputElement, _>($name, None)
42 }
43 );
44
45 pub fn $set(obj: &InputElement, value: Option<Ref<InputElement>>) {
46 obj.set_value(Self::$property(), value)
47 }
48
49 pub fn $get(obj: &InputElement) -> Option<Ref<InputElement>> {
50 obj.get_value(Self::$property())
51 }
52 };
53}
54
55macro_rules! strategy_property {
56 ($(#[$meta:meta])* $property:ident, $name:literal, $get:ident, $set:ident) => {
57 ferro_property!(for XYFocus;
58 $(#[$meta])*
59 pub fn $property() -> AttachedProperty<XYFocusNavigationStrategy> {
60 FerroProperty::register_attached_with::<XYFocus, InputElement, _>(
61 $name,
62 StyledPropertyOptions::new(XYFocusNavigationStrategy::Auto).inherits(true),
63 )
64 }
65 );
66
67 pub fn $set(obj: &InputElement, value: XYFocusNavigationStrategy) {
68 obj.set_value(Self::$property(), value)
69 }
70
71 pub fn $get(obj: &InputElement) -> XYFocusNavigationStrategy {
72 obj.get_value(Self::$property())
73 }
74 };
75}
76
77crate::ferro_properties! {
78 impl XYFocus, also [
79 XYFocus::down_property,
80 XYFocus::left_property,
81 XYFocus::right_property,
82 XYFocus::up_property,
83 XYFocus::down_navigation_strategy_property,
84 XYFocus::up_navigation_strategy_property,
85 XYFocus::left_navigation_strategy_property,
86 XYFocus::right_navigation_strategy_property,
87 XYFocus::navigation_modes_property,
88 XYFocus::is_focus_engagement_enabled_property,
89 XYFocus::is_focus_engaged_property,
90 ] {}
91}
92
93impl XYFocus {
94 override_property!(
97 down_property, "Down", get_down, set_down
99 );
100
101 override_property!(
102 left_property, "Left", get_left, set_left
104 );
105
106 override_property!(
107 right_property, "Right", get_right, set_right
109 );
110
111 override_property!(
112 up_property, "Up", get_up, set_up
114 );
115
116 strategy_property!(
117 down_navigation_strategy_property, "DownNavigationStrategy",
120 get_down_navigation_strategy, set_down_navigation_strategy
121 );
122
123 strategy_property!(
124 up_navigation_strategy_property, "UpNavigationStrategy",
127 get_up_navigation_strategy, set_up_navigation_strategy
128 );
129
130 strategy_property!(
131 left_navigation_strategy_property, "LeftNavigationStrategy",
134 get_left_navigation_strategy, set_left_navigation_strategy
135 );
136
137 strategy_property!(
138 right_navigation_strategy_property, "RightNavigationStrategy",
141 get_right_navigation_strategy, set_right_navigation_strategy
142 );
143
144 ferro_property!(for XYFocus;
145 pub fn navigation_modes_property() -> AttachedProperty<XYFocusNavigationModes> {
148 FerroProperty::register_attached_with::<XYFocus, InputElement, _>(
149 "NavigationModes",
150 StyledPropertyOptions::new(XYFocusNavigationModes::GAMEPAD | XYFocusNavigationModes::REMOTE)
151 .inherits(true),
152 )
153 }
154 );
155
156 pub fn set_navigation_modes(obj: &InputElement, value: XYFocusNavigationModes) {
157 obj.set_value(Self::navigation_modes_property(), value)
158 }
159
160 pub fn get_navigation_modes(obj: &InputElement) -> XYFocusNavigationModes {
161 obj.get_value(Self::navigation_modes_property())
162 }
163
164 ferro_property!(for XYFocus;
165 pub(crate) fn is_focus_engagement_enabled_property() -> AttachedProperty<bool> {
166 FerroProperty::register_attached::<XYFocus, InputElement, _>("IsFocusEngagementEnabled", false)
167 }
168 );
169
170 #[allow(dead_code)]
171 pub(crate) fn set_is_focus_engagement_enabled(obj: &InputElement, value: bool) {
172 obj.set_value(Self::is_focus_engagement_enabled_property(), value)
173 }
174
175 pub(crate) fn get_is_focus_engagement_enabled(obj: &InputElement) -> bool {
176 obj.get_value(Self::is_focus_engagement_enabled_property())
177 }
178
179 ferro_property!(for XYFocus;
180 pub(crate) fn is_focus_engaged_property() -> AttachedProperty<bool> {
181 FerroProperty::register_attached_with::<XYFocus, Visual, _>(
182 "IsFocusEngaged",
183 StyledPropertyOptions::new(false).coerce(|sender, value| {
184 value
185 && sender
186 .downcast_ref::<InputElement>()
187 .is_some_and(XYFocus::get_is_focus_engagement_enabled)
188 }),
189 )
190 }
191 );
192
193 #[allow(dead_code)]
194 pub(crate) fn set_is_focus_engaged(obj: &Visual, value: bool) {
195 obj.set_value(Self::is_focus_engaged_property(), value)
196 }
197
198 pub(crate) fn get_is_focus_engaged(obj: &Visual) -> bool {
199 obj.get_value(Self::is_focus_engaged_property())
200 }
201
202 fn find_elements(
205 focus_list: &mut Vec<XYFocusParams>,
206 start_root: &InputElement,
207 current_element: Option<&Element>,
208 active_scroller: Option<&Element>,
209 ignore_clipping: bool,
210 input_key_device_type: Option<KeyDeviceType>,
211 ) {
212 let is_scrolling = active_scroller.is_some();
213 let Some(collection) = start_root.visual_children_snapshot() else { return };
214
215 for child in collection.iter() {
216 let Some(child) = child.cast::<InputElement>() else { continue };
217
218 let is_engagement_enabled_but_not_engaged =
219 Self::get_is_focus_engagement_enabled(&child) && !Self::get_is_focus_engaged(&child);
220
221 if Some(&child) != current_element && Self::is_valid_candidate(&child, input_key_device_type) {
222 if let Some(bounds) = Self::get_bounds_for_ranking(&child, ignore_clipping) {
223 if is_scrolling {
224 if Self::is_candidate_participating_in_scroll(&child, active_scroller)
225 || !Self::is_occluded(&child, bounds)
226 || Self::is_candidate_child_of_ancestor_scroller(&child, active_scroller)
227 {
228 focus_list.push(XYFocusParams { element: child.clone(), bounds, score: 0.0 });
229 }
230 } else {
231 focus_list.push(XYFocusParams { element: child.clone(), bounds, score: 0.0 });
232 }
233 }
234 }
235
236 if Self::is_valid_focus_subtree(&child) && !is_engagement_enabled_but_not_engaged {
237 Self::find_elements(
238 focus_list,
239 &child,
240 current_element,
241 active_scroller,
242 ignore_clipping,
243 input_key_device_type,
244 );
245 }
246 }
247 }
248
249 fn is_valid_focus_subtree(candidate: &InputElement) -> bool {
250 candidate.is_visible() && candidate.is_enabled()
253 }
254
255 fn is_valid_candidate(candidate: &InputElement, input_key_device_type: Option<KeyDeviceType>) -> bool {
256 candidate.focusable()
257 && candidate.is_effectively_enabled()
258 && candidate.is_effectively_visible()
259 && XYFocusHelpers::is_allowed_xy_navigation_mode(candidate, input_key_device_type)
262 }
263
264 fn is_candidate_participating_in_scroll(candidate: &Element, active_scroller: Option<&Element>) -> bool {
265 let Some(active_scroller) = active_scroller else { return false };
266
267 let closest_scroller = candidate.find_ancestor_of_type_where::<InputElement>(true, |e| e.as_scrollable().is_some());
268 closest_scroller.as_ref() == Some(active_scroller)
269 }
270
271 fn is_candidate_child_of_ancestor_scroller(candidate: &Element, active_scroller: Option<&Element>) -> bool {
272 let Some(active_scroller) = active_scroller else { return false };
273
274 let mut parent = active_scroller.parent();
275 while let Some(current) = parent {
276 if let Some(element) = current.downcast_ref::<InputElement>() {
277 if element.as_scrollable().is_some() && element.is_visual_ancestor_of(candidate) {
278 return true;
279 }
280 }
281 parent = current.parent();
282 }
283
284 false
285 }
286
287 fn is_occluded(element: &InputElement, element_bounds: Rect) -> bool {
288 let Some(root) = element.visual_root() else { return true };
289
290 let visible_bounds = Rect::new(0.0, 0.0, root.bounds().width, root.bounds().height);
292
293 !visible_bounds.intersects(element_bounds)
294 }
295
296 pub(crate) fn get_bounds_for_ranking(element: &InputElement, ignore_clipping: bool) -> Option<Rect> {
297 element.get_transformed_bounds().map(|bounds| {
298 if ignore_clipping {
299 bounds.bounds.transform_to_aabb(bounds.transform)
300 } else {
301 bounds.clip
302 }
303 })
304 }
305
306 fn get_direction_override(
309 element: &InputElement,
310 search_root: Option<&Element>,
311 direction: NavigationDirection,
312 ignore_focusability: bool,
313 ) -> Option<Element> {
314 let index = Self::get_xy_focus_property_index(element, direction)?;
315 let override_element = element.get_value(index)?;
316
317 if !ignore_focusability && !FocusManager::can_focus(&override_element) {
318 return None;
319 }
320
321 if let Some(search_root) = search_root {
324 if !search_root.is_visual_ancestor_of(&override_element) {
325 return None;
326 }
327 }
328
329 Some(override_element)
330 }
331
332 fn try_xy_focus_bubble(
333 element: &Element,
334 candidate: Option<Element>,
335 search_root: Option<&Element>,
336 direction: NavigationDirection,
337 ) -> Option<Element> {
338 let candidate = candidate?;
339 let mut next_focusable_element = candidate.clone();
340
341 if let Some(direction_override_root) = Self::get_direction_override_root(element, search_root, direction) {
342 let is_ancestor = direction_override_root.is_visual_ancestor_of(&candidate);
343 if !is_ancestor {
344 if let Some(overridden) =
345 Self::get_direction_override(&direction_override_root, search_root, direction, false)
346 {
347 next_focusable_element = overridden;
348 }
349 }
350 }
351
352 Some(next_focusable_element)
353 }
354
355 fn get_direction_override_root(
356 element: &Element,
357 search_root: Option<&Element>,
358 direction: NavigationDirection,
359 ) -> Option<Element> {
360 let mut root = Some(element.clone());
361
362 while let Some(current) = &root {
363 if Self::get_direction_override(current, search_root, direction, false).is_some() {
364 break;
365 }
366 root = current.get_visual_parent_of_type::<InputElement>();
367 }
368
369 root
370 }
371
372 fn get_strategy(
373 element: &Element,
374 direction: NavigationDirection,
375 navigation_strategy_override: Option<XYFocusNavigationStrategy>,
376 ) -> XYFocusNavigationStrategy {
377 let is_auto_override = navigation_strategy_override == Some(XYFocusNavigationStrategy::Auto);
378 let mut element = element.clone();
379
380 if let Some(strategy) = navigation_strategy_override.filter(|_| !is_auto_override) {
381 return match strategy as i32 - 1 {
384 1 => XYFocusNavigationStrategy::Projection,
385 2 => XYFocusNavigationStrategy::NavigationDirectionDistance,
386 3 => XYFocusNavigationStrategy::RectilinearDistance,
387 _ => XYFocusNavigationStrategy::Auto,
388 };
389 } else if is_auto_override {
390 if let Some(parent) = element.get_visual_parent_of_type::<InputElement>() {
393 element = parent;
394 }
395 }
396
397 if let Some(index) = Self::get_xy_focus_navigation_strategy_property_index(&element, direction) {
398 let mut current = Some(element);
399 while let Some(c) = current {
400 let mode = c.get_value(index);
401 if mode != XYFocusNavigationStrategy::Auto {
402 return mode;
403 }
404
405 current = c.get_visual_parent_of_type::<InputElement>();
406 }
407 }
408
409 XYFocusNavigationStrategy::Projection
410 }
411
412 fn flip_for_flow_direction(element: &InputElement, direction: NavigationDirection) -> NavigationDirection {
413 if element.flow_direction() == FlowDirection::RightToLeft {
414 match direction {
415 NavigationDirection::Left => return NavigationDirection::Right,
416 NavigationDirection::Right => return NavigationDirection::Left,
417 _ => {}
418 }
419 }
420 direction
421 }
422
423 fn get_xy_focus_property_index(
424 element: &InputElement,
425 direction: NavigationDirection,
426 ) -> Option<&'static AttachedProperty<Option<Ref<InputElement>>>> {
427 match Self::flip_for_flow_direction(element, direction) {
428 NavigationDirection::Left => Some(Self::left_property()),
429 NavigationDirection::Right => Some(Self::right_property()),
430 NavigationDirection::Up => Some(Self::up_property()),
431 NavigationDirection::Down => Some(Self::down_property()),
432 _ => None,
433 }
434 }
435
436 fn get_xy_focus_navigation_strategy_property_index(
437 element: &InputElement,
438 direction: NavigationDirection,
439 ) -> Option<&'static AttachedProperty<XYFocusNavigationStrategy>> {
440 match Self::flip_for_flow_direction(element, direction) {
441 NavigationDirection::Left => Some(Self::left_navigation_strategy_property()),
442 NavigationDirection::Right => Some(Self::right_navigation_strategy_property()),
443 NavigationDirection::Up => Some(Self::up_navigation_strategy_property()),
444 NavigationDirection::Down => Some(Self::down_navigation_strategy_property()),
445 _ => None,
446 }
447 }
448
449 pub(crate) fn new() -> Self {
452 Self { manifolds: Cell::new(XYFocusManifolds::new()), pooled_candidates: RefCell::new(Vec::new()) }
453 }
454
455 pub(crate) fn set_manifolds_from_bounds(&self, bounds: Rect) {
456 self.manifolds.set(XYFocusManifolds {
457 v_manifold: (bounds.left(), bounds.right()),
458 h_manifold: (bounds.top(), bounds.bottom()),
459 });
460 }
461
462 pub(crate) fn update_manifolds(
463 &self,
464 direction: NavigationDirection,
465 element_bounds: Rect,
466 candidate: &InputElement,
467 ignore_clipping: bool,
468 ) {
469 let candidate_bounds = Self::get_bounds_for_ranking(candidate, ignore_clipping)
470 .expect("the candidate of a completed focus change has bounds");
471 let mut manifolds = self.manifolds.get();
472 XYFocusAlgorithms::update_manifolds(direction, element_bounds, candidate_bounds, &mut manifolds);
473 self.manifolds.set(manifolds);
474 }
475
476 pub(crate) fn try_directional_focus(
479 direction: NavigationDirection,
480 element: &Element,
481 _owner: Option<&Element>,
482 engaged_control: Option<&Element>,
483 key_device_type: Option<KeyDeviceType>,
484 ) -> Option<Element> {
485 if !XYFocusHelpers::is_allowed_xy_navigation_mode(element, key_device_type) {
486 return None;
487 }
488
489 let bounds = Self::get_bounds_for_ranking(element, true)?;
490 let search_root = XYFocusHelpers::find_xy_search_root(element, key_device_type);
491
492 INSTANCE.with(|instance| {
493 instance.set_manifolds_from_bounds(bounds);
494
495 let mut options = XYFocusOptions::new();
496 options.key_device_type = key_device_type;
497 options.focused_element_bounds = Some(bounds);
498 options.update_manifold = true;
499 options.search_root = Some(search_root);
500
501 instance.get_next_focusable_element(direction, Some(element), engaged_control, true, &options)
502 })
503 }
504
505 pub(crate) fn get_next_focusable_element(
506 &self,
507 direction: NavigationDirection,
508 element: Option<&Element>,
509 engaged_control: Option<&Element>,
510 update_manifolds: bool,
511 xy_focus_options: &XYFocusOptions,
512 ) -> Option<Element> {
513 let element = element?;
514 let root = element.visual_root().and_then(|root| root.downcast::<InputElement>().ok())?;
515
516 let is_right_to_left = element.flow_direction() == FlowDirection::RightToLeft;
517 let mode = Self::get_strategy(element, direction, xy_focus_options.navigation_strategy_override);
518
519 let mut focused_element_bounds =
520 xy_focus_options.focused_element_bounds.expect("FocusedElementBounds needs to be set");
521
522 let search_root = xy_focus_options.search_root.as_ref();
523
524 if let Some(next) = Self::get_direction_override(element, search_root, direction, true) {
525 return Some(next);
526 }
527
528 let active_scroller = Self::get_active_scroller_for_scroll(direction, element);
529 let is_processing_input_for_scroll = active_scroller.is_some();
530 let mut element = Some(element);
531
532 if let Some(hint) = xy_focus_options.focus_hint_rectangle {
533 focused_element_bounds = hint;
534 element = None;
535 }
536
537 let ignore_clipping = xy_focus_options.ignore_clipping;
538 let root_bounds = match (engaged_control, search_root) {
539 (Some(engaged), _) => Self::get_bounds_for_ranking(engaged, ignore_clipping),
540 (None, Some(search_root)) => Self::get_bounds_for_ranking(search_root, ignore_clipping),
541 (None, None) => Self::get_bounds_for_ranking(&root, ignore_clipping),
542 }
543 .unwrap_or_else(|| root.bounds());
544
545 let mut candidate_list = self.pooled_candidates.try_borrow_mut().map(|mut list| std::mem::take(&mut *list)).unwrap_or_default();
548 let mut next_focusable_element = None;
549
550 Self::get_all_valid_focusable_children(
551 &mut candidate_list,
552 &root,
553 element,
554 engaged_control,
555 search_root,
556 active_scroller.as_ref(),
557 ignore_clipping,
558 xy_focus_options.key_device_type,
559 );
560
561 if !candidate_list.is_empty() {
562 let mut max_root_bounds_distance =
563 (root_bounds.right() - root_bounds.left()).max(root_bounds.bottom() - root_bounds.top());
564 max_root_bounds_distance = max_root_bounds_distance.max(Self::get_max_root_bounds_distance(
565 &candidate_list,
566 focused_element_bounds,
567 direction,
568 ));
569
570 self.rank_elements(
571 &mut candidate_list,
572 direction,
573 focused_element_bounds,
574 max_root_bounds_distance,
575 mode,
576 xy_focus_options.exclusion_rect,
577 xy_focus_options.ignore_cone,
578 );
579
580 let ignore_occlusivity = xy_focus_options.ignore_occlusivity || is_processing_input_for_scroll;
581
582 next_focusable_element = self.choose_best_focusable_element_from_list(
586 &mut candidate_list,
587 direction,
588 focused_element_bounds,
589 ignore_clipping,
590 ignore_occlusivity,
591 is_right_to_left,
592 xy_focus_options.update_manifold && update_manifolds,
593 );
594
595 if let Some(element) = element {
596 next_focusable_element =
597 Self::try_xy_focus_bubble(element, next_focusable_element, search_root, direction);
598 }
599 }
600
601 candidate_list.clear();
602 if let Ok(mut pooled) = self.pooled_candidates.try_borrow_mut() {
603 *pooled = candidate_list;
604 }
605
606 next_focusable_element
607 }
608
609 #[allow(clippy::too_many_arguments)]
610 fn choose_best_focusable_element_from_list(
611 &self,
612 score_list: &mut [XYFocusParams],
613 direction: NavigationDirection,
614 bounds: Rect,
615 ignore_clipping: bool,
616 ignore_occlusivity: bool,
617 is_right_to_left: bool,
618 update_manifolds: bool,
619 ) -> Option<Element> {
620 let compare = |a: f64, b: f64| a.partial_cmp(&b).unwrap_or(Ordering::Equal);
621
622 score_list.sort_by(|element_a, element_b| {
623 if element_a.element == element_b.element {
624 return Ordering::Equal;
625 }
626
627 let compared = compare(element_b.score, element_a.score);
628 if compared == Ordering::Equal {
629 let first_bounds = element_a.bounds;
630 let second_bounds = element_b.bounds;
631
632 if first_bounds == second_bounds {
633 Ordering::Equal
634 } else if direction == NavigationDirection::Up || direction == NavigationDirection::Down {
635 if is_right_to_left {
636 compare(second_bounds.left(), first_bounds.left())
637 } else {
638 compare(first_bounds.left(), second_bounds.left())
639 }
640 } else {
641 compare(first_bounds.top(), second_bounds.top())
642 }
643 } else {
644 compared
645 }
646 });
647
648 for param in score_list.iter() {
649 if param.score <= 0.0 {
650 break;
651 }
652
653 let bounds_for_occ_testing = if ignore_clipping {
654 Self::get_bounds_for_ranking(¶m.element, false).unwrap_or(param.bounds)
655 } else {
656 param.bounds
657 };
658
659 if (param.bounds.x - f64::MAX).abs() > MathUtilities::DOUBLE_EPSILON
663 && (ignore_occlusivity || !Self::is_occluded(¶m.element, bounds_for_occ_testing))
664 {
665 if update_manifolds {
666 let mut manifolds = self.manifolds.get();
668 XYFocusAlgorithms::update_manifolds(direction, bounds, param.bounds, &mut manifolds);
669 self.manifolds.set(manifolds);
670 }
671
672 return Some(param.element.clone());
673 }
674 }
675
676 None
677 }
678
679 #[allow(clippy::too_many_arguments)]
680 fn get_all_valid_focusable_children(
681 candidate_list: &mut Vec<XYFocusParams>,
682 start_root: &Element,
683 current_element: Option<&Element>,
684 engaged_control: Option<&Element>,
685 search_scope: Option<&Element>,
686 active_scroller: Option<&Element>,
687 ignore_clipping: bool,
688 input_key_device_type: Option<KeyDeviceType>,
689 ) {
690 let root_for_tree_walk = search_scope.unwrap_or(start_root);
693
694 match engaged_control {
695 None => Self::find_elements(
696 candidate_list,
697 root_for_tree_walk,
698 current_element,
699 active_scroller,
700 ignore_clipping,
701 input_key_device_type,
702 ),
703 Some(engaged_control) => {
704 Self::find_elements(
708 candidate_list,
709 engaged_control,
710 current_element,
711 active_scroller,
712 ignore_clipping,
713 input_key_device_type,
714 );
715
716 if current_element != Some(engaged_control) {
717 if let Some(bounds) = Self::get_bounds_for_ranking(engaged_control, ignore_clipping) {
718 candidate_list.push(XYFocusParams { element: engaged_control.clone(), bounds, score: 0.0 });
719 }
720 }
721 }
722 }
723 }
724
725 #[allow(clippy::too_many_arguments)]
726 fn rank_elements(
727 &self,
728 candidate_list: &mut [XYFocusParams],
729 direction: NavigationDirection,
730 bounds: Rect,
731 max_root_bounds_distance: f64,
732 mode: XYFocusNavigationStrategy,
733 exclusion_bounds: Rect,
734 ignore_cone: bool,
735 ) {
736 let manifolds = self.manifolds.get();
737
738 for candidate in candidate_list.iter_mut() {
739 let candidate_bounds = candidate.bounds;
740
741 if !(exclusion_bounds.intersects(candidate_bounds) || exclusion_bounds.contains_rect(candidate_bounds)) {
742 if mode == XYFocusNavigationStrategy::Projection
743 && XYFocusAlgorithms::should_candidate_be_considered_for_ranking(
744 bounds,
745 candidate_bounds,
746 max_root_bounds_distance,
747 direction,
748 exclusion_bounds,
749 ignore_cone,
750 )
751 {
752 candidate.score = XYFocusAlgorithms::get_score_projection(
753 direction,
754 bounds,
755 candidate_bounds,
756 &manifolds,
757 max_root_bounds_distance,
758 );
759 } else if mode == XYFocusNavigationStrategy::NavigationDirectionDistance
760 || mode == XYFocusNavigationStrategy::RectilinearDistance
761 {
762 candidate.score = XYFocusAlgorithms::get_score_proximity(
763 direction,
764 bounds,
765 candidate_bounds,
766 max_root_bounds_distance,
767 mode == XYFocusNavigationStrategy::RectilinearDistance,
768 );
769 }
770 }
771 }
772 }
773
774 fn get_max_root_bounds_distance(list: &[XYFocusParams], bounds: Rect, direction: NavigationDirection) -> f64 {
775 let mut max_element = &list[0];
776 let mut max_value = f64::MIN;
777
778 for param in list {
779 let candidate_bounds = param.bounds;
780 let value = match direction {
781 NavigationDirection::Left => candidate_bounds.left(),
782 NavigationDirection::Right => candidate_bounds.right(),
783 NavigationDirection::Up => candidate_bounds.top(),
784 NavigationDirection::Down => candidate_bounds.bottom(),
785 _ => 0.0,
786 };
787
788 if value > max_value {
789 max_value = value;
790 max_element = param;
791 }
792 }
793
794 let max_bounds = max_element.bounds;
795 match direction {
796 NavigationDirection::Left => (max_bounds.right() - bounds.left()).abs(),
797 NavigationDirection::Right => (bounds.right() - max_bounds.left()).abs(),
798 NavigationDirection::Up => (bounds.bottom() - max_bounds.top()).abs(),
799 NavigationDirection::Down => (max_bounds.bottom() - bounds.top()).abs(),
800 _ => 0.0,
801 }
802 }
803
804 fn get_active_scroller_for_scroll(direction: NavigationDirection, focused_element: &Element) -> Option<Element> {
805 let mut parent = Some(focused_element.clone());
806
807 while let Some(element) = parent {
808 if let Some(scrollable) = element.as_scrollable() {
809 let is_horizontally_scrollable_for_direction =
810 matches!(direction, NavigationDirection::Left | NavigationDirection::Right)
811 && scrollable.can_horizontally_scroll();
812 let is_vertically_scrollable_for_direction =
813 matches!(direction, NavigationDirection::Up | NavigationDirection::Down)
814 && scrollable.can_vertically_scroll();
815
816 if is_horizontally_scrollable_for_direction || is_vertically_scrollable_for_direction {
817 return Some(element);
818 }
819 }
820
821 parent = element.get_visual_parent_of_type::<InputElement>();
822 }
823
824 None
825 }
826}