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/BsGUIButtonBase.h"
4#include "2D/BsImageSprite.h"
5#include "GUI/BsGUISkin.h"
6#include "Image/BsSpriteTexture.h"
7#include "2D/BsTextSprite.h"
8#include "GUI/BsGUIDimensions.h"
9#include "GUI/BsGUIMouseEvent.h"
10#include "GUI/BsGUICommandEvent.h"
11#include "GUI/BsGUIHelper.h"
12
13namespace bs
14{
15 GUIButtonBase::GUIButtonBase(const String& styleName, const GUIContent& content, const GUIDimensions& dimensions,
16 GUIElementOptions options)
17 : GUIElement(styleName, dimensions, options), mContent(content)
18 {
19 mImageSprite = bs_new<ImageSprite>();
20 mTextSprite = bs_new<TextSprite>();
21
22 mImageDesc.animationStartTime = gTime().getTime();
23 mContentAnimationStartTime = mImageDesc.animationStartTime;
24
25 refreshContentSprite();
26 }
27
28 GUIButtonBase::~GUIButtonBase()
29 {
30 bs_delete(mTextSprite);
31 bs_delete(mImageSprite);
32
33 if(mContentImageSprite != nullptr)
34 bs_delete(mContentImageSprite);
35 }
36
37 void GUIButtonBase::setContent(const GUIContent& content)
38 {
39 Vector2I origSize = mDimensions.calculateSizeRange(_getOptimalSize()).optimal;
40 mContent = content;
41 mContentAnimationStartTime = gTime().getTime();
42
43 refreshContentSprite();
44
45 Vector2I newSize = mDimensions.calculateSizeRange(_getOptimalSize()).optimal;
46
47 if (origSize != newSize)
48 _markLayoutAsDirty();
49 else
50 _markContentAsDirty();
51 }
52
53 void GUIButtonBase::_setOn(bool on)
54 {
55 if(on)
56 _setState((GUIElementState)((INT32)mActiveState | (INT32)GUIElementState::OnFlag));
57 else
58 _setState((GUIElementState)((INT32)mActiveState & ~(INT32)GUIElementState::OnFlag));
59 }
60
61 bool GUIButtonBase::_isOn() const
62 {
63 return ((INT32)mActiveState & (INT32)GUIElementState::OnFlag) != 0;
64 }
65
66 UINT32 GUIButtonBase::_getNumRenderElements() const
67 {
68 UINT32 numElements = mImageSprite->getNumRenderElements();
69 numElements += mTextSprite->getNumRenderElements();
70
71 if(mContentImageSprite != nullptr)
72 numElements += mContentImageSprite->getNumRenderElements();
73
74 return numElements;
75 }
76
77 const SpriteMaterialInfo& GUIButtonBase::_getMaterial(UINT32 renderElementIdx, SpriteMaterial** material) const
78 {
79 UINT32 textSpriteIdx = mImageSprite->getNumRenderElements();
80 UINT32 contentImgSpriteIdx = textSpriteIdx + mTextSprite->getNumRenderElements();
81
82 if (renderElementIdx >= contentImgSpriteIdx)
83 {
84 *material = mContentImageSprite->getMaterial(contentImgSpriteIdx - renderElementIdx);
85 return mContentImageSprite->getMaterialInfo(contentImgSpriteIdx - renderElementIdx);
86 }
87 else if (renderElementIdx >= textSpriteIdx)
88 {
89 *material = mTextSprite->getMaterial(textSpriteIdx - renderElementIdx);
90 return mTextSprite->getMaterialInfo(textSpriteIdx - renderElementIdx);
91 }
92 else
93 {
94 *material = mImageSprite->getMaterial(renderElementIdx);
95 return mImageSprite->getMaterialInfo(renderElementIdx);
96 }
97 }
98
99 void GUIButtonBase::_getMeshInfo(UINT32 renderElementIdx, UINT32& numVertices, UINT32& numIndices, GUIMeshType& type) const
100 {
101 UINT32 textSpriteIdx = mImageSprite->getNumRenderElements();
102 UINT32 contentImgSpriteIdx = textSpriteIdx + mTextSprite->getNumRenderElements();
103
104 UINT32 numQuads = 0;
105 if(renderElementIdx >= contentImgSpriteIdx)
106 numQuads = mContentImageSprite->getNumQuads(contentImgSpriteIdx - renderElementIdx);
107 else if(renderElementIdx >= textSpriteIdx)
108 numQuads = mTextSprite->getNumQuads(textSpriteIdx - renderElementIdx);
109 else
110 numQuads = mImageSprite->getNumQuads(renderElementIdx);
111
112 numVertices = numQuads * 4;
113 numIndices = numQuads * 6;
114 type = GUIMeshType::Triangle;
115 }
116
117 void GUIButtonBase::updateRenderElementsInternal()
118 {
119 mImageDesc.width = mLayoutData.area.width;
120 mImageDesc.height = mLayoutData.area.height;
121
122 const HSpriteTexture& activeTex = getActiveTexture();
123 if (SpriteTexture::checkIsLoaded(activeTex))
124 mImageDesc.texture = activeTex;
125 else
126 mImageDesc.texture = nullptr;
127
128 mImageDesc.borderLeft = _getStyle()->border.left;
129 mImageDesc.borderRight = _getStyle()->border.right;
130 mImageDesc.borderTop = _getStyle()->border.top;
131 mImageDesc.borderBottom = _getStyle()->border.bottom;
132 mImageDesc.color = getTint();
133
134 mImageSprite->update(mImageDesc, (UINT64)_getParentWidget());
135
136 mTextSprite->update(getTextDesc(), (UINT64)_getParentWidget());
137
138 if(mContentImageSprite != nullptr)
139 {
140 Rect2I contentBounds = getCachedContentBounds();
141
142 HSpriteTexture image = mContent.getImage(mActiveState);
143 UINT32 contentWidth = image->getWidth();
144 UINT32 contentHeight = image->getHeight();
145
146 UINT32 contentMaxWidth = std::min((UINT32)contentBounds.width, contentWidth);
147 UINT32 contentMaxHeight = std::min((UINT32)contentBounds.height, contentHeight);
148
149 float horzRatio = contentMaxWidth / (float)contentWidth;
150 float vertRatio = contentMaxHeight / (float)contentHeight;
151
152 if (horzRatio < vertRatio)
153 {
154 contentWidth = Math::roundToInt(contentWidth * horzRatio);
155 contentHeight = Math::roundToInt(contentHeight * horzRatio);
156 }
157 else
158 {
159 contentWidth = Math::roundToInt(contentWidth * vertRatio);
160 contentHeight = Math::roundToInt(contentHeight * vertRatio);
161 }
162
163 IMAGE_SPRITE_DESC contentImgDesc;
164 contentImgDesc.texture = image;
165 contentImgDesc.width = contentWidth;
166 contentImgDesc.height = contentHeight;
167 contentImgDesc.color = getTint();
168 contentImgDesc.animationStartTime = mContentAnimationStartTime;
169
170 mContentImageSprite->update(contentImgDesc, (UINT64)_getParentWidget());
171 }
172
173 GUIElement::updateRenderElementsInternal();
174 }
175
176 Vector2I GUIButtonBase::_getOptimalSize() const
177 {
178 UINT32 imageWidth = 0;
179 UINT32 imageHeight = 0;
180
181 const HSpriteTexture& activeTex = getActiveTexture();
182 if(SpriteTexture::checkIsLoaded(activeTex))
183 {
184 imageWidth = activeTex->getWidth();
185 imageHeight = activeTex->getHeight();
186 }
187
188 Vector2I contentSize = GUIHelper::calcOptimalContentsSize(mContent, *_getStyle(), _getDimensions(), mActiveState);
189 UINT32 contentWidth = std::max(imageWidth, (UINT32)contentSize.x);
190 UINT32 contentHeight = std::max(imageHeight, (UINT32)contentSize.y);
191
192 return Vector2I(contentWidth, contentHeight);
193 }
194
195 UINT32 GUIButtonBase::_getRenderElementDepth(UINT32 renderElementIdx) const
196 {
197 UINT32 textSpriteIdx = mImageSprite->getNumRenderElements();
198 UINT32 contentImgSpriteIdx = textSpriteIdx + mTextSprite->getNumRenderElements();
199
200 if(renderElementIdx >= contentImgSpriteIdx)
201 return _getDepth();
202 else if(renderElementIdx >= textSpriteIdx)
203 return _getDepth();
204 else
205 return _getDepth() + 1;
206 }
207
208 UINT32 GUIButtonBase::_getRenderElementDepthRange() const
209 {
210 return 2;
211 }
212
213 void GUIButtonBase::_fillBuffer(UINT8* vertices, UINT32* indices, UINT32 vertexOffset, UINT32 indexOffset,
214 UINT32 maxNumVerts, UINT32 maxNumIndices, UINT32 renderElementIdx) const
215 {
216 UINT8* uvs = vertices + sizeof(Vector2);
217 UINT32 vertexStride = sizeof(Vector2) * 2;
218 UINT32 indexStride = sizeof(UINT32);
219
220 UINT32 textSpriteIdx = mImageSprite->getNumRenderElements();
221 UINT32 contentImgSpriteIdx = textSpriteIdx + mTextSprite->getNumRenderElements();
222
223 if(renderElementIdx < textSpriteIdx)
224 {
225 Vector2I offset(mLayoutData.area.x, mLayoutData.area.y);
226
227 mImageSprite->fillBuffer(vertices, uvs, indices, vertexOffset, indexOffset, maxNumVerts, maxNumIndices,
228 vertexStride, indexStride, renderElementIdx, offset, mLayoutData.getLocalClipRect());
229
230 return;
231 }
232
233 Rect2I contentBounds = getCachedContentBounds();
234 Rect2I contentClipRect = getCachedContentClipRect();
235 Rect2I textBounds = mTextSprite->getBounds(Vector2I(), Rect2I());
236
237 Vector2I textOffset;
238 Rect2I textClipRect;
239
240 Vector2I imageOffset;
241 Rect2I imageClipRect;
242 if(mContentImageSprite != nullptr)
243 {
244 Rect2I imageBounds = mContentImageSprite->getBounds(Vector2I(), Rect2I());
245 INT32 imageXOffset = 0;
246 INT32 textImageSpacing = 0;
247
248 if (textBounds.width == 0)
249 {
250 UINT32 freeWidth = (UINT32)std::max(0, (INT32)contentBounds.width - (INT32)textBounds.width - (INT32)imageBounds.width);
251 imageXOffset = (INT32)(freeWidth / 2);
252 }
253 else
254 textImageSpacing = GUIContent::IMAGE_TEXT_SPACING;
255
256 if(_getStyle()->imagePosition == GUIImagePosition::Right)
257 {
258 INT32 imageReservedWidth = std::max(0, (INT32)contentBounds.width - (INT32)textBounds.width);
259
260 textOffset = Vector2I(contentBounds.x, contentBounds.y);
261 textClipRect = contentClipRect;
262 textClipRect.width = std::min(contentBounds.width - imageReservedWidth, textClipRect.width);
263
264 imageOffset = Vector2I(contentBounds.x + textBounds.width + imageXOffset + textImageSpacing, contentBounds.y);
265 imageClipRect = contentClipRect;
266 imageClipRect.x -= textBounds.width + imageXOffset;
267 }
268 else
269 {
270 INT32 imageReservedWidth = imageBounds.width + imageXOffset;
271
272 imageOffset = Vector2I(contentBounds.x + imageXOffset, contentBounds.y);
273 imageClipRect = contentClipRect;
274 imageClipRect.x -= imageXOffset;
275 imageClipRect.width = std::min(imageReservedWidth, (INT32)imageClipRect.width);
276
277 textOffset = Vector2I(contentBounds.x + imageReservedWidth + textImageSpacing, contentBounds.y);
278 textClipRect = contentClipRect;
279 textClipRect.x -= imageReservedWidth;
280 }
281
282 INT32 imageYOffset = (contentBounds.height - imageBounds.height) / 2;
283 imageClipRect.y -= imageYOffset;
284 imageOffset.y += imageYOffset;
285 }
286 else
287 {
288 textOffset = Vector2I(contentBounds.x, contentBounds.y);
289 textClipRect = contentClipRect;
290 }
291
292 if(renderElementIdx >= contentImgSpriteIdx)
293 {
294 mContentImageSprite->fillBuffer(vertices, uvs, indices, vertexOffset, indexOffset, maxNumVerts, maxNumIndices,
295 vertexStride, indexStride, contentImgSpriteIdx - renderElementIdx, imageOffset, imageClipRect);
296 }
297 else
298 {
299 mTextSprite->fillBuffer(vertices, uvs, indices, vertexOffset, indexOffset, maxNumVerts, maxNumIndices,
300 vertexStride, indexStride, textSpriteIdx - renderElementIdx, textOffset, textClipRect);
301 }
302 }
303
304 bool GUIButtonBase::_mouseEvent(const GUIMouseEvent& ev)
305 {
306 if(ev.getType() == GUIMouseEventType::MouseOver)
307 {
308 if (!_isDisabled())
309 {
310 if(mHasFocus)
311 _setState(_isOn() ? GUIElementState::FocusedHoverOn : GUIElementState::FocusedHover);
312 else
313 _setState(_isOn() ? GUIElementState::HoverOn : GUIElementState::Hover);
314
315 onHover();
316 }
317
318 return !mOptionFlags.isSet(GUIElementOption::ClickThrough);
319 }
320 else if(ev.getType() == GUIMouseEventType::MouseOut)
321 {
322 if (!_isDisabled())
323 {
324 if(mHasFocus)
325 _setState(_isOn() ? GUIElementState::FocusedOn : GUIElementState::Focused);
326 else
327 _setState(_isOn() ? GUIElementState::NormalOn : GUIElementState::Normal);
328
329 onOut();
330 }
331
332 return !mOptionFlags.isSet(GUIElementOption::ClickThrough);
333 }
334 else if(ev.getType() == GUIMouseEventType::MouseDown)
335 {
336 if (!_isDisabled())
337 _setState(_isOn() ? GUIElementState::ActiveOn : GUIElementState::Active);
338
339 return !mOptionFlags.isSet(GUIElementOption::ClickThrough);
340 }
341 else if(ev.getType() == GUIMouseEventType::MouseUp)
342 {
343 if (!_isDisabled())
344 {
345 if(mHasFocus)
346 _setState(_isOn() ? GUIElementState::FocusedHoverOn : GUIElementState::FocusedHover);
347 else
348 _setState(_isOn() ? GUIElementState::HoverOn : GUIElementState::Hover);
349
350 onClick();
351 }
352
353 return !mOptionFlags.isSet(GUIElementOption::ClickThrough);
354 }
355 else if (ev.getType() == GUIMouseEventType::MouseDoubleClick)
356 {
357 if (!_isDisabled())
358 onDoubleClick();
359
360 return !mOptionFlags.isSet(GUIElementOption::ClickThrough);
361 }
362
363 return false;
364 }
365
366 bool GUIButtonBase::_commandEvent(const GUICommandEvent& ev)
367 {
368 const bool baseReturnValue = GUIElement::_commandEvent(ev);
369
370 GUIElementState state = (GUIElementState)((UINT32)mActiveState & (UINT32)GUIElementState::TypeMask);
371 if(ev.getType() == GUICommandEventType::FocusGained)
372 {
373 mHasFocus = true;
374
375 if(!_isDisabled())
376 {
377 if(state == GUIElementState::Normal)
378 _setState(_isOn() ? GUIElementState::FocusedOn : GUIElementState::Focused);
379 else if(state == GUIElementState::Hover)
380 _setState(_isOn() ? GUIElementState::FocusedHoverOn : GUIElementState::FocusedHover);
381 }
382
383 return true;
384 }
385 else if(ev.getType() == GUICommandEventType::FocusLost)
386 {
387 mHasFocus = false;
388
389 if (state == GUIElementState::Focused)
390 _setState(_isOn() ? GUIElementState::NormalOn : GUIElementState::Normal);
391 else if (state == GUIElementState::FocusedHover)
392 _setState(_isOn() ? GUIElementState::HoverOn : GUIElementState::Hover);
393
394 return true;
395 }
396
397 return baseReturnValue;
398 }
399
400 String GUIButtonBase::_getTooltip() const
401 {
402 return mContent.tooltip;
403 }
404
405 void GUIButtonBase::refreshContentSprite()
406 {
407 HSpriteTexture contentTex = mContent.getImage(mActiveState);
408 if (SpriteTexture::checkIsLoaded(contentTex))
409 {
410 if (mContentImageSprite == nullptr)
411 mContentImageSprite = bs_new<ImageSprite>();
412 }
413 else
414 {
415 if (mContentImageSprite != nullptr)
416 {
417 bs_delete(mContentImageSprite);
418 mContentImageSprite = nullptr;
419 }
420 }
421 }
422
423 TEXT_SPRITE_DESC GUIButtonBase::getTextDesc() const
424 {
425 TEXT_SPRITE_DESC textDesc;
426 textDesc.text = mContent.text;
427 textDesc.font = _getStyle()->font;
428 textDesc.fontSize = _getStyle()->fontSize;
429 textDesc.color = getTint() * getActiveTextColor();
430
431 Rect2I textBounds = getCachedContentBounds();
432
433 textDesc.width = textBounds.width;
434 textDesc.height = textBounds.height;
435 textDesc.horzAlign = _getStyle()->textHorzAlign;
436 textDesc.vertAlign = _getStyle()->textVertAlign;
437
438 return textDesc;
439 }
440
441 void GUIButtonBase::styleUpdated()
442 {
443 mImageDesc.animationStartTime = gTime().getTime();
444 }
445
446 void GUIButtonBase::_setState(GUIElementState state)
447 {
448 Vector2I origSize = mDimensions.calculateSizeRange(_getOptimalSize()).optimal;
449
450 if(mActiveState != state)
451 mImageDesc.animationStartTime = gTime().getTime();
452
453 mActiveState = state;
454 refreshContentSprite();
455 Vector2I newSize = mDimensions.calculateSizeRange(_getOptimalSize()).optimal;
456
457 if (origSize != newSize)
458 _markLayoutAsDirty();
459 else
460 _markContentAsDirty();
461 }
462
463 const HSpriteTexture& GUIButtonBase::getActiveTexture() const
464 {
465 switch(mActiveState)
466 {
467 case GUIElementState::Normal:
468 return _getStyle()->normal.texture;
469 case GUIElementState::Hover:
470 return _getStyle()->hover.texture;
471 case GUIElementState::Active:
472 return _getStyle()->active.texture;
473 case GUIElementState::Focused:
474 return _getStyle()->focused.texture;
475 case GUIElementState::FocusedHover:
476 return _getStyle()->focusedHover.texture;
477 case GUIElementState::NormalOn:
478 return _getStyle()->normalOn.texture;
479 case GUIElementState::HoverOn:
480 return _getStyle()->hoverOn.texture;
481 case GUIElementState::ActiveOn:
482 return _getStyle()->activeOn.texture;
483 case GUIElementState::FocusedOn:
484 return _getStyle()->focusedOn.texture;
485 case GUIElementState::FocusedHoverOn:
486 return _getStyle()->focusedHoverOn.texture;
487 default:
488 break;
489 }
490
491 return _getStyle()->normal.texture;
492 }
493
494 Color GUIButtonBase::getActiveTextColor() const
495 {
496 switch (mActiveState)
497 {
498 case GUIElementState::Normal:
499 return _getStyle()->normal.textColor;
500 case GUIElementState::Hover:
501 return _getStyle()->hover.textColor;
502 case GUIElementState::Active:
503 return _getStyle()->active.textColor;
504 case GUIElementState::Focused:
505 return _getStyle()->focused.textColor;
506 case GUIElementState::FocusedHover:
507 return _getStyle()->focusedHover.textColor;
508 case GUIElementState::NormalOn:
509 return _getStyle()->normalOn.textColor;
510 case GUIElementState::HoverOn:
511 return _getStyle()->hoverOn.textColor;
512 case GUIElementState::ActiveOn:
513 return _getStyle()->activeOn.textColor;
514 case GUIElementState::FocusedOn:
515 return _getStyle()->focusedOn.textColor;
516 case GUIElementState::FocusedHoverOn:
517 return _getStyle()->focusedHoverOn.textColor;
518 default:
519 break;
520 }
521
522 return _getStyle()->normal.textColor;
523 }
524}