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 | |
10 | namespace 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 | } |