1 module glui.scroll;
2 
3 import raylib;
4 
5 import std.meta;
6 import std.conv;
7 import std.algorithm;
8 
9 import glui.node;
10 import glui.frame;
11 import glui.space;
12 import glui.utils;
13 import glui.input;
14 import glui.style;
15 import glui.structs;
16 import glui.scrollbar;
17 
18 private extern(C) float GetMouseWheelMove();
19 
20 alias GluiScrollFrame = GluiScrollable!GluiFrame;
21 alias vscrollFrame = simpleConstructor!GluiScrollFrame;
22 
23 @safe:
24 
25 GluiScrollFrame hscrollFrame(Args...)(Args args) {
26 
27     auto scroll = vscrollFrame(args);
28     scroll.directionHorizontal = true;
29     return scroll;
30 
31 }
32 
33 /// Implement scrolling for the given frame.
34 ///
35 /// This only supports scrolling in one side.
36 class GluiScrollable(T : GluiFrame) : T {
37 
38     mixin DefineStyles;
39 
40     // TODO: move keyboard input to GluiScrollBar.
41 
42     public {
43 
44         /// Scrollbar for the frame. Can be replaced with a customzed one.
45         GluiScrollBar scrollBar;
46 
47     }
48 
49     private {
50 
51         /// minSize including the padding.
52         Vector2 paddingBoxSize;
53 
54     }
55 
56     this(T...)(T args) {
57 
58         super(args);
59         this.scrollBar = .vscrollBar();
60 
61     }
62 
63     @property {
64 
65         size_t scroll() const {
66 
67             return scrollBar.position;
68 
69         }
70 
71         size_t scroll(size_t value) {
72 
73             return scrollBar.position = value;
74 
75         }
76 
77     }
78 
79     /// Scroll to the beginning of the node.
80     void scrollStart() {
81 
82         scroll = 0;
83 
84     }
85 
86     /// Scroll to the end of the node, requires the node to be drawn at least once.
87     void scrollEnd() {
88 
89         scroll = scrollMax;
90 
91     }
92 
93     /// Set the scroll to a value clamped between start and end.
94     void setScroll(ptrdiff_t value) {
95 
96         scrollBar.setScroll(value);
97 
98     }
99 
100     /// Get the maximum value this container can be scrolled to. Requires at least one draw.
101     size_t scrollMax() const {
102 
103         return scrollBar.scrollMax();
104 
105     }
106 
107     override void resizeImpl(Vector2 space) {
108 
109         assert(scrollBar !is null, "No scrollbar has been set for GluiScrollable");
110         assert(theme !is null);
111         assert(tree !is null);
112 
113         /// Padding represented as a vector. This sums the padding on each axis.
114         const paddingVector = Vector2(style.padding.sideX[].sum, style.padding.sideY[].sum);
115 
116         /// Space with padding included
117         const paddingSpace = space + paddingVector;
118 
119         // Resize the scrollbar
120         with (scrollBar) {
121 
122             horizontal = this.directionHorizontal;
123             layout = .layout!(1, "fill");
124             resize(this.tree, this.theme, paddingSpace);
125 
126         }
127 
128         /// Space without the scrollbar
129         const contentSpace = directionHorizontal
130             ? space - Vector2(0, scrollBar.minSize.y)
131             : space - Vector2(scrollBar.minSize.x, 0);
132 
133         // Resize the frame while reserving some space for the scrollbar
134         super.resizeImpl(contentSpace);
135 
136         // Calculate the expected padding box size
137         paddingBoxSize = minSize + paddingVector;
138 
139         // Set scrollbar size and add the scrollbar to the result
140         if (directionHorizontal) {
141 
142             scrollBar.availableSpace = cast(size_t) paddingBoxSize.x;
143             minSize.y += scrollBar.minSize.y;
144 
145         }
146 
147         else {
148 
149             scrollBar.availableSpace = cast(size_t) paddingBoxSize.y;
150             minSize.x += scrollBar.minSize.x;
151 
152         }
153 
154     }
155 
156     override void drawImpl(Rectangle outer, Rectangle inner) {
157 
158         // Note: Mouse input detection is primitive, awaiting #13 and #14 to help better identify when should the mouse
159         // affect this frame.
160 
161         // This node doesn't use GluiInput because it doesn't take focus, and we don't want to cause related
162         // accessibility issues. It can function perfectly without it, or at least until above note gets fixed.
163         // Then, a "GluiHoverable" interface could possibly become a thing.
164 
165         // TODO Is the above still true?
166 
167         scrollBar.horizontal = directionHorizontal;
168 
169         auto scrollBarRect = outer;
170 
171         if (hovered) inputImpl();
172 
173         // Scroll the given rectangle horizontally
174         if (directionHorizontal) {
175 
176             // Calculate fake box sizes
177             outer.width = max(outer.width, paddingBoxSize.x);
178             inner = style.contentBox(outer);
179 
180             static foreach (rect; AliasSeq!(outer, inner)) {
181 
182                 // Perform the scroll
183                 rect.x -= scroll;
184 
185                 // Reduce both rects by scrollbar size
186                 rect.height -= scrollBar.minSize.y;
187 
188             }
189 
190             scrollBarRect.y += outer.height;
191             scrollBarRect.height = scrollBar.minSize.y;
192 
193         }
194 
195         // Vertically
196         else {
197 
198             // Calculate fake box sizes
199             outer.height = max(outer.height, paddingBoxSize.y);
200             inner = style.contentBox(outer);
201 
202             static foreach (rect; AliasSeq!(outer, inner)) {
203 
204                 // Perform the scroll
205                 rect.y -= scroll;
206 
207                 // Reduce both rects by scrollbar size
208                 rect.width -= scrollBar.minSize.x;
209 
210             }
211 
212             scrollBarRect.x += outer.width;
213             scrollBarRect.width = scrollBar.minSize.x;
214 
215         }
216 
217         // Draw the scrollbar
218         scrollBar.draw(scrollBarRect);
219 
220         // Draw the frame
221         super.drawImpl(outer, inner);
222 
223     }
224 
225     /// Implementation of mouse input
226     private void inputImpl() @trusted {
227 
228         const float move = -GetMouseWheelMove;
229         const float totalChange = move * scrollBar.scrollSpeed;
230 
231         scrollBar.setScroll(scroll.to!ptrdiff_t + totalChange.to!ptrdiff_t);
232 
233     }
234 
235 }