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 }