1 | //************************************ bs::framework - Copyright 2018 Marko Pintera **************************************// |
2 | //*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********// |
3 | #include "GUI/BsGUIDropDownContent.h" |
4 | #include "GUI/BsGUITexture.h" |
5 | #include "GUI/BsGUIButton.h" |
6 | #include "GUI/BsGUILabel.h" |
7 | #include "GUI/BsGUIWidget.h" |
8 | #include "GUI/BsGUIToggle.h" |
9 | #include "GUI/BsGUISkin.h" |
10 | #include "GUI/BsGUIMouseEvent.h" |
11 | #include "GUI/BsGUICommandEvent.h" |
12 | |
13 | #include <climits> |
14 | |
15 | using namespace std::placeholders; |
16 | |
17 | namespace bs |
18 | { |
19 | constexpr const char* GUIDropDownContent::ENTRY_TOGGLE_STYLE_TYPE; |
20 | constexpr const char* GUIDropDownContent::ENTRY_STYLE_TYPE; |
21 | constexpr const char* GUIDropDownContent::ENTRY_EXP_STYLE_TYPE; |
22 | constexpr const char* GUIDropDownContent::SEPARATOR_STYLE_TYPE; |
23 | |
24 | GUIDropDownContent::(GUIDropDownMenu::DropDownSubMenu* parent, const GUIDropDownData& dropDownData, |
25 | const String& style, const GUIDimensions& dimensions) |
26 | : GUIElementContainer(dimensions, style), mDropDownData(dropDownData), mStates(dropDownData.states) |
27 | , mSelectedIdx(UINT_MAX), mRangeStart(0), mRangeEnd(0), mParent(parent), mKeyboardFocus(true) |
28 | , mIsToggle(parent->getType() == GUIDropDownType::MultiListBox) |
29 | { |
30 | |
31 | } |
32 | |
33 | GUIDropDownContent* GUIDropDownContent::(GUIDropDownMenu::DropDownSubMenu* parent, |
34 | const GUIDropDownData& dropDownData, const String& style) |
35 | { |
36 | const String* curStyle = &style; |
37 | if (*curStyle == StringUtil::BLANK) |
38 | curStyle = &GUIDropDownContent::getGUITypeName(); |
39 | |
40 | return new (bs_alloc<GUIDropDownContent>()) GUIDropDownContent(parent, dropDownData, *curStyle, GUIDimensions::create()); |
41 | } |
42 | |
43 | GUIDropDownContent* GUIDropDownContent::(GUIDropDownMenu::DropDownSubMenu* parent, |
44 | const GUIDropDownData& dropDownData, const GUIOptions& options, |
45 | const String& style) |
46 | { |
47 | const String* curStyle = &style; |
48 | if (*curStyle == StringUtil::BLANK) |
49 | curStyle = &GUIDropDownContent::getGUITypeName(); |
50 | |
51 | return new (bs_alloc<GUIDropDownContent>()) GUIDropDownContent(parent, dropDownData, *curStyle, GUIDimensions::create(options)); |
52 | } |
53 | |
54 | void GUIDropDownContent::styleUpdated() |
55 | { |
56 | for (auto& visElem : mVisibleElements) |
57 | { |
58 | GUIDropDownDataEntry& element = mDropDownData.entries[visElem.idx]; |
59 | |
60 | if (element.isSeparator()) |
61 | visElem.separator->setStyle(getSubStyleName(SEPARATOR_STYLE_TYPE)); |
62 | else if (element.isSubMenu()) |
63 | visElem.button->setStyle(getSubStyleName(ENTRY_EXP_STYLE_TYPE)); |
64 | else |
65 | { |
66 | if (mIsToggle) |
67 | visElem.button->setStyle(getSubStyleName(ENTRY_TOGGLE_STYLE_TYPE)); |
68 | else |
69 | visElem.button->setStyle(getSubStyleName(ENTRY_STYLE_TYPE)); |
70 | } |
71 | } |
72 | } |
73 | |
74 | void GUIDropDownContent::setRange(UINT32 start, UINT32 end) |
75 | { |
76 | std::function<void(UINT32, UINT32)> onHover = |
77 | [&](UINT32 idx, UINT32 visIdx) |
78 | { |
79 | setSelected(visIdx); |
80 | mParent->elementSelected(idx); |
81 | }; |
82 | |
83 | std::function<void(UINT32, UINT32)> onClick = |
84 | [&](UINT32 idx, UINT32 visIdx) |
85 | { |
86 | setSelected(visIdx); |
87 | |
88 | if (mIsToggle) |
89 | mStates[idx] = !mStates[idx]; |
90 | |
91 | mParent->elementActivated(idx, mVisibleElements[visIdx].button->_getLayoutData().area); |
92 | }; |
93 | |
94 | // Remove all elements |
95 | while (_getNumChildren() > 0) |
96 | { |
97 | GUIElementBase* child = _getChild(_getNumChildren() - 1); |
98 | assert(child->_getType() == GUIElementBase::Type::Element); |
99 | |
100 | GUIElement::destroy(static_cast<GUIElement*>(child)); |
101 | } |
102 | |
103 | mRangeStart = start; |
104 | mRangeEnd = end; |
105 | |
106 | UINT32 range = end - start; |
107 | if (mSelectedIdx != UINT_MAX && mSelectedIdx >= range) |
108 | mSelectedIdx = UINT_MAX; |
109 | |
110 | mVisibleElements.clear(); |
111 | UINT32 curVisIdx = 0; |
112 | for (UINT32 i = start; i < end; i++) |
113 | { |
114 | mVisibleElements.push_back(VisibleElement()); |
115 | VisibleElement& visElem = mVisibleElements.back(); |
116 | visElem.idx = i; |
117 | GUIDropDownDataEntry& element = mDropDownData.entries[i]; |
118 | |
119 | if (element.isSeparator()) |
120 | { |
121 | visElem.separator = GUITexture::create(TextureScaleMode::StretchToFit, getSubStyleName(SEPARATOR_STYLE_TYPE)); |
122 | _registerChildElement(visElem.separator); |
123 | } |
124 | else if (element.isSubMenu()) |
125 | { |
126 | visElem.button = GUIButton::create(getElementLocalizedName(i), getSubStyleName(ENTRY_EXP_STYLE_TYPE)); |
127 | visElem.button->onHover.connect(std::bind(onClick, i, curVisIdx)); |
128 | _registerChildElement(visElem.button); |
129 | } |
130 | else |
131 | { |
132 | if (mIsToggle) |
133 | { |
134 | GUIToggle* toggle = GUIToggle::create(getElementLocalizedName(i), getSubStyleName(ENTRY_TOGGLE_STYLE_TYPE)); |
135 | if (mStates[i]) |
136 | toggle->toggleOn(); |
137 | |
138 | visElem.button = toggle; |
139 | } |
140 | else |
141 | visElem.button = GUIButton::create(getElementLocalizedName(i), getSubStyleName(ENTRY_STYLE_TYPE)); |
142 | |
143 | visElem.button->onHover.connect(std::bind(onHover, i, curVisIdx)); |
144 | visElem.button->onClick.connect(std::bind(onClick, i, curVisIdx)); |
145 | _registerChildElement(visElem.button); |
146 | |
147 | const String& shortcutTag = element.getShortcutTag(); |
148 | if (!shortcutTag.empty()) |
149 | { |
150 | visElem.shortcutLabel = GUILabel::create(HString(shortcutTag), "RightAlignedLabel" ); |
151 | _registerChildElement(visElem.shortcutLabel); |
152 | } |
153 | } |
154 | |
155 | curVisIdx++; |
156 | } |
157 | |
158 | _markLayoutAsDirty(); |
159 | } |
160 | |
161 | UINT32 GUIDropDownContent::getElementHeight(UINT32 idx) const |
162 | { |
163 | if (_getParentWidget() == nullptr) |
164 | return 14; // Arbitrary |
165 | |
166 | if (mDropDownData.entries[idx].isSeparator()) |
167 | return _getParentWidget()->getSkin().getStyle(getSubStyleName(SEPARATOR_STYLE_TYPE))->height; |
168 | else if (mDropDownData.entries[idx].isSubMenu()) |
169 | return _getParentWidget()->getSkin().getStyle(getSubStyleName(ENTRY_EXP_STYLE_TYPE))->height; |
170 | else |
171 | { |
172 | if (mIsToggle) |
173 | return _getParentWidget()->getSkin().getStyle(getSubStyleName(ENTRY_TOGGLE_STYLE_TYPE))->height; |
174 | else |
175 | return _getParentWidget()->getSkin().getStyle(getSubStyleName(ENTRY_STYLE_TYPE))->height; |
176 | } |
177 | } |
178 | |
179 | HString GUIDropDownContent::getElementLocalizedName(UINT32 idx) const |
180 | { |
181 | const String& label = mDropDownData.entries[idx].getLabel(); |
182 | |
183 | auto findLocalizedName = mDropDownData.localizedNames.find(label); |
184 | if (findLocalizedName != mDropDownData.localizedNames.end()) |
185 | return findLocalizedName->second; |
186 | else |
187 | return HString(label); |
188 | } |
189 | |
190 | void GUIDropDownContent::setKeyboardFocus(bool focus) |
191 | { |
192 | mKeyboardFocus = focus; |
193 | setFocus(focus); |
194 | } |
195 | |
196 | bool GUIDropDownContent::_commandEvent(const GUICommandEvent& ev) |
197 | { |
198 | bool baseReturn = GUIElementContainer::_commandEvent(ev); |
199 | |
200 | if (!mKeyboardFocus) |
201 | return baseReturn; |
202 | |
203 | switch (ev.getType()) |
204 | { |
205 | case GUICommandEventType::MoveDown: |
206 | if (mSelectedIdx == UINT_MAX) |
207 | selectNext(0); |
208 | else |
209 | selectNext(mVisibleElements[mSelectedIdx].idx + 1); |
210 | return true; |
211 | case GUICommandEventType::MoveUp: |
212 | if (mSelectedIdx == UINT_MAX) |
213 | selectNext(0); |
214 | else |
215 | selectPrevious(mVisibleElements[mSelectedIdx].idx - 1); |
216 | return true; |
217 | case GUICommandEventType::Escape: |
218 | case GUICommandEventType::MoveLeft: |
219 | mParent->close(); |
220 | return true; |
221 | case GUICommandEventType::MoveRight: |
222 | { |
223 | if (mSelectedIdx == UINT_MAX) |
224 | selectNext(0); |
225 | else |
226 | { |
227 | GUIDropDownDataEntry& entry = mDropDownData.entries[mVisibleElements[mSelectedIdx].idx]; |
228 | if (entry.isSubMenu()) |
229 | mParent->elementActivated(mVisibleElements[mSelectedIdx].idx, mVisibleElements[mSelectedIdx].button->_getLayoutData().area); |
230 | } |
231 | } |
232 | return true; |
233 | case GUICommandEventType::Confirm: |
234 | if (mSelectedIdx == UINT_MAX) |
235 | selectNext(0); |
236 | else |
237 | { |
238 | if (mIsToggle) |
239 | mVisibleElements[mSelectedIdx].button->_setOn(!mVisibleElements[mSelectedIdx].button->_isOn()); |
240 | |
241 | mParent->elementActivated(mVisibleElements[mSelectedIdx].idx, mVisibleElements[mSelectedIdx].button->_getLayoutData().area); |
242 | } |
243 | return true; |
244 | default: |
245 | break; |
246 | } |
247 | |
248 | return baseReturn; |
249 | } |
250 | |
251 | bool GUIDropDownContent::_mouseEvent(const GUIMouseEvent& ev) |
252 | { |
253 | if (ev.getType() == GUIMouseEventType::MouseWheelScroll) |
254 | { |
255 | if (ev.getWheelScrollAmount() < 0) |
256 | mParent->scrollDown(); |
257 | else |
258 | mParent->scrollUp(); |
259 | |
260 | return true; |
261 | } |
262 | |
263 | return false; |
264 | } |
265 | |
266 | void GUIDropDownContent::setSelected(UINT32 idx) |
267 | { |
268 | if (mSelectedIdx != UINT_MAX) |
269 | { |
270 | if (mVisibleElements[mSelectedIdx].button->_isOn()) |
271 | mVisibleElements[mSelectedIdx].button->_setState(GUIElementState::NormalOn); |
272 | else |
273 | mVisibleElements[mSelectedIdx].button->_setState(GUIElementState::Normal); |
274 | } |
275 | |
276 | mSelectedIdx = idx; |
277 | if (mVisibleElements[mSelectedIdx].button->_isOn()) |
278 | mVisibleElements[mSelectedIdx].button->_setState(GUIElementState::HoverOn); |
279 | else |
280 | mVisibleElements[mSelectedIdx].button->_setState(GUIElementState::Hover); |
281 | |
282 | mParent->elementSelected(mVisibleElements[mSelectedIdx].idx); |
283 | } |
284 | |
285 | void GUIDropDownContent::selectNext(UINT32 startIdx) |
286 | { |
287 | UINT32 numElements = (UINT32)mDropDownData.entries.size(); |
288 | |
289 | bool gotNextIndex = false; |
290 | UINT32 nextIdx = startIdx; |
291 | for (UINT32 i = 0; i < numElements; i++) |
292 | { |
293 | if (nextIdx >= numElements) |
294 | nextIdx = 0; // Wrap around |
295 | |
296 | GUIDropDownDataEntry& entry = mDropDownData.entries[nextIdx]; |
297 | if (!entry.isSeparator()) |
298 | { |
299 | gotNextIndex = true; |
300 | break; |
301 | } |
302 | |
303 | nextIdx++; |
304 | } |
305 | |
306 | if (gotNextIndex) |
307 | { |
308 | while (nextIdx < mRangeStart || nextIdx >= mRangeEnd) |
309 | mParent->scrollDown(); |
310 | |
311 | UINT32 visIdx = 0; |
312 | for (auto& visElem : mVisibleElements) |
313 | { |
314 | if (visElem.idx == nextIdx) |
315 | { |
316 | setSelected(visIdx); |
317 | break; |
318 | } |
319 | |
320 | visIdx++; |
321 | } |
322 | } |
323 | } |
324 | |
325 | void GUIDropDownContent::selectPrevious(UINT32 startIdx) |
326 | { |
327 | UINT32 numElements = (UINT32)mDropDownData.entries.size(); |
328 | |
329 | bool gotNextIndex = false; |
330 | INT32 prevIdx = (INT32)startIdx; |
331 | |
332 | for (UINT32 i = 0; i < numElements; i++) |
333 | { |
334 | if (prevIdx < 0) |
335 | prevIdx = numElements - 1; // Wrap around |
336 | |
337 | GUIDropDownDataEntry& entry = mDropDownData.entries[prevIdx]; |
338 | if (!entry.isSeparator()) |
339 | { |
340 | gotNextIndex = true; |
341 | break; |
342 | } |
343 | |
344 | prevIdx--; |
345 | } |
346 | |
347 | if (gotNextIndex) |
348 | { |
349 | while (prevIdx < (INT32)mRangeStart || prevIdx >= (INT32)mRangeEnd) |
350 | mParent->scrollUp(); |
351 | |
352 | UINT32 visIdx = 0; |
353 | for (auto& visElem : mVisibleElements) |
354 | { |
355 | if (visElem.idx == (UINT32)prevIdx) |
356 | { |
357 | setSelected(visIdx); |
358 | break; |
359 | } |
360 | |
361 | visIdx++; |
362 | } |
363 | } |
364 | } |
365 | |
366 | Vector2I GUIDropDownContent::_getOptimalSize() const |
367 | { |
368 | Vector2I optimalSize; |
369 | for (auto& visElem : mVisibleElements) |
370 | { |
371 | const GUIDropDownDataEntry& element = mDropDownData.entries[visElem.idx]; |
372 | |
373 | optimalSize.y += (INT32)getElementHeight(visElem.idx); |
374 | |
375 | if (element.isSeparator()) |
376 | optimalSize.x = std::max(optimalSize.x, visElem.separator->_getOptimalSize().x); |
377 | else |
378 | optimalSize.x = std::max(optimalSize.x, visElem.button->_getOptimalSize().x); |
379 | } |
380 | |
381 | return optimalSize; |
382 | } |
383 | |
384 | void GUIDropDownContent::_updateLayoutInternal(const GUILayoutData& data) |
385 | { |
386 | GUILayoutData childData = data; |
387 | INT32 yOffset = data.area.y; |
388 | |
389 | for (auto& visElem : mVisibleElements) |
390 | { |
391 | const GUIDropDownDataEntry& element = mDropDownData.entries[visElem.idx]; |
392 | |
393 | GUIElement* guiMainElement = nullptr; |
394 | if (element.isSeparator()) |
395 | guiMainElement = visElem.separator; |
396 | else |
397 | guiMainElement = visElem.button; |
398 | |
399 | childData.area.y = yOffset; |
400 | childData.area.height = getElementHeight(visElem.idx); |
401 | |
402 | yOffset += childData.area.height; |
403 | |
404 | guiMainElement->_setLayoutData(childData); |
405 | |
406 | // Shortcut label |
407 | GUILabel* shortcutLabel = visElem.shortcutLabel; |
408 | if (shortcutLabel != nullptr) |
409 | shortcutLabel->_setLayoutData(childData); |
410 | } |
411 | } |
412 | |
413 | const String& GUIDropDownContent::getGUITypeName() |
414 | { |
415 | static String typeName = "GUIDropDownContent" ; |
416 | return typeName; |
417 | } |
418 | } |
419 | |