1 /*
2 * Copyright (C) 2011-2012 Me and My Shadow
3 *
4 * This file is part of Me and My Shadow.
5 *
6 * Me and My Shadow is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Me and My Shadow is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "Functions.h"
21 #include "GUIObject.h"
22 #include "ThemeManager.h"
23 #include "InputManager.h"
24 #include "GUIListBox.h"
25 #include "GUISlider.h"
26 #include <algorithm>
27 #include <iostream>
28 #include <list>
29 #include <SDL_ttf.h>
30
31 #include "Render.h"
32
33 using namespace std;
34
35 //Set the GUIObjectRoot to NULL.
36 GUIObject* GUIObjectRoot=NULL;
37 //Initialise the event queue.
38 list<GUIEvent> GUIEventQueue;
39
40 //A boolean variable used to skip next mouse up event for GUI (temporary workaround).
41 bool GUISkipNextMouseUpEvent = false;
42
GUIObjectHandleEvents(ImageManager & imageManager,SDL_Renderer & renderer,bool kill)43 void GUIObjectHandleEvents(ImageManager& imageManager, SDL_Renderer& renderer, bool kill){
44 //Check if we need to reset the skip variable.
45 if (event.type == SDL_MOUSEBUTTONDOWN) {
46 GUISkipNextMouseUpEvent = false;
47 }
48
49 //Check if we need to skip event.
50 if (event.type == SDL_MOUSEBUTTONUP && GUISkipNextMouseUpEvent) {
51 GUISkipNextMouseUpEvent = false;
52 } else {
53 //Make sure that GUIObjectRoot isn't null.
54 if (GUIObjectRoot)
55 GUIObjectRoot->handleEvents(renderer);
56 }
57
58 //Check for SDL_QUIT.
59 if(event.type==SDL_QUIT && kill){
60 //We get a quit event so enter the exit state.
61 setNextState(STATE_EXIT);
62 delete GUIObjectRoot;
63 GUIObjectRoot=NULL;
64 return;
65 }
66
67 //Keep calling events until there are none left.
68 while(!GUIEventQueue.empty()){
69 //Get one event and remove it from the queue.
70 GUIEvent e=GUIEventQueue.front();
71 GUIEventQueue.pop_front();
72
73 //If an eventCallback exist call it.
74 if(e.eventCallback){
75 e.eventCallback->GUIEventCallback_OnEvent(imageManager,renderer,e.name,e.obj,e.eventType);
76 }
77 }
78 //We empty the event queue just to be sure.
79 GUIEventQueue.clear();
80 }
81
GUIObject(ImageManager & imageManager,SDL_Renderer & renderer,int left,int top,int width,int height,const char * caption,int value,bool enabled,bool visible,int gravity)82 GUIObject::GUIObject(ImageManager& imageManager, SDL_Renderer& renderer, int left, int top, int width, int height,
83 const char* caption, int value,
84 bool enabled, bool visible, int gravity) :
85 left(left), top(top), width(width), height(height),
86 gravity(gravity), value(value),
87 enabled(enabled), visible(visible),
88 eventCallback(NULL), state(0),
89 cachedEnabled(enabled), gravityX(0),
90 gravityLeft(0), gravityTop(0), gravityRight(0), gravityBottom(0)
91 {
92 //Make sure that caption isn't NULL before setting it.
93 if (caption){
94 GUIObject::caption = caption;
95 //And set the cached caption.
96 cachedCaption = caption;
97 }
98
99 if (width <= 0)
100 autoWidth = true;
101 else
102 autoWidth = false;
103
104 inDialog = false;
105
106 //Load the gui images.
107 bmGuiTex = imageManager.loadTexture(getDataPath() + "gfx/gui.png", renderer);
108 }
109
~GUIObject()110 GUIObject::~GUIObject(){
111 //We need to delete every child we have.
112 for(unsigned int i=0;i<childControls.size();i++){
113 delete childControls[i];
114 }
115 //Deleted the childs now empty the childControls vector.
116 childControls.clear();
117 }
118
addChild(GUIObject * obj)119 void GUIObject::addChild(GUIObject* obj){
120 //Add widget add a child
121 childControls.push_back(obj);
122
123 //Copy inDialog boolean from parent.
124 obj->inDialog = inDialog;
125 }
126
getChild(const std::string & name)127 GUIObject* GUIObject::getChild(const std::string& name){
128 //Look for a child with the name.
129 for (unsigned int i = 0; i<childControls.size(); i++)
130 if (childControls[i]->name == name)
131 return childControls[i];
132
133 //Not found so return NULL.
134 return NULL;
135 }
136
handleEvents(SDL_Renderer & renderer,int x,int y,bool enabled,bool visible,bool processed)137 bool GUIObject::handleEvents(SDL_Renderer& renderer,int x,int y,bool enabled,bool visible,bool processed){
138 //Boolean if the event is processed.
139 bool b=processed;
140
141 //The GUIObject is only enabled when its parent are enabled.
142 enabled=enabled && this->enabled;
143 //The GUIObject is only enabled when its parent are enabled.
144 visible=visible && this->visible;
145
146 //Get the absolute position.
147 x+=left-gravityX;
148 y+=top;
149
150 //Also let the children handle their events.
151 for(unsigned int i=0;i<childControls.size();i++){
152 bool b1=childControls[i]->handleEvents(renderer,x,y,enabled,visible,b);
153
154 //The event is processed when either our or the childs is true (or both).
155 b=b||b1;
156 }
157 return b;
158 }
159
render(SDL_Renderer & renderer,int x,int y,bool draw)160 void GUIObject::render(SDL_Renderer& renderer, int x,int y,bool draw){
161 //There's no need drawing the GUIObject when it's invisible.
162 if(!visible)
163 return;
164
165 //Get the absolute x and y location.
166 x+=left;
167 y+=top;
168
169 //We now need to draw all the children of the GUIObject.
170 for(unsigned int i=0;i<childControls.size();i++){
171 childControls[i]->render(renderer,x,y,draw);
172 }
173 }
174
onResize()175 void GUIObject::onResize() {
176 }
177
refreshCache(bool enabled)178 void GUIObject::refreshCache(bool enabled) {
179 //Check if the enabled state changed or the caption, if so we need to clear the (old) cache.
180 if(enabled!=cachedEnabled || caption.compare(cachedCaption)!=0 || width<=0){
181 //TODO: Only change alpha if only enabled changes.
182 //Free the cache.
183 cacheTex.reset(nullptr);
184
185 //And cache the new values.
186 cachedEnabled=enabled;
187 cachedCaption=caption;
188
189 //Finally resize the widget
190 if(autoWidth)
191 width=-1;
192 }
193 }
194
getSelectedControl()195 int GUIObject::getSelectedControl() {
196 for (int i = 0; i < (int)childControls.size(); i++) {
197 GUIObject *obj = childControls[i];
198 if (obj && obj->visible && obj->enabled && obj->state) {
199 if (dynamic_cast<GUITextBox*>(obj) && obj->state == 2) {
200 return i;
201 } else if (dynamic_cast<GUIButton*>(obj) || dynamic_cast<GUICheckBox*>(obj)
202 || dynamic_cast<GUISingleLineListBox*>(obj)
203 || dynamic_cast<GUISlider*>(obj)
204 )
205 {
206 return i;
207 }
208 }
209 }
210
211 return -1;
212 }
213
setSelectedControl(int index)214 void GUIObject::setSelectedControl(int index) {
215 for (int i = 0; i < (int)childControls.size(); i++) {
216 GUIObject *obj = childControls[i];
217 if (obj && obj->visible && obj->enabled) {
218 if (dynamic_cast<GUIButton*>(obj) || dynamic_cast<GUICheckBox*>(obj)) {
219 //It's a button.
220 obj->state = (i == index) ? 1 : 0;
221 } else if (dynamic_cast<GUITextBox*>(obj)) {
222 //It's a text box (or a spin box).
223 if(i == index) {
224 obj->state = 2;
225 } else {
226 dynamic_cast<GUITextBox*>(obj)->blur();
227 }
228 } else if (dynamic_cast<GUISingleLineListBox*>(obj)) {
229 //It's a single line list box.
230 obj->state = (i == index) ? 0x100 : 0;
231 } else if (dynamic_cast<GUISlider*>(obj)) {
232 //It's a slider.
233 obj->state = (i == index) ? 0x10000 : 0;
234 }
235 }
236 }
237 }
238
selectNextControl(int direction,int selected)239 int GUIObject::selectNextControl(int direction, int selected) {
240 //Get the index of currently selected control.
241 if (selected == 0x80000000) {
242 selected = getSelectedControl();
243 }
244
245 //Find the next control.
246 for (int i = 0; i < (int)childControls.size(); i++) {
247 if (selected < 0) {
248 selected = 0;
249 } else {
250 selected += direction;
251 if (selected >= (int)childControls.size()) {
252 selected -= childControls.size();
253 } else if (selected < 0) {
254 selected += childControls.size();
255 }
256 }
257
258 GUIObject *obj = childControls[selected];
259 if (obj && obj->visible && obj->enabled) {
260 if (dynamic_cast<GUIButton*>(obj) || dynamic_cast<GUICheckBox*>(obj)
261 || dynamic_cast<GUITextBox*>(obj)
262 || dynamic_cast<GUISingleLineListBox*>(obj)
263 || dynamic_cast<GUISlider*>(obj)
264 )
265 {
266 setSelectedControl(selected);
267 return selected;
268 }
269 }
270 }
271
272 return -1;
273 }
274
handleKeyboardNavigationEvents(ImageManager & imageManager,SDL_Renderer & renderer,int keyboardNavigationMode)275 bool GUIObject::handleKeyboardNavigationEvents(ImageManager& imageManager, SDL_Renderer& renderer, int keyboardNavigationMode) {
276 if (keyboardNavigationMode == 0) return false;
277
278 //Check operation on focused control. These have higher priority.
279 if (isKeyboardOnly) {
280 //Check enter key.
281 if ((keyboardNavigationMode & ReturnControls) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_SELECT)) {
282 int index = getSelectedControl();
283 if (index >= 0) {
284 GUIObject *obj = childControls[index];
285
286 if (dynamic_cast<GUIButton*>(obj)) {
287 //It's a button.
288 if (obj->eventCallback) {
289 obj->eventCallback->GUIEventCallback_OnEvent(imageManager, renderer, obj->name, obj, GUIEventClick);
290 }
291 return true;
292 }
293 if (dynamic_cast<GUICheckBox*>(obj)) {
294 //It's a check box.
295 obj->value = obj->value ? 0 : 1;
296 if (obj->eventCallback) {
297 obj->eventCallback->GUIEventCallback_OnEvent(imageManager, renderer, obj->name, obj, GUIEventClick);
298 }
299 return true;
300 }
301 }
302 }
303
304 //Check left/right key.
305 if ((keyboardNavigationMode & LeftRightControls) != 0 && (inputMgr.isKeyDownEvent(INPUTMGR_LEFT) || inputMgr.isKeyDownEvent(INPUTMGR_RIGHT))) {
306 int index = getSelectedControl();
307 if (index >= 0) {
308 GUIObject *obj = childControls[index];
309
310 auto sllb = dynamic_cast<GUISingleLineListBox*>(obj);
311 if (sllb) {
312 //It's a single line list box.
313 int newValue = sllb->value + (inputMgr.isKeyDownEvent(INPUTMGR_RIGHT) ? 1 : -1);
314 if (newValue >= (int)sllb->item.size()) {
315 newValue -= sllb->item.size();
316 } else if (newValue < 0) {
317 newValue += sllb->item.size();
318 }
319
320 if (sllb->value != newValue) {
321 sllb->value = newValue;
322 if (obj->eventCallback) {
323 obj->eventCallback->GUIEventCallback_OnEvent(imageManager, renderer, obj->name, obj, GUIEventClick);
324 }
325 }
326 return true;
327 }
328
329 auto slider = dynamic_cast<GUISlider*>(obj);
330 if (slider) {
331 //It's a slider.
332 int newValue = slider->value + (inputMgr.isKeyDownEvent(INPUTMGR_RIGHT) ? slider->largeChange : -slider->largeChange);
333 newValue = clamp(newValue, slider->minValue, slider->maxValue);
334
335 if (slider->value != newValue) {
336 slider->value = newValue;
337 if (obj->eventCallback) {
338 obj->eventCallback->GUIEventCallback_OnEvent(imageManager, renderer, obj->name, obj, GUIEventChange);
339 }
340 }
341 return true;
342 }
343 }
344
345 }
346 }
347
348 //Check if we need to exclude printable characters
349 bool excludePrintable = false;
350 {
351 int index = getSelectedControl();
352 if (index >= 0) {
353 GUIObject *obj = childControls[index];
354 if (dynamic_cast<GUITextBox*>(obj)) {
355 excludePrintable = true;
356 }
357 }
358 }
359
360 //Check focus movement
361 int m = SDL_GetModState();
362 if (((keyboardNavigationMode & LeftRightFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_RIGHT, excludePrintable))
363 || ((keyboardNavigationMode & UpDownFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_DOWN, excludePrintable))
364 || ((keyboardNavigationMode & TabFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_TAB, excludePrintable) && (m & KMOD_SHIFT) == 0)
365 )
366 {
367 isKeyboardOnly = true;
368 selectNextControl(1);
369 return true;
370 } else if (((keyboardNavigationMode & LeftRightFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_LEFT, excludePrintable))
371 || ((keyboardNavigationMode & UpDownFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_UP, excludePrintable))
372 || ((keyboardNavigationMode & TabFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_TAB, excludePrintable) && (m & KMOD_SHIFT) != 0)
373 )
374 {
375 isKeyboardOnly = true;
376 selectNextControl(-1);
377 return true;
378 }
379
380 return false;
381 }
382
383 //////////////GUIButton///////////////////////////////////////////////////////////////////
384
handleEvents(SDL_Renderer & renderer,int x,int y,bool enabled,bool visible,bool processed)385 bool GUIButton::handleEvents(SDL_Renderer& renderer,int x,int y,bool enabled,bool visible,bool processed){
386 //Boolean if the event is processed.
387 bool b=processed;
388
389 //The widget is only enabled when its parent are enabled.
390 enabled=enabled && this->enabled;
391 //The widget is only enabled when its parent are enabled.
392 visible=visible && this->visible;
393
394 //Get the absolute position.
395 x+=left-gravityX;
396 y+=top;
397
398 //We don't update button state under keyboard only mode.
399 if (!isKeyboardOnly) {
400 //Set state to 0.
401 state = 0;
402
403 //Only check for events when the object is both enabled and visible.
404 if (enabled && visible) {
405 //The mouse location (x=i, y=j) and the mouse button (k).
406 int i, j, k;
407 k = SDL_GetMouseState(&i, &j);
408
409 //Check if the mouse is inside the widget.
410 if (i >= x && i < x + width && j >= y && j < y + height) {
411 //We have hover so set state to one.
412 state = 1;
413 //Check for a mouse button press.
414 if (k&SDL_BUTTON(1))
415 state = 2;
416
417 //Check if there's a mouse press and the event hasn't been already processed.
418 if (event.type == SDL_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_LEFT && !b) {
419 //If event callback is configured then add an event to the queue.
420 if (eventCallback) {
421 GUIEvent e = { eventCallback, name, this, GUIEventClick };
422 GUIEventQueue.push_back(e);
423 }
424
425 //Event has been processed.
426 b = true;
427 }
428 }
429 }
430 }
431
432 //Also let the children handle their events.
433 for(unsigned int i=0;i<childControls.size();i++){
434 bool b1=childControls[i]->handleEvents(renderer,x,y,enabled,visible,b);
435
436 //The event is processed when either our or the childs is true (or both).
437 b=b||b1;
438 }
439 return b;
440 }
441
render(SDL_Renderer & renderer,int x,int y,bool draw)442 void GUIButton::render(SDL_Renderer& renderer, int x,int y,bool draw){
443 //There's no need drawing the widget when it's invisible.
444 if(!visible)
445 return;
446
447 //Get the absolute x and y location.
448 x+=left;
449 y+=top;
450
451 refreshCache(enabled);
452
453 //Get the text and make sure it isn't empty.
454 const char* lp=caption.c_str();
455 if(lp!=NULL && lp[0]){
456 //Update cache if needed.
457 if(!cacheTex){
458 SDL_Color color = objThemes.getTextColor(inDialog);
459
460 if(!smallFont) {
461 cacheTex = textureFromText(renderer, *fontGUI, lp, color);
462 } else {
463 cacheTex = textureFromText(renderer, *fontGUISmall, lp, color);
464 }
465 //Make the widget transparent if it's disabled.
466 if(!enabled) {
467 SDL_SetTextureAlphaMod(cacheTex.get(), 128);
468 }
469 //Calculate proper size for the widget.
470 if(width<=0){
471 width=textureWidth(*cacheTex)+50;
472 if(gravity==GUIGravityCenter){
473 gravityX=int(width/2);
474 }else if(gravity==GUIGravityRight){
475 gravityX=width;
476 }else{
477 gravityX=0;
478 }
479 }
480 }
481
482 if(draw){
483 //Center the text both vertically as horizontally.
484 const SDL_Rect size = rectFromTexture(*cacheTex);
485 const int drawX=x-gravityX+(width-size.w)/2;
486 const int drawY=y+(height-size.h)/2-GUI_FONT_RAISE;
487
488 //Check if the arrows don't fall of.
489 if(size.w+32<=width){
490 if(state==1){
491 if(inDialog){
492 applyTexture(x-gravityX+(width-size.w)/2+4+size.w+5,y+2,*arrowLeft2,renderer);
493 applyTexture(x-gravityX+(width-size.w)/2-25,y+2,*arrowRight2,renderer);
494 }else{
495 applyTexture(x-gravityX+(width-size.w)/2+4+size.w+5,y+2,*arrowLeft1,renderer);
496 applyTexture(x-gravityX+(width-size.w)/2-25,y+2,*arrowRight1,renderer);
497 }
498 }else if(state==2){
499 if(inDialog){
500 applyTexture(x-gravityX+(width-size.w)/2+4+size.w,y+2,*arrowLeft2,renderer);
501 applyTexture(x-gravityX+(width-size.w)/2-20,y+2,*arrowRight2,renderer);
502 }else{
503 applyTexture(x-gravityX+(width-size.w)/2+4+size.w,y+2,*arrowLeft1,renderer);
504 applyTexture(x-gravityX+(width-size.w)/2-20,y+2,arrowRight1,renderer);
505 }
506 }
507 }
508
509 //Draw the text.
510 applyTexture(drawX, drawY, *cacheTex, renderer);
511 }
512 }
513 }
514
515 //////////////GUICheckBox///////////////////////////////////////////////////////////////////
516
handleEvents(SDL_Renderer &,int x,int y,bool enabled,bool visible,bool processed)517 bool GUICheckBox::handleEvents(SDL_Renderer&,int x,int y,bool enabled,bool visible,bool processed){
518 //Boolean if the event is processed.
519 bool b=processed;
520
521 //The widget is only enabled when its parent are enabled.
522 enabled=enabled && this->enabled;
523 //The widget is only enabled when its parent are enabled.
524 visible=visible && this->visible;
525
526 //Get the absolute position.
527 x+=left-gravityX;
528 y+=top;
529
530 //We don't update state under keyboard only mode.
531 if (!isKeyboardOnly) {
532 //Set state to 0.
533 state = 0;
534
535 //Only check for events when the object is both enabled and visible.
536 if (enabled&&visible){
537 //The mouse location (x=i, y=j) and the mouse button (k).
538 int i, j, k;
539 k = SDL_GetMouseState(&i, &j);
540
541 //Check if the mouse is inside the widget.
542 if (i >= x && i < x + width && j >= y && j < y + height){
543 //We have hover so set state to one.
544 state = 1;
545 //Check for a mouse button press.
546 if (k&SDL_BUTTON(1))
547 state = 2;
548
549 //Check if there's a mouse press and the event hasn't been already processed.
550 if (event.type == SDL_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_LEFT && !b){
551 //It's a checkbox so toggle the value.
552 value = value ? 0 : 1;
553
554 //If event callback is configured then add an event to the queue.
555 if (eventCallback){
556 GUIEvent e = { eventCallback, name, this, GUIEventClick };
557 GUIEventQueue.push_back(e);
558 }
559
560 //Event has been processed.
561 b = true;
562 }
563 }
564 }
565 }
566
567 return b;
568 }
569
render(SDL_Renderer & renderer,int x,int y,bool draw)570 void GUICheckBox::render(SDL_Renderer& renderer, int x,int y,bool draw){
571 //There's no need drawing the widget when it's invisible.
572 if(!visible)
573 return;
574
575 //Get the absolute x and y location.
576 x+=left;
577 y+=top;
578
579 refreshCache(enabled);
580
581 //Draw the highlight in keyboard only mode.
582 if (isKeyboardOnly && state && draw) {
583 drawGUIBox(x, y, width, height, renderer, 0xFFFFFF40);
584 }
585
586 //Get the text.
587 const char* lp=caption.c_str();
588 //Make sure it isn't empty.
589 if(lp!=NULL && lp[0]){
590 //Update the cache if needed.
591 if(!cacheTex){
592 SDL_Color color = objThemes.getTextColor(inDialog);
593
594 cacheTex=textureFromText(renderer,*fontText,lp,color);
595 }
596
597 if(draw){
598 //Calculate the location, center it vertically.
599 const int drawX=x;
600 const int drawY=y+(height - textureHeight(*cacheTex))/2;
601
602 //Draw the text
603 applyTexture(drawX, drawY, *cacheTex, renderer);
604 }
605 }
606
607 if(draw){
608 //Draw the check (or not).
609 //value*16 determines where in the gui textures we draw from.
610 //if(value==1||value==2)
611 // r1.x=value*16;
612 const SDL_Rect srcRect={value*16,0,16,16};
613 const SDL_Rect dstRect={x+width-20, y+(height-16)/2, 16, 16};
614 //Get the right image depending on the state of the object.
615 SDL_RenderCopy(&renderer, bmGuiTex.get(), &srcRect, &dstRect);
616 }
617 }
618
619 //////////////GUILabel///////////////////////////////////////////////////////////////////
620
handleEvents(SDL_Renderer &,int,int,bool,bool,bool processed)621 bool GUILabel::handleEvents(SDL_Renderer&,int ,int ,bool ,bool ,bool processed){
622 return processed;
623 }
624
render(SDL_Renderer & renderer,int x,int y,bool draw)625 void GUILabel::render(SDL_Renderer& renderer, int x,int y,bool draw){
626 //There's no need drawing the widget when it's invisible.
627 if(!visible)
628 return;
629
630 //Check if the enabled state changed or the caption, if so we need to clear the (old) cache.
631 refreshCache(enabled);
632
633 //Get the absolute x and y location.
634 x+=left;
635 y+=top;
636
637 //Rectangle the size of the widget.
638 SDL_Rect r;
639 r.x=x;
640 r.y=y;
641 r.w=width;
642 r.h=height;
643
644 //Get the caption and make sure it isn't empty.
645 const char* lp=caption.c_str();
646 if(lp!=NULL && lp[0]){
647 //Update cache if needed.
648 if(!cacheTex){
649 SDL_Color color = objThemes.getTextColor(inDialog);
650
651 cacheTex=textureFromText(renderer, *fontText, lp, color);
652 if(width<=0)
653 width=textureWidth(*cacheTex);
654 }
655
656 //Align the text properly and draw it.
657 if(draw){
658 const SDL_Rect size = rectFromTexture(*cacheTex);
659 if(gravity==GUIGravityCenter)
660 gravityX=(width-size.w)/2;
661 else if(gravity==GUIGravityRight)
662 gravityX=width-size.w;
663 else
664 gravityX=0;
665
666 r.y=y+(height - size.h)/2;
667 r.x+=gravityX;
668 applyTexture(r.x, r.y, cacheTex, renderer);
669 }
670 }
671 }
672
673 //////////////GUITextBox///////////////////////////////////////////////////////////////////
674
backspaceChar()675 void GUITextBox::backspaceChar(){
676 //We need to remove a character so first make sure that there is text.
677 if(caption.length()>0){
678 if(highlightStart==highlightEnd&&highlightStart>0){
679 int advance = 0;
680
681 // this is proper UTF-8 support
682 int ch = utf8ReadBackward(caption.c_str(), highlightStart); // we obtain new highlightStart from this
683 if (ch > 0) TTF_GlyphMetrics(fontText, ch, NULL, NULL, NULL, NULL, &advance);
684 highlightEndX = highlightStartX = highlightEndX - advance;
685
686 caption.erase(highlightStart, highlightEnd - highlightStart);
687 highlightEnd = highlightStart;
688 } else if (highlightStart<highlightEnd){
689 caption.erase(highlightStart,highlightEnd-highlightStart);
690 highlightEnd=highlightStart;
691 highlightEndX=highlightStartX;
692 }else{
693 caption.erase(highlightEnd,highlightStart-highlightEnd);
694 highlightStart=highlightEnd;
695 highlightStartX=highlightEndX;
696 }
697
698 //If there is an event callback then call it.
699 if(eventCallback){
700 GUIEvent e={eventCallback,name,this,GUIEventChange};
701 GUIEventQueue.push_back(e);
702 }
703 }
704 }
705
deleteChar()706 void GUITextBox::deleteChar(){
707 //We need to remove a character so first make sure that there is text.
708 if(caption.length()>0){
709 if(highlightStart==highlightEnd){
710 // this is proper utf8 support
711 int i = highlightEnd;
712 utf8ReadForward(caption.c_str(), i);
713 if (i > highlightEnd) caption.erase(highlightEnd, i - highlightEnd);
714
715 highlightStart=highlightEnd;
716 highlightStartX=highlightEndX;
717 }else if(highlightStart<highlightEnd){
718 caption.erase(highlightStart,highlightEnd-highlightStart);
719
720 highlightEnd=highlightStart;
721 highlightEndX=highlightStartX;
722 }else{
723 caption.erase(highlightEnd,highlightStart-highlightEnd);
724
725 highlightStart=highlightEnd;
726 highlightStartX=highlightEndX;
727 }
728
729 //If there is an event callback then call it.
730 if(eventCallback){
731 GUIEvent e={eventCallback,name,this,GUIEventChange};
732 GUIEventQueue.push_back(e);
733 }
734 }
735 }
736
moveCarrotLeft()737 void GUITextBox::moveCarrotLeft(){
738 if(highlightEnd>0){
739 int advance = 0;
740
741 // this is proper UTF-8 support
742 int ch = utf8ReadBackward(caption.c_str(), highlightEnd); // we obtain new highlightEnd from this
743 if (ch > 0) TTF_GlyphMetrics(fontText, ch, NULL, NULL, NULL, NULL, &advance);
744
745 if(SDL_GetModState() & KMOD_SHIFT){
746 highlightEndX-=advance;
747 }else{
748 highlightStart=highlightEnd;
749 highlightStartX=highlightEndX=highlightEndX-advance;
750 }
751 }else{
752 if((SDL_GetModState() & KMOD_SHIFT)==0){
753 highlightStart=highlightEnd;
754 highlightStartX=highlightEndX;
755 }
756 }
757 tick=15;
758 }
759
moveCarrotRight()760 void GUITextBox::moveCarrotRight(){
761 if(highlightEnd<caption.length()){
762 int advance = 0;
763
764 // this is proper UTF-8 support
765 int ch = utf8ReadForward(caption.c_str(), highlightEnd); // we obtain new highlightEnd from this
766 if (ch > 0) TTF_GlyphMetrics(fontText, ch, NULL, NULL, NULL, NULL, &advance);
767
768 if(SDL_GetModState() & KMOD_SHIFT){
769 highlightEndX+=advance;
770 }else{
771 highlightStartX=highlightEndX=highlightEndX+advance;
772 highlightStart=highlightEnd;
773 }
774 }else{
775 if((SDL_GetModState() & KMOD_SHIFT)==0){
776 highlightStart=highlightEnd;
777 highlightStartX=highlightEndX;
778 }
779 }
780 tick=15;
781 }
782
updateText(const std::string & text)783 void GUITextBox::updateText(const std::string& text) {
784 caption = text;
785 updateSelection(0, 0);
786 }
787
updateSelection(int start,int end)788 void GUITextBox::updateSelection(int start, int end) {
789 start = clamp(start, 0, caption.size());
790 end = clamp(end, 0, caption.size());
791
792 highlightStart = start;
793 highlightStartX = 0;
794 highlightEnd = end;
795 highlightEndX = 0;
796
797 if (start > 0) {
798 TTF_SizeUTF8(fontText, caption.substr(0, start).c_str(), &highlightStartX, NULL);
799 }
800 if (end > 0) {
801 TTF_SizeUTF8(fontText, caption.substr(0, end).c_str(), &highlightEndX, NULL);
802 }
803 }
804
inputText(const char * s)805 void GUITextBox::inputText(const char* s) {
806 int m = strlen(s);
807
808 if (m > 0){
809 if (highlightStart == highlightEnd) {
810 caption.insert((size_t)highlightStart, s);
811 highlightStart += m;
812 highlightEnd = highlightStart;
813 } else if (highlightStart < highlightEnd) {
814 caption.erase(highlightStart, highlightEnd - highlightStart);
815 caption.insert((size_t)highlightStart, s);
816 highlightStart += m;
817 highlightEnd = highlightStart;
818 highlightEndX = highlightStartX;
819 } else {
820 caption.erase(highlightEnd, highlightStart - highlightEnd);
821 caption.insert((size_t)highlightEnd, s);
822 highlightEnd += m;
823 highlightStart = highlightEnd;
824 highlightStartX = highlightEndX;
825 }
826 int advance = 0;
827 for (int i = 0;;) {
828 int a = 0;
829 int ch = utf8ReadForward(s, i);
830 if (ch <= 0) break;
831 TTF_GlyphMetrics(fontText, ch, NULL, NULL, NULL, NULL, &a);
832 advance += a;
833 }
834 highlightStartX = highlightEndX = highlightStartX + advance;
835
836 //If there is an event callback then call it.
837 if (eventCallback){
838 GUIEvent e = { eventCallback, name, this, GUIEventChange };
839 GUIEventQueue.push_back(e);
840 }
841 }
842 }
843
blur()844 void GUITextBox::blur(){
845 state = 0;
846 highlightStart=highlightStartX=0;
847 highlightEnd=highlightEndX=0;
848 }
849
handleEvents(SDL_Renderer &,int x,int y,bool enabled,bool visible,bool processed)850 bool GUITextBox::handleEvents(SDL_Renderer&,int x,int y,bool enabled,bool visible,bool processed){
851 //Boolean if the event is processed.
852 bool b=processed;
853
854 //The widget is only enabled when its parent are enabled.
855 enabled=enabled && this->enabled;
856 //The widget is only enabled when its parent are enabled.
857 visible=visible && this->visible;
858
859 //Get the absolute position.
860 x+=left-gravityX;
861 y+=top;
862
863 //NOTE: We don't reset the state to have a "focus" effect.
864
865 //Only check for events when the object is both enabled and visible.
866 if(enabled&&visible){
867 //Check if there's a key press and the event hasn't been already processed.
868 if(state==2 && event.type==SDL_KEYDOWN && !b){
869 //Get the keycode.
870 SDL_Keycode key=event.key.keysym.sym;
871
872 if ((event.key.keysym.mod & KMOD_CTRL) == 0) {
873 //Check if the key is supported.
874 if (event.key.keysym.sym == SDLK_BACKSPACE){
875 backspaceChar();
876 } else if (event.key.keysym.sym == SDLK_DELETE){
877 deleteChar();
878 } else if (event.key.keysym.sym == SDLK_RIGHT){
879 moveCarrotRight();
880 } else if (event.key.keysym.sym == SDLK_LEFT){
881 moveCarrotLeft();
882 }
883 } else {
884 //Check hotkey.
885 if (event.key.keysym.sym == SDLK_a) {
886 //Select all.
887 highlightStart = 0;
888 highlightStartX = 0;
889 highlightEnd = caption.size();
890 highlightEndX = 0;
891 if (highlightEnd > 0) {
892 TTF_SizeUTF8(fontText, caption.c_str(), &highlightEndX, NULL);
893 }
894 } else if (event.key.keysym.sym == SDLK_x || event.key.keysym.sym == SDLK_c) {
895 //Cut or copy.
896 int start = highlightStart, end = highlightEnd;
897 if (start > end) std::swap(start, end);
898 if (start < end) {
899 SDL_SetClipboardText(caption.substr(start, end - start).c_str());
900 if (event.key.keysym.sym == SDLK_x) {
901 //Cut.
902 backspaceChar();
903 }
904 }
905 } else if (event.key.keysym.sym == SDLK_v) {
906 //Paste.
907 if (SDL_HasClipboardText()) {
908 char *s = SDL_GetClipboardText();
909 inputText(s);
910 SDL_free(s);
911 }
912 }
913 }
914
915 //The event has been processed.
916 b = true;
917 } else if (state == 2 && event.type == SDL_TEXTINPUT && !b){
918 inputText(event.text.text);
919
920 //The event has been processed.
921 b = true;
922 } else if (state == 2 && event.type == SDL_TEXTEDITING && !b){
923 // TODO: process SDL_TEXTEDITING event
924 }
925
926 //Only process mouse event when not in keyboard only mode
927 if (!isKeyboardOnly) {
928 //The mouse location (x=i, y=j) and the mouse button (k).
929 int i, j, k;
930 k = SDL_GetMouseState(&i, &j);
931
932 //Check if the mouse is inside the widget.
933 if (i >= x && i < x + width && j >= y && j < y + height){
934 //We can only increase our state. (nothing->hover->focus).
935 if (state != 2){
936 state = 1;
937 }
938
939 //Also update the cursor type.
940 currentCursor = CURSOR_CARROT;
941
942 //Move carrot and highlightning according to mouse input.
943 int clickX = i - x - 2;
944
945 int finalPos = 0;
946 int finalX = 0;
947
948 if (cacheTex&&!caption.empty()){
949 finalPos = caption.length();
950 for (int i = 0;;){
951 int advance = 0;
952
953 // this is proper UTF-8 support
954 int i0 = i;
955 int ch = utf8ReadForward(caption.c_str(), i);
956 if (ch <= 0) break;
957 TTF_GlyphMetrics(fontText, ch, NULL, NULL, NULL, NULL, &advance);
958 finalX += advance;
959
960 if (clickX < finalX - advance / 2){
961 finalPos = i0;
962 finalX -= advance;
963 break;
964 }
965 }
966 }
967
968 if (event.type == SDL_MOUSEBUTTONUP && state == 2){
969 state = 2;
970 highlightEnd = finalPos;
971 highlightEndX = finalX;
972 } else if (event.type == SDL_MOUSEBUTTONDOWN){
973 state = 2;
974 highlightStart = highlightEnd = finalPos;
975 highlightStartX = highlightEndX = finalX;
976 } else if (event.type == SDL_MOUSEMOTION && (k&SDL_BUTTON(1)) && state == 2){
977 state = 2;
978 highlightEnd = finalPos;
979 highlightEndX = finalX;
980 }
981 } else{
982 //The mouse is outside the TextBox.
983 //If we don't have focus but only hover we lose it.
984 if (state == 1){
985 state = 0;
986 }
987
988 //If it's a click event outside the textbox then we blur.
989 if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT){
990 blur();
991 }
992 }
993 }
994 }
995
996 return b;
997 }
998
render(SDL_Renderer & renderer,int x,int y,bool draw)999 void GUITextBox::render(SDL_Renderer& renderer, int x,int y,bool draw){
1000 //There's no need drawing the widget when it's invisible.
1001 if(!visible)
1002 return;
1003
1004 //Get the absolute x and y location.
1005 x+=left;
1006 y+=top;
1007
1008 //Check if the enabled state changed or the caption, if so we need to clear the (old) cache.
1009 refreshCache(enabled);
1010
1011 if(draw){
1012 //Default background opacity
1013 int clr=50;
1014 //If hovering or focused make background more visible.
1015 if(state==1)
1016 clr=128;
1017 else if (state==2)
1018 clr=100;
1019
1020 //Draw the box.
1021 Uint32 color=0xFFFFFF00|clr;
1022 drawGUIBox(x,y,width,height,renderer,color);
1023 }
1024
1025 //Rectangle used for drawing.
1026 SDL_Rect r{0,0,0,0};
1027
1028 //Get the text and make sure it isn't empty.
1029 const char* lp=caption.c_str();
1030 if(lp!=NULL && lp[0]){
1031 if(!cacheTex) {
1032 //Draw the text.
1033 cacheTex=textureFromText(renderer,*fontText,lp,objThemes.getTextColor(true));
1034 }
1035
1036 if(draw){
1037 //Only draw the carrot and highlight when focus.
1038 if(state==2){
1039 //Place the highlighted area.
1040 r.x=x+4;
1041 r.y=y+3;
1042 r.h=height-6;
1043
1044 if(highlightStart<highlightEnd){
1045 r.x+=highlightStartX;
1046 r.w=highlightEndX-highlightStartX;
1047 }else{
1048 r.x+=highlightEndX;
1049 r.w=highlightStartX-highlightEndX;
1050 }
1051
1052 //Draw the area.
1053 //SDL_FillRect(screen,&r,SDL_MapRGB(screen->format,128,128,128));
1054 SDL_SetRenderDrawColor(&renderer, 128,128,128,255);
1055 SDL_RenderFillRect(&renderer, &r);
1056
1057 //Ticking carrot.
1058 if(tick<16){
1059 //Show carrot: 15->0.
1060 r.x=x+highlightEndX+2;
1061 r.y=y+3;
1062 r.h=height-6;
1063 r.w=2;
1064 //SDL_FillRect(screen,&r,SDL_MapRGB(screen->format,0,0,0));
1065 SDL_SetRenderDrawColor(&renderer,0,0,0,255);
1066 SDL_RenderFillRect(&renderer, &r);
1067
1068 //Reset: 32 or count down.
1069 if(tick<=0)
1070 tick=32;
1071 else
1072 tick--;
1073 }else{
1074 //Hide carrot: 32->16.
1075 tick--;
1076 }
1077 }
1078
1079 //Calculate the location, center it vertically.
1080 SDL_Rect dstRect=rectFromTexture(*cacheTex);
1081 dstRect.x=x+4;
1082 dstRect.y=y+(height-dstRect.h)/2;
1083 dstRect.w=std::min(width-2, dstRect.w);
1084 //Draw the text.
1085 const SDL_Rect srcRect={0,0,width-2,25};
1086 SDL_RenderCopy(&renderer, cacheTex.get(), &srcRect, &dstRect);
1087 }
1088 }else{
1089 //Only draw the carrot when focus.
1090 if(state==2&&draw){
1091 //Ticking carrot.
1092 if (tick<16){
1093 //Show carrot: 15->0.
1094 r.x = x + 4;
1095 r.y = y + 4;
1096 r.w = 2;
1097 r.h = height - 8;
1098 //SDL_FillRect(screen,&r,SDL_MapRGB(screen->format,0,0,0));
1099 SDL_SetRenderDrawColor(&renderer, 0, 0, 0, 255);
1100 SDL_RenderFillRect(&renderer, &r);
1101
1102 //Reset: 32 or count down.
1103 if (tick <= 0)
1104 tick = 32;
1105 else
1106 tick--;
1107 } else{
1108 //Hide carrot: 32->16.
1109 tick--;
1110 }
1111 }
1112 }
1113 }
1114
1115 //////////////GUIFrame///////////////////////////////////////////////////////////////////
1116
handleEvents(SDL_Renderer & renderer,int x,int y,bool enabled,bool visible,bool processed)1117 bool GUIFrame::handleEvents(SDL_Renderer& renderer,int x,int y,bool enabled,bool visible,bool processed){
1118 //Boolean if the event is processed.
1119 bool b=processed;
1120
1121 //The widget is only enabled when its parent are enabled.
1122 enabled=enabled && this->enabled;
1123 //The widget is only enabled when its parent are enabled.
1124 visible=visible && this->visible;
1125
1126 //Get the absolute position.
1127 x+=left;
1128 y+=top;
1129
1130 //Also let the children handle their events.
1131 for(unsigned int i=0;i<childControls.size();i++){
1132 bool b1=childControls[i]->handleEvents(renderer,x,y,enabled,visible,b);
1133
1134 //The event is processed when either our or the childs is true (or both).
1135 b=b||b1;
1136 }
1137 return b;
1138 }
1139
render(SDL_Renderer & renderer,int x,int y,bool draw)1140 void GUIFrame::render(SDL_Renderer& renderer, int x,int y,bool draw){
1141 //There's no need drawing this widget when it's invisible.
1142 if(!visible)
1143 return;
1144
1145 //Get the absolute x and y location.
1146 x+=left;
1147 y+=top;
1148
1149 //Check if the enabled state changed or the caption, if so we need to clear the (old) cache.
1150 if(enabled!=cachedEnabled || caption.compare(cachedCaption)!=0 || width<=0){
1151 //Free the cache.
1152 cacheTex.reset(nullptr);
1153
1154 //And cache the new values.
1155 cachedEnabled=enabled;
1156 cachedCaption=caption;
1157
1158 //Finally resize the widget.
1159 if(autoWidth)
1160 width=-1;
1161 }
1162
1163 //Draw fill and borders.
1164 if(draw){
1165 Uint32 color=0xDDDDDDFF;
1166 drawGUIBox(x,y,width,height,renderer,color);
1167 }
1168
1169 //Get the title text and make sure it isn't empty.
1170 const char* lp=caption.c_str();
1171 if(lp!=NULL && lp[0]){
1172 //Update cache if needed.
1173 if(!cacheTex) {
1174 cacheTex = textureFromText(renderer, *fontGUI, lp, objThemes.getTextColor(true));
1175 }
1176 //Draw the text.
1177 if(draw) {
1178 applyTexture(x+(width-textureWidth(*cacheTex))/2, y+6-GUI_FONT_RAISE, *cacheTex, renderer);
1179 }
1180 }
1181 //We now need to draw all the children.
1182 for(unsigned int i=0;i<childControls.size();i++){
1183 childControls[i]->render(renderer,x,y,draw);
1184 }
1185 }
1186
1187 //////////////GUIImage///////////////////////////////////////////////////////////////////
1188
~GUIImage()1189 GUIImage::~GUIImage(){
1190 }
1191
handleEvents(SDL_Renderer &,int,int,bool,bool,bool processed)1192 bool GUIImage::handleEvents(SDL_Renderer&,int ,int ,bool ,bool ,bool processed){
1193 return processed;
1194 }
1195
fitToImage()1196 void GUIImage::fitToImage(){
1197 const SDL_Rect imageSize = rectFromTexture(*image);
1198
1199 //Increase or decrease the width and height to fully show the image.
1200 if(clip.w!=0) {
1201 width=clip.w;
1202 } else {
1203 width=imageSize.w;
1204 }
1205 if(clip.h!=0) {
1206 height=clip.h;
1207 } else {
1208 height=imageSize.h;
1209 }
1210 }
1211
render(SDL_Renderer & renderer,int x,int y,bool draw)1212 void GUIImage::render(SDL_Renderer& renderer, int x,int y,bool draw){
1213 //There's no need drawing the widget when it's invisible.
1214 //Also make sure the image isn't null.
1215 if(!visible || !image)
1216 return;
1217
1218 //Get the absolute x and y location.
1219 x+=left;
1220 y+=top;
1221
1222 //Create a clip rectangle.
1223 SDL_Rect r=clip;
1224 //The width and height are capped by the GUIImage itself.
1225 if(r.w>width || r.w==0) {
1226 r.w=width;
1227 }
1228 if(r.h>height || r.h==0) {
1229 r.h=height;
1230 }
1231
1232 const SDL_Rect dstRect={x,y,r.w,r.h};
1233 SDL_RenderCopy(&renderer, image.get(), &r, &dstRect);
1234 }
1235