1 /**
2     Dropdown list
3 
4     Copyright: (c) Enalye 2017
5     License: Zlib
6     Authors: Enalye
7 */
8 
9 module atelier.ui.list.dropdownlist;
10 
11 import std.conv : to;
12 import std.algorithm : min, max;
13 import atelier.core, atelier.render, atelier.common;
14 import atelier.ui.gui_element, atelier.ui.gui_overlay, atelier.ui.list.vlist,
15     atelier.ui.label, atelier.ui.button;
16 
17 private class DropDownListCancelTrigger : GuiElement {
18     override void onSubmit() {
19         triggerCallback();
20     }
21 }
22 
23 private class DropDownListSubElement : Button {
24     Label label;
25 
26     this(string title, Vec2f sz) {
27         size = sz;
28         label = new Label(title);
29         label.setAlign(GuiAlignX.center, GuiAlignY.center);
30         if ((label.size.x + 20) > size.x) {
31             size = Vec2f(label.size.x + 20, size.y);
32         }
33         appendChild(label);
34     }
35 
36     override void draw() {
37         drawFilledRect(origin, size, isHovered ? Color.gray : Color.black);
38     }
39 }
40 
41 /// A clickable button that deploy a list of choices.
42 class DropDownList : GuiElement {
43     private {
44         VList _list;
45         Label _label;
46         DropDownListCancelTrigger _cancelTrigger;
47         bool _isUnrolled = false;
48         uint _maxListLength = 5;
49         float _maxWidth = 5f;
50         Timer _timer;
51     }
52 
53     @property {
54         /// The ID of the currently selected child.
55         uint selected() const {
56             return _list.selected;
57         }
58         /// Ditto
59         uint selected(uint id) {
60             return _list.selected = id;
61         }
62 
63         /// The list of all its children.
64         override const(GuiElement[]) children() const {
65             return _list.children;
66         }
67         /// Ditto
68         override GuiElement[] children() {
69             return _list.children;
70         }
71 
72         /// Return the first child gui.
73         override GuiElement firstChild() {
74             return _list.firstChild;
75         }
76 
77         /// Return the last child gui.
78         override GuiElement lastChild() {
79             return _list.lastChild;
80         }
81 
82         /// The number of children it currently has.
83         override size_t childCount() const {
84             return _list.childCount;
85         }
86     }
87 
88     /// Size is used for the canvas, avoid resizing too often. \
89     /// maxListLength is the maximum number of choices that can be displayed at the same time.
90     this(Vec2f newSize, uint maxListLength = 5U) {
91         _maxListLength = maxListLength;
92         size = newSize;
93         hasCanvas(true);
94         _maxWidth = max(size.x, _maxWidth);
95 
96         _list = new VList(Vec2f(_maxWidth, _maxListLength * size.x));
97         _list.setAlign(GuiAlignX.left, GuiAlignY.top);
98 
99         _cancelTrigger = new DropDownListCancelTrigger;
100         _cancelTrigger.setAlign(GuiAlignX.left, GuiAlignY.top);
101         _cancelTrigger.size = size;
102         _cancelTrigger.setCallback(this, "cancel");
103 
104         _label = new Label;
105         _label.setAlign(GuiAlignX.center, GuiAlignY.center);
106 
107         _timer.mode = Timer.Mode.bounce;
108         _timer.start(2f);
109         super.appendChild(_label);
110     }
111 
112     override void onSubmit() {
113         if (!isLocked) {
114             _isUnrolled = !_isUnrolled;
115 
116             if (_isUnrolled) {
117                 setOverlay(_cancelTrigger);
118                 setOverlay(_list);
119             }
120             else {
121                 stopOverlay();
122                 triggerCallback();
123             }
124         }
125     }
126 
127     override void onCallback(string id) {
128         if (id == "cancel") {
129             _isUnrolled = false;
130             stopOverlay();
131             triggerCallback();
132         }
133     }
134 
135     override void update(float deltaTime) {
136         _timer.update(deltaTime);
137         if (_label.size.x > size.x) {
138             _label.setAlign(GuiAlignX.left, GuiAlignY.center);
139             _label.position = Vec2f(lerp(-(_label.size.x - size.x), 0f,
140                     easeInOutSine(_timer.value01)), 0f);
141         }
142         else {
143             _label.setAlign(GuiAlignX.center, GuiAlignY.center);
144             _label.position = Vec2f.zero;
145         }
146 
147         if (_isUnrolled) {
148             _list.update(deltaTime);
149         }
150     }
151 
152     override void drawOverlay() {
153         if (_isUnrolled) {
154             _cancelTrigger.position = origin;
155             _list.position = origin + Vec2f(0f, _size.y);
156 
157             int id;
158             foreach (gui; _list.children) {
159                 if (gui.hasFocus) {
160                     _isUnrolled = false;
161                     selected = id;
162 
163                     stopOverlay();
164                     triggerCallback();
165                 }
166                 id++;
167             }
168         }
169     }
170 
171     override void draw() {
172         super.draw();
173         auto guis = _list.children;
174         if (guis.length > _list.selected) {
175             auto gui = cast(DropDownListSubElement)(guis[_list.selected]);
176             _label.text = gui.label.text;
177         }
178         drawFilledRect(origin, size, Color.black);
179         drawRect(origin, size, Color.white);
180     }
181 
182     protected override void appendChild(GuiElement gui) {
183         float width = size.x;
184         _list.appendChild(gui);
185         auto guis = _list.children;
186         foreach (child; guis) {
187             width = max(child.size.x, width);
188         }
189         foreach (child; guis) {
190             child.size = Vec2f(width, child.size.y);
191         }
192         _list.size = Vec2f(width, min(_maxListLength, guis.length) * size.y);
193     }
194 
195     /// Add a choice to the list. \
196     /// Use this instead of appendChild unless you want to define your own.
197     void add(string msg) {
198         auto gui = new DropDownListSubElement(msg, size);
199         appendChild(gui);
200     }
201 
202     override void removeChildren() {
203         _list.removeChildren();
204     }
205 
206     override void removeChild(size_t id) {
207         _list.removeChild(id);
208     }
209 
210     override void removeChild(GuiElement gui) {
211         _list.removeChild(gui);
212     }
213 
214     /// Returns the name of the selected choice.
215     string getSelectedName() {
216         auto list = cast(DropDownListSubElement[]) children;
217         if (selected() >= list.length)
218             return "";
219         return list[selected()].label.text;
220     }
221 
222     /// Change the name of the selected choice.
223     void setSelectedName(string name) {
224         auto list = cast(DropDownListSubElement[]) children;
225         int i;
226         foreach (btn; list) {
227             if (btn.label.text == name) {
228                 selected(i);
229                 triggerCallback();
230                 return;
231             }
232             i++;
233         }
234     }
235 }