1 module rpui.widget_locator;
2 
3 import std.math;
4 
5 import rpui.primitives;
6 import gapi.vec;
7 
8 import rpui.widget;
9 
10 package final class WidgetLocator {
11     private Widget holder;
12 
13     this(Widget widget) {
14         this.holder = widget;
15     }
16 
17     void updateAbsolutePosition() {
18         with (holder) {
19             vec2 res = vec2(0, 0);
20             Widget lastParent = parent;
21 
22             while (lastParent !is null) {
23                 res += lastParent.position - lastParent.contentOffset;
24                 res += lastParent.innerOffsetStart + lastParent.outerOffsetStart;
25                 lastParent = lastParent.parent;
26             }
27 
28             absolutePosition_ = position + res + outerOffsetStart;
29             absolutePosition_.x = round(absolutePosition_.x);
30             absolutePosition_.y = round(absolutePosition_.y);
31         }
32     }
33 
34     void updateLocationAlign() {
35         with (holder) {
36             switch (locationAlign) {
37                 case Align.left:
38                     position.x = 0;
39                     break;
40 
41                 case Align.right:
42                     position.x = parent.innerBoundarySizeClamped.x - size.x - outerOffsetSize.x;
43                     break;
44 
45                 case Align.center:
46                     const halfSize = (parent.innerBoundarySizeClamped.x - size.x - outerOffsetSize.x) / 2;
47                     position.x = round(halfSize);
48                     break;
49 
50                 default:
51                     break;
52             }
53         }
54     }
55 
56     void updateVerticalLocationAlign() {
57         with (holder) {
58             switch (verticalLocationAlign) {
59                 case VerticalAlign.top:
60                     position.y = 0;
61                     break;
62 
63                 case VerticalAlign.bottom:
64                     position.y = parent.innerBoundarySizeClamped.y - size.y - outerOffsetSize.y;
65                     break;
66 
67                 case VerticalAlign.middle:
68                     const halfSize = (parent.innerBoundarySizeClamped.y - size.y - outerOffsetSize.y) / 2;
69                     position.y = round(halfSize);
70                     break;
71 
72                 default:
73                     break;
74             }
75         }
76     }
77 
78     void updateRegionAlign() {
79         with (holder) {
80             if (regionAlign == RegionAlign.none)
81                 return;
82 
83             const FrameRect region = locator.findRegion();
84             const vec2 regionSize = vec2(
85                 parent.innerSize.x - region.right  - region.left - outerOffsetSize.x,
86                 parent.innerSize.y - region.bottom - region.top  - outerOffsetSize.y
87             );
88 
89             const vec2 fullRegionSize = vec2(
90                 parent.size.x - region.right  - region.left - outerOffsetSize.x - parent.innerOffsetSize.x,
91                 parent.size.y - region.bottom - region.top  - outerOffsetSize.y - parent.innerOffsetSize.y
92             );
93 
94             outerBoundarySize = size;
95 
96             switch (regionAlign) {
97                 case RegionAlign.client:
98                     size = regionSize;
99                     outerBoundarySize = fullRegionSize;
100                     position = vec2(region.left, region.top);
101                     break;
102 
103                 case RegionAlign.top:
104                     size.x = regionSize.x;
105                     outerBoundarySize.x = fullRegionSize.x;
106                     position = vec2(region.left, region.top);
107                     break;
108 
109                 case RegionAlign.bottom:
110                     size.x = regionSize.x;
111                     outerBoundarySize.x = fullRegionSize.x;
112                     position.x = region.left;
113                     position.y = parent.innerSize.y - outerSize.y - region.bottom;
114                     break;
115 
116                 case RegionAlign.left:
117                     size.y = regionSize.y;
118                     outerBoundarySize.y = fullRegionSize.y;
119                     position = vec2(region.left, region.top);
120                     break;
121 
122                 case RegionAlign.right:
123                     size.y = regionSize.y;
124                     outerBoundarySize.y = fullRegionSize.y;
125                     position.x = parent.innerSize.x - outerSize.x - region.right;
126                     position.y = region.top;
127                     break;
128 
129                 default:
130                     break;
131             }
132         }
133     }
134 
135     FrameRect findRegion() {
136         FrameRect region;
137 
138         foreach (Widget widget; holder.parent.children) {
139             if (widget == holder)
140                 break;
141 
142             if (!widget.isVisible || widget.regionAlign == RegionAlign.none)
143                 continue;
144 
145             switch (widget.regionAlign) {
146                 case RegionAlign.top:
147                     region.top += widget.size.y + widget.outerOffset.bottom;
148                     break;
149 
150                 case RegionAlign.left:
151                     region.left += widget.size.x + widget.outerOffset.right;
152                     break;
153 
154                 case RegionAlign.bottom:
155                     region.bottom += widget.size.y + widget.outerOffset.top;
156                     break;
157 
158                 case RegionAlign.right:
159                     region.right += widget.size.x + widget.outerOffset.left;
160                     break;
161 
162                 default:
163                     continue;
164             }
165         }
166 
167         return region;
168     }
169 }