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