1 module rpui.focus_navigator;
2 
3 import rpui.widget;
4 import rpui.widget_events;
5 
6 package final class FocusNavigator {
7     private Widget holder;
8 
9     this (Widget widget) {
10         this.holder = widget;
11     }
12 
13     // NOTE: navFocusFront and navFocusBack are symmetrical
14     // focusNext and focusPrev too therefore potential code reduction.
15     package void navFocusFront() {
16         with (holder) {
17             events.notify(FocusFrontEvent());
18 
19             if (skipFocus && isVisible && firstWidget !is null) {
20                 firstWidget.focusNavigator.navFocusFront();
21             } else {
22                 if (focusable && isVisible) {
23                     focus();
24                 } else {
25                     focusNext();
26                 }
27             }
28         }
29     }
30 
31     /// Focus to the next widget.
32     void focusNext() {
33         with (holder) {
34             if (skipFocus && isFocused) {
35                 navFocusFront();
36                 return;
37             }
38 
39             if (parent.lastWidget != holder) {
40                 holder.nextWidget.focusNavigator.navFocusFront();
41             } else {
42                 if (parent.finalFocus) {
43                     parent.focusNavigator.navFocusFront();
44                 } else {
45                     parent.focusNavigator.focusNext();
46                 }
47             }
48         }
49     }
50 
51     package void navFocusBack() {
52         with (holder) {
53             events.notify(FocusBackEvent());
54 
55             if (skipFocus && isVisible && lastWidget !is null) {
56                 lastWidget.focusNavigator.navFocusBack();
57             } else {
58                 if (focusable && isVisible) {
59                     focus();
60                 } else {
61                     focusPrev();
62                 }
63             }
64         }
65     }
66 
67     /// Focus to the previous widget.
68     void focusPrev() {
69         with (holder) {
70             if (skipFocus && isFocused) {
71                 navFocusBack();
72                 return;
73             }
74 
75             if (parent.firstWidget != holder) {
76                 holder.prevWidget.focusNavigator.navFocusBack();
77             } else {
78                 if (parent.finalFocus) {
79                     parent.focusNavigator.navFocusBack();
80                 } else {
81                     parent.focusNavigator.focusPrev();
82                 }
83             }
84         }
85     }
86 
87     void borderScrollToWidget() {
88         Widget parent = holder.parent;
89 
90         while (parent !is null) {
91             auto scrollable = cast(Scrollable) parent;
92             auto focusScrollNavigation = cast(FocusScrollNavigation) parent;
93             parent = parent.parent;
94 
95             if (scrollable is null)
96                 continue;
97 
98             if (focusScrollNavigation is null) {
99                 scrollable.scrollToWidget(holder);
100             } else {
101                 focusScrollNavigation.borderScrollToWidget(holder);
102             }
103         }
104     }
105 
106     /**
107      * Focus primary element in view.
108      */
109     void focusPrimary() {
110         if (!holder.children.empty)
111             navFocusFront();
112     }
113 }