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/BsGUISliderHandle.h"
4#include "2D/BsImageSprite.h"
5#include "GUI/BsGUISkin.h"
6#include "Image/BsSpriteTexture.h"
7#include "GUI/BsGUIDimensions.h"
8#include "GUI/BsGUIMouseEvent.h"
9
10namespace bs
11{
12 const UINT32 GUISliderHandle::RESIZE_HANDLE_SIZE = 7;
13
14 const String& GUISliderHandle::getGUITypeName()
15 {
16 static String name = "SliderHandle";
17 return name;
18 }
19
20 GUISliderHandle::GUISliderHandle(GUISliderHandleFlags flags, const String& styleName, const GUIDimensions& dimensions)
21 : GUIElement(styleName, dimensions), mFlags(flags)
22 {
23 mImageSprite = bs_new<ImageSprite>();
24
25 // Calling virtual method is okay in this case
26 styleUpdated();
27 }
28
29 GUISliderHandle::~GUISliderHandle()
30 {
31 bs_delete(mImageSprite);
32 }
33
34 GUISliderHandle* GUISliderHandle::create(GUISliderHandleFlags flags, const String& styleName)
35 {
36 return new (bs_alloc<GUISliderHandle>()) GUISliderHandle(flags,
37 getStyleName<GUISliderHandle>(styleName), GUIDimensions::create());
38 }
39
40 GUISliderHandle* GUISliderHandle::create(GUISliderHandleFlags flags, const GUIOptions& options, const String& styleName)
41 {
42 return new (bs_alloc<GUISliderHandle>()) GUISliderHandle(flags,
43 getStyleName<GUISliderHandle>(styleName), GUIDimensions::create(options));
44 }
45
46 void GUISliderHandle::_setHandleSize(float pct)
47 {
48 mPctHandleSize = Math::clamp01(pct);
49 }
50
51 void GUISliderHandle::_setHandlePos(float pct)
52 {
53 float maxPct = 1.0f;
54 if (mStep > 0.0f && pct < maxPct)
55 {
56 pct = (pct + mStep * 0.5f) - fmod(pct + mStep * 0.5f, mStep);
57 maxPct = Math::floor(1.0f / mStep) * mStep;
58 }
59
60 mPctHandlePos = Math::clamp(pct, 0.0f, maxPct);
61 }
62
63 float GUISliderHandle::getHandlePos() const
64 {
65 return mPctHandlePos;;
66 }
67
68 float GUISliderHandle::getStep() const
69 {
70 return mStep;
71 }
72
73 void GUISliderHandle::setStep(float step)
74 {
75 mStep = Math::clamp01(step);
76 }
77
78 UINT32 GUISliderHandle::getScrollableSize() const
79 {
80 return getMaxSize() - getHandleSize();
81 }
82
83 UINT32 GUISliderHandle::_getNumRenderElements() const
84 {
85 return mImageSprite->getNumRenderElements();
86 }
87
88 const SpriteMaterialInfo& GUISliderHandle::_getMaterial(UINT32 renderElementIdx, SpriteMaterial** material) const
89 {
90 *material = mImageSprite->getMaterial(renderElementIdx);
91 return mImageSprite->getMaterialInfo(renderElementIdx);
92 }
93
94 void GUISliderHandle::_getMeshInfo(UINT32 renderElementIdx, UINT32& numVertices, UINT32& numIndices, GUIMeshType& type) const
95 {
96 UINT32 numQuads = mImageSprite->getNumQuads(renderElementIdx);
97 numVertices = numQuads * 4;
98 numIndices = numQuads * 6;
99 type = GUIMeshType::Triangle;
100 }
101
102 void GUISliderHandle::updateRenderElementsInternal()
103 {
104 IMAGE_SPRITE_DESC desc;
105
106 HSpriteTexture activeTex = getActiveTexture();
107 if(SpriteTexture::checkIsLoaded(activeTex))
108 desc.texture = activeTex;
109
110 UINT32 handleSize = getHandleSize();
111 if (mFlags.isSet(GUISliderHandleFlag::Horizontal))
112 {
113 if (handleSize == 0 && desc.texture != nullptr)
114 {
115 handleSize = desc.texture->getWidth();
116 mPctHandleSize = handleSize / (float)getMaxSize();
117 }
118
119 desc.width = handleSize;
120 desc.height = mLayoutData.area.height;
121 }
122 else
123 {
124 if (handleSize == 0 && desc.texture != nullptr)
125 {
126 handleSize = desc.texture->getHeight();
127 mPctHandleSize = handleSize / (float)getMaxSize();
128 }
129
130 desc.width = mLayoutData.area.width;
131 desc.height = handleSize;
132 }
133
134 desc.borderLeft = _getStyle()->border.left;
135 desc.borderRight = _getStyle()->border.right;
136 desc.borderTop = _getStyle()->border.top;
137 desc.borderBottom = _getStyle()->border.bottom;
138 desc.color = getTint();
139 mImageSprite->update(desc, (UINT64)_getParentWidget());
140
141 GUIElement::updateRenderElementsInternal();
142 }
143
144 void GUISliderHandle::updateClippedBounds()
145 {
146 mClippedBounds = mLayoutData.area;
147 mClippedBounds.clip(mLayoutData.clipRect);
148 }
149
150 Vector2I GUISliderHandle::_getOptimalSize() const
151 {
152 HSpriteTexture activeTex = getActiveTexture();
153
154 if(SpriteTexture::checkIsLoaded(activeTex))
155 return Vector2I(activeTex->getWidth(), activeTex->getHeight());
156
157 return Vector2I();
158 }
159
160 void GUISliderHandle::_fillBuffer(UINT8* vertices, UINT32* indices, UINT32 vertexOffset, UINT32 indexOffset,
161 UINT32 maxNumVerts, UINT32 maxNumIndices, UINT32 renderElementIdx) const
162 {
163 UINT8* uvs = vertices + sizeof(Vector2);
164 UINT32 vertexStride = sizeof(Vector2) * 2;
165 UINT32 indexStride = sizeof(UINT32);
166
167 Vector2I offset(mLayoutData.area.x, mLayoutData.area.y);
168 Rect2I clipRect = mLayoutData.getLocalClipRect();
169
170 if (mFlags.isSet(GUISliderHandleFlag::Horizontal))
171 {
172 offset.x += getHandlePosPx();
173 clipRect.x -= getHandlePosPx();
174 }
175 else
176 {
177 offset.y += getHandlePosPx();
178 clipRect.y -= getHandlePosPx();
179 }
180
181 mImageSprite->fillBuffer(vertices, uvs, indices, vertexOffset, indexOffset, maxNumVerts, maxNumIndices,
182 vertexStride, indexStride, renderElementIdx, offset, clipRect);
183 }
184
185 bool GUISliderHandle::_mouseEvent(const GUIMouseEvent& ev)
186 {
187 UINT32 handleSize = getHandleSize();
188
189 if(ev.getType() == GUIMouseEventType::MouseMove)
190 {
191 if (!_isDisabled())
192 {
193 if (mMouseOverHandle)
194 {
195 if (!isOnHandle(ev.getPosition()))
196 {
197 mMouseOverHandle = false;
198
199 mState = State::Normal;
200 _markLayoutAsDirty();
201
202 return true;
203 }
204 }
205 else
206 {
207 if (isOnHandle(ev.getPosition()))
208 {
209 mMouseOverHandle = true;
210
211 mState = State::Hover;
212 _markLayoutAsDirty();
213
214 return true;
215 }
216 }
217 }
218 }
219
220 bool jumpOnClick = mFlags.isSet(GUISliderHandleFlag::JumpOnClick);
221 if(ev.getType() == GUIMouseEventType::MouseDown && (mMouseOverHandle || jumpOnClick))
222 {
223 if (!_isDisabled())
224 {
225 mState = State::Active;
226 _markLayoutAsDirty();
227
228 if (jumpOnClick)
229 {
230 float handlePosPx = 0.0f;
231
232 if (mFlags.isSet(GUISliderHandleFlag::Horizontal))
233 handlePosPx = (float)(ev.getPosition().x - (INT32)mLayoutData.area.x - handleSize * 0.5f);
234 else
235 handlePosPx = (float)(ev.getPosition().y - (INT32)mLayoutData.area.y - handleSize * 0.5f);
236
237 setHandlePosPx((INT32)handlePosPx);
238 onHandleMovedOrResized(mPctHandlePos, _getHandleSizePct());
239 }
240
241 bool isResizeable = mFlags.isSet(GUISliderHandleFlag::Resizeable);
242 if (mFlags.isSet(GUISliderHandleFlag::Horizontal))
243 {
244 INT32 left = (INT32)mLayoutData.area.x + getHandlePosPx();
245
246 if(isResizeable)
247 {
248 INT32 right = left + handleSize;
249
250 INT32 clickPos = ev.getPosition().x;
251 if (clickPos >= left && clickPos < (left + (INT32)RESIZE_HANDLE_SIZE))
252 mDragState = DragState::LeftResize;
253 else if (clickPos >= (right - (INT32)RESIZE_HANDLE_SIZE) && clickPos < right)
254 mDragState = DragState::RightResize;
255 else
256 mDragState = DragState::Normal;
257 }
258 else
259 mDragState = DragState::Normal;
260
261 mDragStartPos = ev.getPosition().x - left;
262 }
263 else
264 {
265 INT32 top = (INT32)mLayoutData.area.y + getHandlePosPx();
266
267 if(isResizeable)
268 {
269 INT32 bottom = top + handleSize;
270
271 INT32 clickPos = ev.getPosition().y;
272 if (clickPos >= top && clickPos < (top + (INT32)RESIZE_HANDLE_SIZE))
273 mDragState = DragState::LeftResize;
274 else if (clickPos >= (bottom - (INT32)RESIZE_HANDLE_SIZE) && clickPos < bottom)
275 mDragState = DragState::RightResize;
276 else
277 mDragState = DragState::Normal;
278 }
279 else
280 mDragState = DragState::Normal;
281
282 mDragStartPos = ev.getPosition().y - top;
283 }
284
285 mHandleDragged = true;
286 }
287
288 return true;
289 }
290
291 if(ev.getType() == GUIMouseEventType::MouseDrag && mHandleDragged)
292 {
293 if (!_isDisabled())
294 {
295 if (mDragState == DragState::Normal)
296 {
297 INT32 handlePosPx;
298 if (mFlags.isSet(GUISliderHandleFlag::Horizontal))
299 handlePosPx = ev.getPosition().x - mDragStartPos - mLayoutData.area.x;
300 else
301 handlePosPx = ev.getPosition().y - mDragStartPos - mLayoutData.area.y;
302
303 setHandlePosPx(handlePosPx);
304 onHandleMovedOrResized(mPctHandlePos, _getHandleSizePct());
305 }
306 else // Resizing
307 {
308 INT32 clickPosPx;
309 if (mFlags.isSet(GUISliderHandleFlag::Horizontal))
310 clickPosPx = ev.getPosition().x - mLayoutData.area.x;
311 else
312 clickPosPx = ev.getPosition().y - mLayoutData.area.y;
313
314 INT32 left = getHandlePosPx();
315 UINT32 maxSize = getMaxSize();
316
317 INT32 newHandleSize;
318 float newHandlePos;
319 if(mDragState == DragState::LeftResize)
320 {
321 INT32 newLeft = clickPosPx - mDragStartPos;
322 INT32 right = left + handleSize;
323 newLeft = Math::clamp(newLeft, 0, right);
324
325 newHandleSize = std::max((INT32)mMinHandleSize, right - newLeft);
326 newLeft = right - newHandleSize;
327
328 float scrollableSize = (float)(maxSize - newHandleSize);
329 if (scrollableSize > 0.0f)
330 newHandlePos = newLeft / scrollableSize;
331 else
332 newHandlePos = 0.0f;
333 }
334 else // Right resize
335 {
336 INT32 newRight = clickPosPx;
337 newHandleSize = std::max((INT32)mMinHandleSize, std::min(newRight, (INT32)maxSize) - left);
338
339 float scrollableSize = (float)(maxSize - newHandleSize);
340 if (scrollableSize > 0.0f)
341 newHandlePos = left / scrollableSize;
342 else
343 newHandlePos = 0.0f;
344 }
345
346 _setHandleSize(newHandleSize / (float)maxSize);
347 _setHandlePos(newHandlePos);
348
349 onHandleMovedOrResized(mPctHandlePos, _getHandleSizePct());
350 }
351
352 _markLayoutAsDirty();
353 }
354
355 return true;
356 }
357
358 if(ev.getType() == GUIMouseEventType::MouseOut)
359 {
360 if (!_isDisabled())
361 {
362 mMouseOverHandle = false;
363
364 if (!mHandleDragged)
365 {
366 mState = State::Normal;
367 _markLayoutAsDirty();
368 }
369 }
370
371 return true;
372 }
373
374 if(ev.getType() == GUIMouseEventType::MouseUp)
375 {
376 if (!_isDisabled())
377 {
378 if (mMouseOverHandle)
379 mState = State::Hover;
380 else
381 mState = State::Normal;
382
383 if (!mHandleDragged)
384 {
385 // If we clicked above or below the scroll handle, scroll by one page
386 INT32 handlePosPx = getHandlePosPx();
387 if (!mFlags.isSet(GUISliderHandleFlag::JumpOnClick))
388 {
389 if (mFlags.isSet(GUISliderHandleFlag::Horizontal))
390 {
391 INT32 handleLeft = (INT32)mLayoutData.area.x + handlePosPx;
392 INT32 handleRight = handleLeft + handleSize;
393
394 if (ev.getPosition().x < handleLeft)
395 moveOneStep(false);
396 else if (ev.getPosition().x > handleRight)
397 moveOneStep(true);
398 }
399 else
400 {
401 INT32 handleTop = (INT32)mLayoutData.area.y + handlePosPx;
402 INT32 handleBottom = handleTop + handleSize;
403
404 if (ev.getPosition().y < handleTop)
405 moveOneStep(false);
406 else if (ev.getPosition().y > handleBottom)
407 moveOneStep(true);
408 }
409 }
410 }
411 mHandleDragged = false;
412 _markLayoutAsDirty();
413 }
414
415 return true;
416 }
417
418 if(ev.getType() == GUIMouseEventType::MouseDragEnd)
419 {
420 if (!_isDisabled())
421 {
422 mHandleDragged = false;
423 if (mMouseOverHandle)
424 mState = State::Hover;
425 else
426 mState = State::Normal;
427
428 _markLayoutAsDirty();
429 }
430
431 return true;
432 }
433
434 return false;
435 }
436
437 void GUISliderHandle::moveOneStep(bool forward)
438 {
439 const UINT32 handleSize = getHandleSize();
440 INT32 handlePosPx = getHandlePosPx();
441
442 INT32 stepSizePx;
443 if (mStep > 0.0f)
444 stepSizePx = (INT32)(mStep * getMaxSize());
445 else
446 stepSizePx = (INT32)handleSize;
447
448 handlePosPx += forward ? stepSizePx : -stepSizePx;
449
450 setHandlePosPx(handlePosPx);
451 onHandleMovedOrResized(mPctHandlePos, _getHandleSizePct());
452
453 _markLayoutAsDirty();
454 }
455
456 bool GUISliderHandle::isOnHandle(const Vector2I& pos) const
457 {
458 UINT32 handleSize = getHandleSize();
459 if(mFlags.isSet(GUISliderHandleFlag::Horizontal))
460 {
461 INT32 left = (INT32)mLayoutData.area.x + getHandlePosPx();
462 INT32 right = left + handleSize;
463
464 if(pos.x >= left && pos.x < right)
465 return true;
466 }
467 else
468 {
469 INT32 top = (INT32)mLayoutData.area.y + getHandlePosPx();
470 INT32 bottom = top + handleSize;
471
472 if(pos.y >= top && pos.y < bottom)
473 return true;
474 }
475
476 return false;
477 }
478
479 INT32 GUISliderHandle::getHandlePosPx() const
480 {
481 INT32 maxScrollAmount = std::max(0, (INT32)getMaxSize() - (INT32)getHandleSize());
482 return Math::floorToInt(mPctHandlePos * maxScrollAmount);
483 }
484
485 UINT32 GUISliderHandle::getHandleSize() const
486 {
487 return std::max(mMinHandleSize, (UINT32)(getMaxSize() * mPctHandleSize));
488 }
489
490 float GUISliderHandle::_getHandleSizePct() const
491 {
492 return mPctHandleSize;
493 }
494
495 void GUISliderHandle::styleUpdated()
496 {
497 const GUIElementStyle* style = _getStyle();
498 if (style != nullptr)
499 {
500 if (mFlags.isSet(GUISliderHandleFlag::Horizontal))
501 mMinHandleSize = style->fixedWidth ? style->width : style->minWidth;
502 else
503 mMinHandleSize = style->fixedHeight ? style->height : style->minHeight;
504 }
505 }
506
507 void GUISliderHandle::setHandlePosPx(INT32 pos)
508 {
509 float scrollableSize = (float)getMaxSize() - getHandleSize();
510
511 if (scrollableSize > 0.0f)
512 _setHandlePos(pos / scrollableSize);
513 else
514 _setHandlePos(0.0f);
515 }
516
517 UINT32 GUISliderHandle::getMaxSize() const
518 {
519 UINT32 maxSize = mLayoutData.area.height;
520 if(mFlags.isSet(GUISliderHandleFlag::Horizontal))
521 maxSize = mLayoutData.area.width;
522
523 return maxSize;
524 }
525
526 const HSpriteTexture& GUISliderHandle::getActiveTexture() const
527 {
528 switch(mState)
529 {
530 case State::Active:
531 return _getStyle()->active.texture;
532 case State::Hover:
533 return _getStyle()->hover.texture;
534 case State::Normal:
535 return _getStyle()->normal.texture;
536 }
537
538 return _getStyle()->normal.texture;
539 }
540}