Page MenuHomePhabricator (Chris)

No OneTemporary

Authored By
Unknown
Size
67 KB
Referenced Files
None
Subscribers
None
diff --git a/src/GUIObject.cpp b/src/GUIObject.cpp
index 6910405..8a39258 100644
--- a/src/GUIObject.cpp
+++ b/src/GUIObject.cpp
@@ -1,1258 +1,1280 @@
/*
* Copyright (C) 2011-2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Functions.h"
#include "UTF8Functions.h"
#include "GUIObject.h"
#include "ThemeManager.h"
#include "InputManager.h"
#include "GUIListBox.h"
#include "GUISlider.h"
#include <algorithm>
#include <iostream>
#include <list>
#include <SDL_ttf_fontfallback.h>
#include "Render.h"
using namespace std;
//Set the GUIObjectRoot to NULL.
GUIObject* GUIObjectRoot=NULL;
//Initialise the event queue.
list<GUIEvent> GUIEventQueue;
//A boolean variable used to skip next mouse up event for GUI (temporary workaround).
bool GUISkipNextMouseUpEvent = false;
+GUIObject *GUIObjectWhichWillBringToFront = NULL;
+
void GUIObjectHandleEvents(ImageManager& imageManager, SDL_Renderer& renderer, bool kill){
//Check if we need to reset the skip variable.
if (event.type == SDL_MOUSEBUTTONDOWN) {
GUISkipNextMouseUpEvent = false;
}
+ //Reset temp variable.
+ GUIObjectWhichWillBringToFront = NULL;
+
//Check if we need to skip event.
if (event.type == SDL_MOUSEBUTTONUP && GUISkipNextMouseUpEvent) {
GUISkipNextMouseUpEvent = false;
} else {
//Make sure that GUIObjectRoot isn't null.
if (GUIObjectRoot)
GUIObjectRoot->handleEvents(renderer);
}
+
+ //Reset temp variable again.
+ GUIObjectWhichWillBringToFront = NULL;
//Check for SDL_QUIT.
if(event.type==SDL_QUIT && kill){
//We get a quit event so enter the exit state.
setNextState(STATE_EXIT);
delete GUIObjectRoot;
GUIObjectRoot=NULL;
return;
}
//Keep calling events until there are none left.
while(!GUIEventQueue.empty()){
//Get one event and remove it from the queue.
GUIEvent e=GUIEventQueue.front();
GUIEventQueue.pop_front();
//If an eventCallback exist call it.
if(e.eventCallback){
e.eventCallback->GUIEventCallback_OnEvent(imageManager,renderer,e.name,e.obj,e.eventType);
}
}
//We empty the event queue just to be sure.
GUIEventQueue.clear();
}
GUIObject::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) :
left(left), top(top), width(width), height(height),
gravity(gravity), value(value),
enabled(enabled), visible(visible),
eventCallback(NULL), state(0),
cachedEnabled(enabled), gravityX(0),
gravityLeft(0), gravityTop(0), gravityRight(0), gravityBottom(0)
{
//Make sure that caption isn't NULL before setting it.
if (caption){
GUIObject::caption = caption;
//And set the cached caption.
cachedCaption = caption;
}
if (width <= 0)
autoWidth = true;
else
autoWidth = false;
inDialog = false;
//Load the gui images.
bmGuiTex = imageManager.loadTexture(getDataPath() + "gfx/gui.png", renderer);
}
GUIObject::~GUIObject(){
//We need to delete every child we have.
for(unsigned int i=0;i<childControls.size();i++){
delete childControls[i];
}
//Deleted the childs now empty the childControls vector.
childControls.clear();
}
void GUIObject::addChild(GUIObject* obj){
//Add widget add a child
childControls.push_back(obj);
//Copy inDialog boolean from parent.
obj->inDialog = inDialog;
}
GUIObject* GUIObject::getChild(const std::string& name){
//Look for a child with the name.
for (unsigned int i = 0; i<childControls.size(); i++)
if (childControls[i]->name == name)
return childControls[i];
//Not found so return NULL.
return NULL;
}
bool GUIObject::handleEvents(SDL_Renderer& renderer,int x,int y,bool enabled,bool visible,bool processed){
//Boolean if the event is processed.
bool b=processed;
//The GUIObject is only enabled when its parent are enabled.
enabled=enabled && this->enabled;
//The GUIObject is only enabled when its parent are enabled.
visible=visible && this->visible;
//Get the absolute position.
x+=left-gravityX;
y+=top;
//Also let the children handle their events.
for (int i = childControls.size() - 1; i >= 0; i--) {
bool b1 = childControls[i]->handleEvents(renderer, x, y, enabled, visible, b);
//The event is processed when either our or the childs is true (or both).
b=b||b1;
}
+ //Check if we should bring some control to front.
+ if (GUIObjectWhichWillBringToFront) {
+ for (int i = childControls.size() - 1; i >= 0; i--) {
+ if (childControls[i] == GUIObjectWhichWillBringToFront) {
+ if (i < (int)childControls.size() - 1) {
+ childControls.erase(childControls.begin() + i);
+ childControls.push_back(GUIObjectWhichWillBringToFront);
+ }
+ GUIObjectWhichWillBringToFront = NULL;
+ break;
+ }
+ }
+ }
+
return b;
}
void GUIObject::render(SDL_Renderer& renderer, int x,int y,bool draw){
//There's no need drawing the GUIObject when it's invisible.
if(!visible)
return;
//Get the absolute x and y location.
x+=left;
y+=top;
//We now need to draw all the children of the GUIObject.
for(unsigned int i=0;i<childControls.size();i++){
childControls[i]->render(renderer,x,y,draw);
}
}
void GUIObject::onResize() {
}
void GUIObject::refreshCache(bool enabled) {
//Check if the enabled state changed or the caption, if so we need to clear the (old) cache.
if(enabled!=cachedEnabled || caption.compare(cachedCaption)!=0 || width<=0){
//TODO: Only change alpha if only enabled changes.
//Free the cache.
cacheTex.reset(nullptr);
//And cache the new values.
cachedEnabled=enabled;
cachedCaption=caption;
//Finally resize the widget
if(autoWidth)
width=-1;
}
}
int GUIObject::getSelectedControl() {
for (int i = 0; i < (int)childControls.size(); i++) {
GUIObject *obj = childControls[i];
if (obj && obj->visible && obj->enabled && obj->state) {
if (dynamic_cast<GUITextBox*>(obj) && obj->state == 2) {
return i;
} else if (dynamic_cast<GUIButton*>(obj) || dynamic_cast<GUICheckBox*>(obj)
|| dynamic_cast<GUISingleLineListBox*>(obj)
|| dynamic_cast<GUISlider*>(obj)
)
{
return i;
}
}
}
return -1;
}
void GUIObject::setSelectedControl(int index) {
for (int i = 0; i < (int)childControls.size(); i++) {
GUIObject *obj = childControls[i];
if (obj && obj->visible && obj->enabled) {
if (dynamic_cast<GUIButton*>(obj) || dynamic_cast<GUICheckBox*>(obj)) {
//It's a button.
obj->state = (i == index) ? 1 : 0;
} else if (dynamic_cast<GUITextBox*>(obj)) {
//It's a text box (or a spin box).
if(i == index) {
obj->state = 2;
} else {
dynamic_cast<GUITextBox*>(obj)->blur();
}
} else if (dynamic_cast<GUISingleLineListBox*>(obj)) {
//It's a single line list box.
obj->state = (i == index) ? 0x100 : 0;
} else if (dynamic_cast<GUISlider*>(obj)) {
//It's a slider.
obj->state = (i == index) ? 0x10000 : 0;
}
}
}
}
int GUIObject::selectNextControl(int direction, int selected) {
//Get the index of currently selected control.
if (selected == 0x80000000) {
selected = getSelectedControl();
}
//Find the next control.
for (int i = 0; i < (int)childControls.size(); i++) {
if (selected < 0) {
selected = 0;
} else {
selected += direction;
if (selected >= (int)childControls.size()) {
selected -= childControls.size();
} else if (selected < 0) {
selected += childControls.size();
}
}
GUIObject *obj = childControls[selected];
if (obj && obj->visible && obj->enabled) {
if (dynamic_cast<GUIButton*>(obj) || dynamic_cast<GUICheckBox*>(obj)
|| dynamic_cast<GUITextBox*>(obj)
|| dynamic_cast<GUISingleLineListBox*>(obj)
|| dynamic_cast<GUISlider*>(obj)
)
{
setSelectedControl(selected);
return selected;
}
}
}
return -1;
}
bool GUIObject::handleKeyboardNavigationEvents(ImageManager& imageManager, SDL_Renderer& renderer, int keyboardNavigationMode) {
if (keyboardNavigationMode == 0) return false;
//Check operation on focused control. These have higher priority.
if (isKeyboardOnly) {
//Check enter key.
if ((keyboardNavigationMode & ReturnControls) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_SELECT)) {
int index = getSelectedControl();
if (index >= 0) {
GUIObject *obj = childControls[index];
if (dynamic_cast<GUIButton*>(obj)) {
//It's a button.
if (obj->eventCallback) {
obj->eventCallback->GUIEventCallback_OnEvent(imageManager, renderer, obj->name, obj, GUIEventClick);
}
return true;
}
if (dynamic_cast<GUICheckBox*>(obj)) {
//It's a check box.
obj->value = obj->value ? 0 : 1;
if (obj->eventCallback) {
obj->eventCallback->GUIEventCallback_OnEvent(imageManager, renderer, obj->name, obj, GUIEventClick);
}
return true;
}
}
}
//Check left/right key.
if ((keyboardNavigationMode & LeftRightControls) != 0 && (inputMgr.isKeyDownEvent(INPUTMGR_LEFT) || inputMgr.isKeyDownEvent(INPUTMGR_RIGHT))) {
int index = getSelectedControl();
if (index >= 0) {
GUIObject *obj = childControls[index];
auto sllb = dynamic_cast<GUISingleLineListBox*>(obj);
if (sllb) {
//It's a single line list box.
int newValue = sllb->value + (inputMgr.isKeyDownEvent(INPUTMGR_RIGHT) ? 1 : -1);
if (newValue >= (int)sllb->item.size()) {
newValue -= sllb->item.size();
} else if (newValue < 0) {
newValue += sllb->item.size();
}
if (sllb->value != newValue) {
sllb->value = newValue;
if (obj->eventCallback) {
obj->eventCallback->GUIEventCallback_OnEvent(imageManager, renderer, obj->name, obj, GUIEventClick);
}
}
return true;
}
auto slider = dynamic_cast<GUISlider*>(obj);
if (slider) {
//It's a slider.
int newValue = slider->value + (inputMgr.isKeyDownEvent(INPUTMGR_RIGHT) ? slider->largeChange : -slider->largeChange);
newValue = clamp(newValue, slider->minValue, slider->maxValue);
if (slider->value != newValue) {
slider->value = newValue;
if (obj->eventCallback) {
obj->eventCallback->GUIEventCallback_OnEvent(imageManager, renderer, obj->name, obj, GUIEventChange);
}
}
return true;
}
}
}
}
//Check if we need to exclude printable characters
bool excludePrintable = false;
{
int index = getSelectedControl();
if (index >= 0) {
GUIObject *obj = childControls[index];
if (dynamic_cast<GUITextBox*>(obj)) {
excludePrintable = true;
}
}
}
//Check focus movement
int m = SDL_GetModState();
if (((keyboardNavigationMode & LeftRightFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_RIGHT, excludePrintable))
|| ((keyboardNavigationMode & UpDownFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_DOWN, excludePrintable))
|| ((keyboardNavigationMode & TabFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_TAB, excludePrintable) && (m & KMOD_SHIFT) == 0)
)
{
isKeyboardOnly = true;
selectNextControl(1);
return true;
} else if (((keyboardNavigationMode & LeftRightFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_LEFT, excludePrintable))
|| ((keyboardNavigationMode & UpDownFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_UP, excludePrintable))
|| ((keyboardNavigationMode & TabFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_TAB, excludePrintable) && (m & KMOD_SHIFT) != 0)
)
{
isKeyboardOnly = true;
selectNextControl(-1);
return true;
}
return false;
}
//////////////GUIButton///////////////////////////////////////////////////////////////////
bool GUIButton::handleEvents(SDL_Renderer& renderer,int x,int y,bool enabled,bool visible,bool processed){
//Boolean if the event is processed.
bool b=processed;
//The widget is only enabled when its parent are enabled.
enabled=enabled && this->enabled;
//The widget is only enabled when its parent are enabled.
visible=visible && this->visible;
//Get the absolute position.
x+=left-gravityX;
y+=top;
//We don't update button state under keyboard only mode.
if (!isKeyboardOnly) {
//Set state to 0.
state = 0;
//Only check for events when the object is both enabled and visible and the event hasn't been already processed.
if (enabled && visible && !b) {
//The mouse location (x=i, y=j) and the mouse button (k).
int i, j, k;
k = SDL_GetMouseState(&i, &j);
//Check if the mouse is inside the widget.
if (i >= x && i < x + width && j >= y && j < y + height) {
//We have hover so set state to one.
state = 1;
//Check for a mouse button press.
if (k&SDL_BUTTON(1))
state = 2;
//Check if there's a mouse press.
if (event.type == SDL_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_LEFT) {
//If event callback is configured then add an event to the queue.
if (eventCallback) {
GUIEvent e = { eventCallback, name, this, GUIEventClick };
GUIEventQueue.push_back(e);
}
}
//Event has been processed as long as this is a mouse event and the mouse is inside the widget.
if ((event.type == SDL_MOUSEMOTION && event.motion.state == 0)
|| event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEWHEEL)
{
b = true;
}
}
}
}
return b;
}
void GUIButton::render(SDL_Renderer& renderer, int x,int y,bool draw){
//There's no need drawing the widget when it's invisible.
if(!visible)
return;
//Get the absolute x and y location.
x+=left;
y+=top;
refreshCache(enabled);
//Get the text and make sure it isn't empty.
const char* lp=caption.c_str();
if(lp!=NULL && lp[0]){
//Update cache if needed.
if(!cacheTex){
SDL_Color color = objThemes.getTextColor(inDialog);
if(!smallFont) {
cacheTex = textureFromText(renderer, *fontGUI, lp, color);
} else {
cacheTex = textureFromText(renderer, *fontGUISmall, lp, color);
}
//Make the widget transparent if it's disabled.
if(!enabled) {
SDL_SetTextureAlphaMod(cacheTex.get(), 128);
}
//Calculate proper size for the widget.
if(width<=0){
width=textureWidth(*cacheTex)+50;
if(gravity==GUIGravityCenter){
gravityX=int(width/2);
}else if(gravity==GUIGravityRight){
gravityX=width;
}else{
gravityX=0;
}
}
}
if(draw){
//Center the text both vertically as horizontally.
const SDL_Rect size = rectFromTexture(*cacheTex);
const int drawX=x-gravityX+(width-size.w)/2;
const int drawY=y+(height-size.h)/2-GUI_FONT_RAISE;
//Check if the arrows don't fall of.
if(size.w+32<=width){
if(state==1){
if(inDialog){
applyTexture(x-gravityX+(width-size.w)/2+4+size.w+5,y+2,*arrowLeft2,renderer);
applyTexture(x-gravityX+(width-size.w)/2-25,y+2,*arrowRight2,renderer);
}else{
applyTexture(x-gravityX+(width-size.w)/2+4+size.w+5,y+2,*arrowLeft1,renderer);
applyTexture(x-gravityX+(width-size.w)/2-25,y+2,*arrowRight1,renderer);
}
}else if(state==2){
if(inDialog){
applyTexture(x-gravityX+(width-size.w)/2+4+size.w,y+2,*arrowLeft2,renderer);
applyTexture(x-gravityX+(width-size.w)/2-20,y+2,*arrowRight2,renderer);
}else{
applyTexture(x-gravityX+(width-size.w)/2+4+size.w,y+2,*arrowLeft1,renderer);
applyTexture(x-gravityX+(width-size.w)/2-20,y+2,arrowRight1,renderer);
}
}
}
//Draw the text.
applyTexture(drawX, drawY, *cacheTex, renderer);
}
}
}
//////////////GUICheckBox///////////////////////////////////////////////////////////////////
bool GUICheckBox::handleEvents(SDL_Renderer&,int x,int y,bool enabled,bool visible,bool processed){
//Boolean if the event is processed.
bool b=processed;
//The widget is only enabled when its parent are enabled.
enabled=enabled && this->enabled;
//The widget is only enabled when its parent are enabled.
visible=visible && this->visible;
//Get the absolute position.
x+=left-gravityX;
y+=top;
//We don't update state under keyboard only mode.
if (!isKeyboardOnly) {
//Set state to 0.
state = 0;
//Only check for events when the object is both enabled and visible and the event hasn't been already processed.
if (enabled && visible && !b) {
//The mouse location (x=i, y=j) and the mouse button (k).
int i, j, k;
k = SDL_GetMouseState(&i, &j);
//Check if the mouse is inside the widget.
if (i >= x && i < x + width && j >= y && j < y + height){
//We have hover so set state to one.
state = 1;
//Check for a mouse button press.
if (k&SDL_BUTTON(1))
state = 2;
//Check if there's a mouse press and the event hasn't been already processed.
if (event.type == SDL_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_LEFT){
//It's a checkbox so toggle the value.
value = value ? 0 : 1;
//If event callback is configured then add an event to the queue.
if (eventCallback){
GUIEvent e = { eventCallback, name, this, GUIEventClick };
GUIEventQueue.push_back(e);
}
}
//Event has been processed as long as this is a mouse event and the mouse is inside the widget.
if ((event.type == SDL_MOUSEMOTION && event.motion.state == 0)
|| event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEWHEEL)
{
b = true;
}
}
}
}
return b;
}
void GUICheckBox::render(SDL_Renderer& renderer, int x,int y,bool draw){
//There's no need drawing the widget when it's invisible.
if(!visible)
return;
//Get the absolute x and y location.
x+=left;
y+=top;
refreshCache(enabled);
//Draw the highlight in keyboard only mode.
if (isKeyboardOnly && state && draw) {
drawGUIBox(x, y, width, height, renderer, 0xFFFFFF40);
}
//Get the text.
const char* lp=caption.c_str();
//Make sure it isn't empty.
if(lp!=NULL && lp[0]){
//Update the cache if needed.
if(!cacheTex){
SDL_Color color = objThemes.getTextColor(inDialog);
cacheTex=textureFromText(renderer,*fontText,lp,color);
}
if(draw){
//Calculate the location, center it vertically.
const int drawX=x;
const int drawY=y+(height - textureHeight(*cacheTex))/2;
//Draw the text
applyTexture(drawX, drawY, *cacheTex, renderer);
}
}
if(draw){
//Draw the check (or not).
//value*16 determines where in the gui textures we draw from.
//if(value==1||value==2)
// r1.x=value*16;
const SDL_Rect srcRect={value*16,0,16,16};
const SDL_Rect dstRect={x+width-20, y+(height-16)/2, 16, 16};
//Get the right image depending on the state of the object.
SDL_RenderCopy(&renderer, bmGuiTex.get(), &srcRect, &dstRect);
}
}
//////////////GUILabel///////////////////////////////////////////////////////////////////
bool GUILabel::handleEvents(SDL_Renderer&,int ,int ,bool ,bool ,bool processed){
return processed;
}
void GUILabel::render(SDL_Renderer& renderer, int x,int y,bool draw){
//There's no need drawing the widget when it's invisible.
if(!visible)
return;
//Check if the enabled state changed or the caption, if so we need to clear the (old) cache.
refreshCache(enabled);
//Get the absolute x and y location.
x+=left;
y+=top;
//Rectangle the size of the widget.
SDL_Rect r;
r.x=x;
r.y=y;
r.w=width;
r.h=height;
//Get the caption and make sure it isn't empty.
const char* lp=caption.c_str();
if(lp!=NULL && lp[0]){
//Update cache if needed.
if(!cacheTex){
SDL_Color color = objThemes.getTextColor(inDialog);
cacheTex=textureFromText(renderer, *fontText, lp, color);
if(width<=0)
width=textureWidth(*cacheTex);
}
//Align the text properly and draw it.
if(draw){
const SDL_Rect size = rectFromTexture(*cacheTex);
if(gravity==GUIGravityCenter)
gravityX=(width-size.w)/2;
else if(gravity==GUIGravityRight)
gravityX=width-size.w;
else
gravityX=0;
r.y=y+(height - size.h)/2;
r.x+=gravityX;
applyTexture(r.x, r.y, cacheTex, renderer);
}
}
}
//////////////GUITextBox///////////////////////////////////////////////////////////////////
void GUITextBox::backspaceChar(){
//We need to remove a character so first make sure that there is text.
if(caption.length()>0){
if(highlightStart==highlightEnd&&highlightStart>0){
int advance = 0;
// this is proper UTF-8 support
int ch = utf8ReadBackward(caption.c_str(), highlightStart); // we obtain new highlightStart from this
if (ch > 0) TTF_GlyphMetrics(fontText, ch, NULL, NULL, NULL, NULL, &advance);
highlightEndX = highlightStartX = highlightEndX - advance;
caption.erase(highlightStart, highlightEnd - highlightStart);
highlightEnd = highlightStart;
} else if (highlightStart<highlightEnd){
caption.erase(highlightStart,highlightEnd-highlightStart);
highlightEnd=highlightStart;
highlightEndX=highlightStartX;
}else{
caption.erase(highlightEnd,highlightStart-highlightEnd);
highlightStart=highlightEnd;
highlightStartX=highlightEndX;
}
//If there is an event callback then call it.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
}
}
void GUITextBox::deleteChar(){
//We need to remove a character so first make sure that there is text.
if(caption.length()>0){
if(highlightStart==highlightEnd){
// this is proper utf8 support
int i = highlightEnd;
utf8ReadForward(caption.c_str(), i);
if (i > highlightEnd) caption.erase(highlightEnd, i - highlightEnd);
highlightStart=highlightEnd;
highlightStartX=highlightEndX;
}else if(highlightStart<highlightEnd){
caption.erase(highlightStart,highlightEnd-highlightStart);
highlightEnd=highlightStart;
highlightEndX=highlightStartX;
}else{
caption.erase(highlightEnd,highlightStart-highlightEnd);
highlightStart=highlightEnd;
highlightStartX=highlightEndX;
}
//If there is an event callback then call it.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
}
}
void GUITextBox::moveCarrotLeft(){
if(highlightEnd>0){
int advance = 0;
// this is proper UTF-8 support
int ch = utf8ReadBackward(caption.c_str(), highlightEnd); // we obtain new highlightEnd from this
if (ch > 0) TTF_GlyphMetrics(fontText, ch, NULL, NULL, NULL, NULL, &advance);
if(SDL_GetModState() & KMOD_SHIFT){
highlightEndX-=advance;
}else{
highlightStart=highlightEnd;
highlightStartX=highlightEndX=highlightEndX-advance;
}
}else{
if((SDL_GetModState() & KMOD_SHIFT)==0){
highlightStart=highlightEnd;
highlightStartX=highlightEndX;
}
}
tick=15;
}
void GUITextBox::moveCarrotRight(){
if(highlightEnd<caption.length()){
int advance = 0;
// this is proper UTF-8 support
int ch = utf8ReadForward(caption.c_str(), highlightEnd); // we obtain new highlightEnd from this
if (ch > 0) TTF_GlyphMetrics(fontText, ch, NULL, NULL, NULL, NULL, &advance);
if(SDL_GetModState() & KMOD_SHIFT){
highlightEndX+=advance;
}else{
highlightStartX=highlightEndX=highlightEndX+advance;
highlightStart=highlightEnd;
}
}else{
if((SDL_GetModState() & KMOD_SHIFT)==0){
highlightStart=highlightEnd;
highlightStartX=highlightEndX;
}
}
tick=15;
}
void GUITextBox::updateText(const std::string& text) {
caption = text;
updateSelection(0, 0);
}
void GUITextBox::updateSelection(int start, int end) {
start = clamp(start, 0, caption.size());
end = clamp(end, 0, caption.size());
highlightStart = start;
highlightStartX = 0;
highlightEnd = end;
highlightEndX = 0;
if (start > 0) {
TTF_SizeUTF8(fontText, caption.substr(0, start).c_str(), &highlightStartX, NULL);
}
if (end > 0) {
TTF_SizeUTF8(fontText, caption.substr(0, end).c_str(), &highlightEndX, NULL);
}
}
void GUITextBox::inputText(const char* s) {
int m = strlen(s);
if (m > 0){
if (highlightStart == highlightEnd) {
caption.insert((size_t)highlightStart, s);
highlightStart += m;
highlightEnd = highlightStart;
} else if (highlightStart < highlightEnd) {
caption.erase(highlightStart, highlightEnd - highlightStart);
caption.insert((size_t)highlightStart, s);
highlightStart += m;
highlightEnd = highlightStart;
highlightEndX = highlightStartX;
} else {
caption.erase(highlightEnd, highlightStart - highlightEnd);
caption.insert((size_t)highlightEnd, s);
highlightEnd += m;
highlightStart = highlightEnd;
highlightStartX = highlightEndX;
}
int advance = 0;
for (int i = 0;;) {
int a = 0;
int ch = utf8ReadForward(s, i);
if (ch <= 0) break;
TTF_GlyphMetrics(fontText, ch, NULL, NULL, NULL, NULL, &a);
advance += a;
}
highlightStartX = highlightEndX = highlightStartX + advance;
//If there is an event callback then call it.
if (eventCallback){
GUIEvent e = { eventCallback, name, this, GUIEventChange };
GUIEventQueue.push_back(e);
}
}
}
void GUITextBox::blur(){
state = 0;
highlightStart=highlightStartX=0;
highlightEnd=highlightEndX=0;
}
bool GUITextBox::handleEvents(SDL_Renderer&,int x,int y,bool enabled,bool visible,bool processed){
//Boolean if the event is processed.
bool b=processed;
//The widget is only enabled when its parent are enabled.
enabled=enabled && this->enabled;
//The widget is only enabled when its parent are enabled.
visible=visible && this->visible;
//Get the absolute position.
x+=left-gravityX;
y+=top;
//NOTE: We don't reset the state to have a "focus" effect.
//Only check for events when the object is both enabled and visible.
if(enabled&&visible){
//Check if there's a key press and the event hasn't been already processed.
if(state==2 && event.type==SDL_KEYDOWN && !b){
//Get the keycode.
SDL_Keycode key=event.key.keysym.sym;
if ((event.key.keysym.mod & KMOD_CTRL) == 0) {
//Check if the key is supported.
if (event.key.keysym.sym == SDLK_BACKSPACE){
backspaceChar();
} else if (event.key.keysym.sym == SDLK_DELETE){
deleteChar();
} else if (event.key.keysym.sym == SDLK_RIGHT){
moveCarrotRight();
} else if (event.key.keysym.sym == SDLK_LEFT){
moveCarrotLeft();
}
} else {
//Check hotkey.
if (event.key.keysym.sym == SDLK_a) {
//Select all.
highlightStart = 0;
highlightStartX = 0;
highlightEnd = caption.size();
highlightEndX = 0;
if (highlightEnd > 0) {
TTF_SizeUTF8(fontText, caption.c_str(), &highlightEndX, NULL);
}
} else if (event.key.keysym.sym == SDLK_x || event.key.keysym.sym == SDLK_c) {
//Cut or copy.
int start = highlightStart, end = highlightEnd;
if (start > end) std::swap(start, end);
if (start < end) {
SDL_SetClipboardText(caption.substr(start, end - start).c_str());
if (event.key.keysym.sym == SDLK_x) {
//Cut.
backspaceChar();
}
}
} else if (event.key.keysym.sym == SDLK_v) {
//Paste.
if (SDL_HasClipboardText()) {
char *s = SDL_GetClipboardText();
inputText(s);
SDL_free(s);
}
}
}
//The event has been processed.
b = true;
} else if (state == 2 && event.type == SDL_TEXTINPUT && !b){
inputText(event.text.text);
//The event has been processed.
b = true;
} else if (state == 2 && event.type == SDL_TEXTEDITING && !b){
// TODO: process SDL_TEXTEDITING event
}
//Only process mouse event when not in keyboard only mode
if (!isKeyboardOnly &&
(event.type == SDL_MOUSEMOTION || event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEWHEEL))
{
//The mouse location (x=i, y=j) and the mouse button (k).
int i, j, k;
k = SDL_GetMouseState(&i, &j);
//Check if the mouse is inside the widget and the event hasn't been processed.
if (i >= x && i < x + width && j >= y && j < y + height && !b) {
//We can only increase our state. (nothing->hover->focus).
if (state != 2){
state = 1;
}
//Also update the cursor type.
currentCursor = CURSOR_CARROT;
//Move carrot and highlightning according to mouse input.
int clickX = i - x - 2;
int finalPos = 0;
int finalX = 0;
if (cacheTex&&!caption.empty()){
finalPos = caption.length();
for (int i = 0;;){
int advance = 0;
// this is proper UTF-8 support
int i0 = i;
int ch = utf8ReadForward(caption.c_str(), i);
if (ch <= 0) break;
TTF_GlyphMetrics(fontText, ch, NULL, NULL, NULL, NULL, &advance);
finalX += advance;
if (clickX < finalX - advance / 2){
finalPos = i0;
finalX -= advance;
break;
}
}
}
if (event.type == SDL_MOUSEBUTTONUP && state == 2){
state = 2;
highlightEnd = finalPos;
highlightEndX = finalX;
} else if (event.type == SDL_MOUSEBUTTONDOWN){
state = 2;
highlightStart = highlightEnd = finalPos;
highlightStartX = highlightEndX = finalX;
} else if (event.type == SDL_MOUSEMOTION && (k&SDL_BUTTON(1)) && state == 2){
state = 2;
highlightEnd = finalPos;
highlightEndX = finalX;
}
//Event has been processed as long as this is a mouse event and the mouse is inside the widget.
b = true;
} else{
//The mouse is outside the TextBox.
//If we don't have focus but only hover we lose it.
if (state == 1){
state = 0;
}
//If it's a click event outside the textbox then we blur.
if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT){
blur();
}
}
}
}
return b;
}
void GUITextBox::render(SDL_Renderer& renderer, int x,int y,bool draw){
//There's no need drawing the widget when it's invisible.
if(!visible)
return;
//Get the absolute x and y location.
x+=left;
y+=top;
//Check if the enabled state changed or the caption, if so we need to clear the (old) cache.
refreshCache(enabled);
if(draw){
//Default background opacity
int clr=50;
//If hovering or focused make background more visible.
if(state==1)
clr=128;
else if (state==2)
clr=100;
//Draw the box.
Uint32 color=0xFFFFFF00|clr;
drawGUIBox(x,y,width,height,renderer,color);
}
//Rectangle used for drawing.
SDL_Rect r{0,0,0,0};
//Get the text and make sure it isn't empty.
const char* lp=caption.c_str();
if(lp!=NULL && lp[0]){
if(!cacheTex) {
//Draw the text.
cacheTex=textureFromText(renderer,*fontText,lp,objThemes.getTextColor(true));
}
if(draw){
//Only draw the carrot and highlight when focus.
if(state==2){
//Place the highlighted area.
r.x=x+4;
r.y=y+3;
r.h=height-6;
if(highlightStart<highlightEnd){
r.x+=highlightStartX;
r.w=highlightEndX-highlightStartX;
}else{
r.x+=highlightEndX;
r.w=highlightStartX-highlightEndX;
}
//Draw the area.
//SDL_FillRect(screen,&r,SDL_MapRGB(screen->format,128,128,128));
SDL_SetRenderDrawColor(&renderer, 128,128,128,255);
SDL_RenderFillRect(&renderer, &r);
//Ticking carrot.
if(tick<16){
//Show carrot: 15->0.
r.x=x+highlightEndX+2;
r.y=y+3;
r.h=height-6;
r.w=2;
//SDL_FillRect(screen,&r,SDL_MapRGB(screen->format,0,0,0));
SDL_SetRenderDrawColor(&renderer,0,0,0,255);
SDL_RenderFillRect(&renderer, &r);
//Reset: 32 or count down.
if(tick<=0)
tick=32;
else
tick--;
}else{
//Hide carrot: 32->16.
tick--;
}
}
//Calculate the location, center it vertically.
SDL_Rect dstRect=rectFromTexture(*cacheTex);
dstRect.x=x+4;
dstRect.y=y+(height-dstRect.h)/2;
dstRect.w=std::min(width-2, dstRect.w);
//Draw the text.
const SDL_Rect srcRect={0,0,width-2,25};
SDL_RenderCopy(&renderer, cacheTex.get(), &srcRect, &dstRect);
}
}else{
//Only draw the carrot when focus.
if(state==2&&draw){
//Ticking carrot.
if (tick<16){
//Show carrot: 15->0.
r.x = x + 4;
r.y = y + 4;
r.w = 2;
r.h = height - 8;
//SDL_FillRect(screen,&r,SDL_MapRGB(screen->format,0,0,0));
SDL_SetRenderDrawColor(&renderer, 0, 0, 0, 255);
SDL_RenderFillRect(&renderer, &r);
//Reset: 32 or count down.
if (tick <= 0)
tick = 32;
else
tick--;
} else{
//Hide carrot: 32->16.
tick--;
}
}
}
}
//////////////GUIFrame///////////////////////////////////////////////////////////////////
bool GUIFrame::handleEvents(SDL_Renderer& renderer,int x,int y,bool enabled,bool visible,bool processed){
//Boolean if the event is processed.
bool b=processed;
//The widget is only enabled when its parent are enabled.
enabled=enabled && this->enabled;
//The widget is only enabled when its parent are enabled.
visible=visible && this->visible;
//Get the absolute position.
x+=left;
y+=top;
//Also let the children handle their events.
for (int i = childControls.size() - 1; i >= 0; i--) {
bool b1 = childControls[i]->handleEvents(renderer, x, y, enabled, visible, b);
//The event is processed when either our or the childs is true (or both).
b=b||b1;
}
//If we are visible, the event is a mouse event, and the mouse is inside the widget, we mark this event as processed.
if (visible &&
((event.type == SDL_MOUSEMOTION && event.motion.state == 0)
|| event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEWHEEL))
{
//The mouse location (x=i, y=j) and the mouse button (k).
int i, j, k;
k = SDL_GetMouseState(&i, &j);
//Check if the mouse is inside the widget.
if (i >= x && i < x + width && j >= y && j < y + height) {
b = true;
}
}
return b;
}
void GUIFrame::render(SDL_Renderer& renderer, int x,int y,bool draw){
//There's no need drawing this widget when it's invisible.
if(!visible)
return;
//Get the absolute x and y location.
x+=left;
y+=top;
//Check if the enabled state changed or the caption, if so we need to clear the (old) cache.
if(enabled!=cachedEnabled || caption.compare(cachedCaption)!=0 || width<=0){
//Free the cache.
cacheTex.reset(nullptr);
//And cache the new values.
cachedEnabled=enabled;
cachedCaption=caption;
//Finally resize the widget.
if(autoWidth)
width=-1;
}
//Draw fill and borders.
if(draw){
Uint32 color=0xDDDDDDFF;
drawGUIBox(x,y,width,height,renderer,color);
}
//Get the title text and make sure it isn't empty.
const char* lp=caption.c_str();
if(lp!=NULL && lp[0]){
//Update cache if needed.
if(!cacheTex) {
cacheTex = textureFromText(renderer, *fontGUI, lp, objThemes.getTextColor(true));
}
//Draw the text.
if(draw) {
applyTexture(x+(width-textureWidth(*cacheTex))/2, y+6-GUI_FONT_RAISE, *cacheTex, renderer);
}
}
//We now need to draw all the children.
for(unsigned int i=0;i<childControls.size();i++){
childControls[i]->render(renderer,x,y,draw);
}
}
//////////////GUIImage///////////////////////////////////////////////////////////////////
GUIImage::~GUIImage(){
}
bool GUIImage::handleEvents(SDL_Renderer&,int ,int ,bool ,bool ,bool processed){
return processed;
}
void GUIImage::fitToImage(){
const SDL_Rect imageSize = rectFromTexture(*image);
//Increase or decrease the width and height to fully show the image.
if(clip.w!=0) {
width=clip.w;
} else {
width=imageSize.w;
}
if(clip.h!=0) {
height=clip.h;
} else {
height=imageSize.h;
}
}
void GUIImage::render(SDL_Renderer& renderer, int x,int y,bool draw){
//There's no need drawing the widget when it's invisible.
//Also make sure the image isn't null.
if(!visible || !image)
return;
//Get the absolute x and y location.
x+=left;
y+=top;
//Create a clip rectangle.
SDL_Rect r=clip;
//The width and height are capped by the GUIImage itself.
if(r.w>width || r.w==0) {
r.w=width;
}
if(r.h>height || r.h==0) {
r.h=height;
}
const SDL_Rect dstRect={x,y,r.w,r.h};
SDL_RenderCopy(&renderer, image.get(), &r, &dstRect);
}
diff --git a/src/GUIObject.h b/src/GUIObject.h
index fefbe8b..2d9eab0 100644
--- a/src/GUIObject.h
+++ b/src/GUIObject.h
@@ -1,409 +1,417 @@
/*
* Copyright (C) 2011-2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef GUIOBJECT_H
#define GUIOBJECT_H
#include "FileManager.h"
#include "ImageManager.h"
#include "Render.h"
#include <string>
#include <vector>
#include <list>
//Widget gravity properties
enum GUIGravityMode {
GUIGravityLeft,
GUIGravityCenter,
GUIGravityRight,
};
//The event id's.
enum GUIEventId {
//A click event used for e.g. buttons.
GUIEventClick,
//A change event used for e.g. textboxes.
GUIEventChange,
};
//A boolean variable used to skip next mouse up event for GUI (temporary workaround).
//This is used in level editor and addon screen which will open a new window when user clicks an item in a list box.
//However, the click event in the newly created window may triggered since they can receive a mouse up event.
//NOTE: This will be reset to false when GUIObjectHandleEvents() receives a mouse down event.
//This is OK since it may be set to true only after this point.
extern bool GUISkipNextMouseUpEvent;
+class GUIObject;
+
+//A pointer (WARNING: Weak reference!) points to a GUIObject
+//which will be bring to front in the next frame.
+//This is used in GUIWindow which can dynamically change its z-order.
+//NOTE: Currently this only works if it's a child widget of a GUIObject (not its subclasses).
+extern GUIObject *GUIObjectWhichWillBringToFront;
+
struct SDL_Renderer;
class GUIObject;
//Class that is used as event callback.
class GUIEventCallback{
public:
//This method is called when an event is fired.
//name: The name of the event.
//obj: Pointer to the GUIObject which caused this event.
//eventType: The type of event as defined above.
virtual void GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name,GUIObject* obj,int eventType)=0;
};
//Class containing the
class GUIObject{
public:
//The relative x location of the GUIObject.
int left;
//The relative y location of the GUIObject.
int top;
//The width of the GUIObject.
int width;
//The height of the GUIObject.
int height;
//The value of the GUIObject.
//It depends on the type of GUIObject what it means.
int value;
//The name of the GUIObject.
std::string name;
//The caption of the GUIObject.
//It depends on the type of GUIObject what it is.
std::string caption;
//Boolean if the GUIObject is enabled.
bool enabled;
//Boolean if the GUIObject is visible.
bool visible;
//Vector containing the children of the GUIObject.
std::vector<GUIObject*> childControls;
//Event callback used to invoke events.
GUIEventCallback* eventCallback;
int gravityX;
//Widget's gravity to centering
char gravity;
bool autoWidth;
//Widget's gravity for positioning.
//NOTE: Currently this is only used in GUIWindow::resize().
char gravityLeft, gravityTop, gravityRight, gravityBottom;
//Is the parent widget a dialog?
bool inDialog;
//The state of the GUIObject.
//It depends on the type of GUIObject where it's used for.
int state;
protected:
//Texture containing different gui images.
SharedTexture bmGuiTex;
//Surface that can be used to cache rendered text.
TexturePtr cacheTex;
//String containing the old caption to detect if it changed.
std::string cachedCaption;
//Boolean containing the previous enabled state.
bool cachedEnabled;
//Check if the caption or status has changed, or if the width is <0 and
//recreate the cached texture if so.
void refreshCache(bool enabled);
public:
//Constructor.
//left: The relative x location of the GUIObject.
//top: The relative y location of the GUIObject.
//witdh: The width of the GUIObject.
//height: The height of the GUIObject.
//caption: The text on the GUIObject.
//value: The value of the GUIObject.
//enabled: Boolean if the GUIObject is enabled or not.
//visible: Boolean if the GUIObject is visisble or not.
//gravity: The way the GUIObject needs to be aligned.
GUIObject(ImageManager& imageManager, SDL_Renderer& renderer, int left = 0, int top = 0, int width = 0, int height = 0,
const char* caption = NULL, int value = 0,
bool enabled = true, bool visible = true, int gravity = 0);
//Destructor.
virtual ~GUIObject();
//Method used to handle mouse and/or key events.
//x: The x mouse location.
//y: The y mouse location.
//enabled: Boolean if the parent is enabled or not.
//visible: Boolean if the parent is visible or not.
//processed: Boolean if the event has been processed (by the parent) or not.
//Returns: Boolean if the event is processed by the child.
virtual bool handleEvents(SDL_Renderer&renderer, int x=0, int y=0, bool enabled=true, bool visible=true, bool processed=false);
//Method that will render the GUIObject.
//x: The x location to draw the GUIObject. (x+left)
//y: The y location to draw the GUIObject. (y+top)
//draw: Draw widget or just update it without drawing
virtual void render(SDL_Renderer& renderer, int x=0,int y=0,bool draw=true);
//Method used to reposition subwidgets after a resize.
//NOTE: Currently only the GUIWindow call this function for its subwidgets automatically.
virtual void onResize();
void addChild(GUIObject* obj);
//Method for getting a child from a GUIObject.
//NOTE: This method doesn't search recursively.
//name: The name of the child to return.
//Returns: Pointer to the requested child, NULL otherwise.
GUIObject* getChild(const std::string& name);
//Experimental function to get the index of selected child control in keyboard only mode.
//Return value: the index of selected child control. -1 means nothing selected.
int getSelectedControl();
//Experimental function to set the index of selected child control in keyboard only mode.
void setSelectedControl(int index);
//Experimental function to move the focus in keyboard only mode.
//direction: the move direction, 1 or -1.
//selected: currently selected control (optional). Default value means obtain currently selected control automatically.
//Return value: the index of newly selected child control. -1 means nothing selected.
int selectNextControl(int direction, int selected = 0x80000000);
//Experimental function to process keyboard navigation events.
//NOTE: This function need to be called manually.
//keyboardNavigationMode: see the enum KeyboardNavigationMode.
//Return value: if this event is processed.
bool handleKeyboardNavigationEvents(ImageManager& imageManager, SDL_Renderer& renderer, int keyboardNavigationMode);
};
// A bit-field flags describing keyboard navigation mode.
enum KeyboardNavigationMode {
LeftRightFocus = 1, // left/right for focus movement
UpDownFocus = 2, // up/down for focus movement
TabFocus = 4, // tab/shift+tab for focus movement
ReturnControls = 8, // return for individual controls
LeftRightControls = 16, // left/right for individual controls
};
//Method used to handle the GUIEvents from the GUIEventQueue.
//kill: Boolean if an SDL_QUIT event may kill the GUIObjectRoot.
void GUIObjectHandleEvents(ImageManager &imageManager, SDL_Renderer &renderer, bool kill=false);
//A structure containing the needed variables to call an event.
struct GUIEvent{
//Event callback used to invoke the event.
GUIEventCallback* eventCallback;
//The name of the event.
std::string name;
//Pointer to the object which invoked the event.
GUIObject* obj;
//The type of event.
int eventType;
};
//List used to queue the gui events.
extern std::list<GUIEvent> GUIEventQueue;
class GUIButton:public GUIObject{
public:
GUIButton(ImageManager& imageManager, SDL_Renderer& renderer,int left=0,int top=0,int width=0,int height=0,
const char* caption=NULL,int value=0,
bool enabled=true,bool visible=true,int gravity=0):
GUIObject(imageManager,renderer,left,top,width,height,caption,value,enabled,visible,gravity),
smallFont(false){ }
//Method used to handle mouse and/or key events.
//x: The x mouse location.
//y: The y mouse location.
//enabled: Boolean if the parent is enabled or not.
//visible: Boolean if the parent is visible or not.
//processed: Boolean if the event has been processed (by the parent) or not.
//Returns: Boolean if the event is processed by the child.
virtual bool handleEvents(SDL_Renderer&renderer, int x=0, int y=0, bool enabled=true, bool visible=true, bool processed=false);
//Method that will render the GUIScrollBar.
//x: The x location to draw the GUIObject. (x+left)
//y: The y location to draw the GUIObject. (y+top)
//draw: Whether displey the widget or not.
virtual void render(SDL_Renderer& renderer, int x=0,int y=0,bool draw=true);
//Boolean if small font is used.
bool smallFont;
};
class GUICheckBox:public GUIObject{
public:
GUICheckBox(ImageManager& imageManager, SDL_Renderer& renderer,int left=0,int top=0,int width=0,int height=0,
const char* caption=NULL,int value=0,
bool enabled=true,bool visible=true,int gravity=0):
GUIObject(imageManager,renderer,left,top,width,height,caption,value,enabled,visible,gravity){}
//Method used to handle mouse and/or key events.
//x: The x mouse location.
//y: The y mouse location.
//enabled: Boolean if the parent is enabled or not.
//visible: Boolean if the parent is visible or not.
//processed: Boolean if the event has been processed (by the parent) or not.
//Returns: Boolean if the event is processed by the child.
virtual bool handleEvents(SDL_Renderer&,int x=0,int y=0,bool enabled=true,bool visible=true,bool processed=false);
//Method that will render the GUIScrollBar.
//x: The x location to draw the GUIObject. (x+left)
//y: The y location to draw the GUIObject. (y+top)
//draw: Whether displey the widget or not.
virtual void render(SDL_Renderer &renderer, int x=0, int y=0, bool draw=true);
};
class GUILabel:public GUIObject{
public:
GUILabel(ImageManager& imageManager, SDL_Renderer& renderer,int left=0,int top=0,int width=0,int height=0,
const char* caption=NULL,int value=0,
bool enabled=true,bool visible=true,int gravity=0):
GUIObject(imageManager,renderer,left,top,width,height,caption,value,enabled,visible,gravity){}
//Method used to handle mouse and/or key events.
//x: The x mouse location.
//y: The y mouse location.
//enabled: Boolean if the parent is enabled or not.
//visible: Boolean if the parent is visible or not.
//processed: Boolean if the event has been processed (by the parent) or not.
//Returns: Boolean if the event is processed by the child.
virtual bool handleEvents(SDL_Renderer&,int =0, int =0, bool =true, bool =true, bool processed=false);
//Method that will render the GUIScrollBar.
//x: The x location to draw the GUIObject. (x+left)
//y: The y location to draw the GUIObject. (y+top)
//draw: Whether displey the widget or not.
virtual void render(SDL_Renderer &renderer, int x=0, int y=0, bool draw=true);
};
class GUITextBox:public GUIObject{
public:
GUITextBox(ImageManager& imageManager, SDL_Renderer& renderer,int left=0,int top=0,int width=0,int height=0,
const char* caption=NULL,int value=0,
bool enabled=true,bool visible=true,int gravity=0):
GUIObject(imageManager,renderer,left,top,width,height,caption,value,enabled,visible,gravity),
highlightStart(0),highlightEnd(0),highlightStartX(0),highlightEndX(0),tick(15){}
//Method used to handle mouse and/or key events.
//x: The x mouse location.
//y: The y mouse location.
//enabled: Boolean if the parent is enabled or not.
//visible: Boolean if the parent is visible or not.
//processed: Boolean if the event has been processed (by the parent) or not.
//Returns: Boolean if the event is processed by the child.
virtual bool handleEvents(SDL_Renderer&,int x=0,int y=0,bool enabled=true,bool visible=true,bool processed=false);
//Method that will render the GUIScrollBar.
//x: The x location to draw the GUIObject. (x+left)
//y: The y location to draw the GUIObject. (y+top)
//draw: Whether displey the widget or not.
virtual void render(SDL_Renderer& renderer, int x=0,int y=0,bool draw=true);
//Method used to update text. This will also reset the selection.
void updateText(const std::string& text);
//Method used to update selection.
void updateSelection(int start, int end);
void blur();
private:
//Text highlights.
int highlightStart;
int highlightEnd;
int highlightStartX;
int highlightEndX;
//Carrot ticking.
int tick;
//Functions for modifying the text.
void backspaceChar();
void deleteChar();
void inputText(const char* s);
//Functions for moving the carrot.
void moveCarrotLeft();
void moveCarrotRight();
};
class GUIFrame:public GUIObject{
public:
GUIFrame(ImageManager& imageManager, SDL_Renderer& renderer,int left=0,int top=0,int width=0,int height=0,
const char* caption=NULL,int value=0,
bool enabled=true,bool visible=true,int gravity=0):
GUIObject(imageManager,renderer,left,top,width,height,caption,value,enabled,visible,gravity){
inDialog=true;
}
//Method used to handle mouse and/or key events.
//x: The x mouse location.
//y: The y mouse location.
//enabled: Boolean if the parent is enabled or not.
//visible: Boolean if the parent is visible or not.
//processed: Boolean if the event has been processed (by the parent) or not.
//Returns: Boolean if the event is processed by the child.
virtual bool handleEvents(SDL_Renderer&renderer, int x=0, int y=0, bool enabled=true, bool visible=true, bool processed=false);
//Method that will render the GUIScrollBar.
//x: The x location to draw the GUIObject. (x+left)
//y: The y location to draw the GUIObject. (y+top)
//draw: Whether displey the widget or not.
virtual void render(SDL_Renderer &renderer, int x=0, int y=0, bool draw=true);
};
//A GUIObject that holds an shared_ptr to a Texture for rendering.
class GUIImage:public GUIObject{
public:
GUIImage(ImageManager& imageManager, SDL_Renderer& renderer,int left=0,int top=0,int width=0,int height=0,
SharedTexture image = nullptr, SDL_Rect clip = SDL_Rect{0,0,0,0},
bool enabled=true,bool visible=true):
GUIObject(imageManager,renderer,left,top,width,height,NULL,0,enabled,visible,0),
image(image),clip(clip){ }
//Destructor.
~GUIImage();
//Method used to handle mouse and/or key events.
//x: The x mouse location.
//y: The y mouse location.
//enabled: Boolean if the parent is enabled or not.
//visible: Boolean if the parent is visible or not.
//processed: Boolean if the event has been processed (by the parent) or not.
//Returns: Boolean if the event is processed by the child.
virtual bool handleEvents(SDL_Renderer&,int =0, int =0, bool =true, bool =true, bool processed=false);
//Method that will render the GUIScrollBar.
//x: The x location to draw the GUIObject. (x+left)
//y: The y location to draw the GUIObject. (y+top)
//draw: Whether display the widget or not.
virtual void render(SDL_Renderer &renderer, int x=0, int y=0, bool draw=true);
//Method that will change the dimensions of the GUIImage so that the full image is shown.
//OR in case of a clip rect that the selected section of the image is shown.
void fitToImage();
//Method for setting the image of the widget.
//image: SharedTexture containing the image.
void setImage(SharedTexture texture){
image=texture;
}
//Method for setting the clip rectangle for the GUIImager.
//rect: The new clip rectangle.
void setClipRect(SDL_Rect rect){
clip=rect;
}
private:
//Pointer to the SDL_Texture to draw.
//MAY BE NULL!!
SharedTexture image;
//Optional rectangle for defining the section of the texture that should be drawn.
//NOTE: This doesn't have to correspond with the dimensions of the GUIObject.
SDL_Rect clip;
};
#endif
diff --git a/src/GUIWindow.cpp b/src/GUIWindow.cpp
index 85552fc..f491107 100644
--- a/src/GUIWindow.cpp
+++ b/src/GUIWindow.cpp
@@ -1,356 +1,363 @@
/*
* Copyright (C) 2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Functions.h"
#include "GUIWindow.h"
#include "ThemeManager.h"
using namespace std;
GUIWindow::GUIWindow(ImageManager& imageManager,SDL_Renderer& renderer,int left,int top,int width,int height,bool enabled,bool visible,const char* caption):
GUIObject(imageManager,renderer,left,top,width,height,caption,-1,enabled,visible){
//Set some default values.
inDialog = true;
dragging=false;
resizing=false;
minWidth=minHeight=0;
maxWidth=maxHeight=0;
this->caption = textureFromText(renderer, *fontGUI, caption, objThemes.getTextColor(true));
}
bool GUIWindow::handleEvents(SDL_Renderer& renderer,int x,int y,bool enabled,bool visible,bool processed){
//Boolean if the event is processed.
bool b=processed;
//The GUIObject is only enabled when he and his parent are enabled.
enabled=enabled && this->enabled;
//The GUIObject is only enabled when he and his parent are enabled.
visible=visible && this->visible;
//Get the absolute position.
x+=left;
y+=top;
//NOTE: We don't reset the state to have a "focus" effect.
//Only check for events when the object is both enabled and visible.
- if(enabled && visible && !b){
+ if(enabled && visible && !b &&
+ (event.type == SDL_MOUSEMOTION || event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEWHEEL))
+ {
//Check if the titlebar is hit.
bool clicked=(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_LEFT);
- //Check if the mouse is inside the window.
- SDL_Rect mouse={event.button.x,event.button.y,0,0};
+ SDL_Rect mouse={0,0,0,0};
+ SDL_GetMouseState(&mouse.x, &mouse.y);
SDL_Rect titlebar={x,y+5,width,43}; //We have a resize edge at the top five pixels.
//FIXME: Only set the cursor to POINTER when moving away from the GUIWindow?
if(clicked && pointOnRect(mouse,titlebar)){
//Mouse pressed inside the window,so assume dragging
dragging=true;
}
+ //Check if we should bring ourself to front.
+ if (event.type == SDL_MOUSEBUTTONDOWN && mouse.x >= x && mouse.x < x + width && mouse.y >= y && mouse.y < y + height) {
+ GUIObjectWhichWillBringToFront = this;
+ }
+
//Check for resizing.
SDL_Rect edge={x,y,width,5};
//Check each edge only if not resizing.
//NOTE: This is done to preserve the resize cursor type when off the edge.
bool topEdge=resizing?(resizeDirection==GUIResizeTop || resizeDirection==GUIResizeTopLeft || resizeDirection==GUIResizeTopRight):pointOnRect(mouse,edge);
edge.x=x+width-5;
edge.w=5;
edge.h=height;
bool rightEdge=resizing?(resizeDirection==GUIResizeRight || resizeDirection==GUIResizeTopRight || resizeDirection==GUIResizeBottomRight):pointOnRect(mouse,edge);
edge.x=x;
edge.y=y+height-5;
edge.w=width;
edge.h=5;
bool bottomEdge=resizing?(resizeDirection==GUIResizeBottom || resizeDirection==GUIResizeBottomLeft || resizeDirection==GUIResizeBottomRight):pointOnRect(mouse,edge);
edge.y=y;
edge.w=5;
edge.h=height;
bool leftEdge=resizing?(resizeDirection==GUIResizeLeft || resizeDirection==GUIResizeTopLeft || resizeDirection==GUIResizeBottomLeft):pointOnRect(mouse,edge);
//Set resizing true when resizing previously of clicking on a edge.
if(topEdge || rightEdge || bottomEdge || leftEdge)
resizing=resizing?true:clicked;
//Determine the resize direction.
if(topEdge){
resizeDirection=GUIResizeTop;
currentCursor=CURSOR_SIZE_VER;
//Check if there's an additional horizontal edge (corner).
if(leftEdge){
currentCursor=CURSOR_SIZE_FDIAG;
resizeDirection=GUIResizeTopLeft;
}else if(rightEdge){
currentCursor=CURSOR_SIZE_BDIAG;
resizeDirection=GUIResizeTopRight;
}
}else if(bottomEdge){
resizeDirection=GUIResizeBottom;
currentCursor=CURSOR_SIZE_VER;
//Check if there's an additional horizontal edge (corner).
if(leftEdge){
currentCursor=CURSOR_SIZE_BDIAG;
resizeDirection=GUIResizeBottomLeft;
}else if(rightEdge){
currentCursor=CURSOR_SIZE_FDIAG;
resizeDirection=GUIResizeBottomRight;
}
}else if(leftEdge){
resizeDirection=GUIResizeLeft;
currentCursor=CURSOR_SIZE_HOR;
}else if(rightEdge){
resizeDirection=GUIResizeRight;
currentCursor=CURSOR_SIZE_HOR;
}
if(event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT){
//Stop dragging
dragging=false;
SDL_Rect mouse={event.button.x,event.button.y,0,0};
//Check if close button clicked
{
SDL_Rect r={left+width-36,top+12,24,24};
if(pointOnRect(mouse,r)){
this->visible=false;
//And we add a close event to the queue.
GUIEvent e={eventCallback,name,this,GUIEventClick};
GUIEventQueue.push_back(e);
}
}
}else if(event.type==SDL_MOUSEMOTION){
if((event.motion.state & SDL_BUTTON_LMASK)==0){
//Stop dragging or resizing.
dragging=false;
resizing=false;
}else if(dragging){
move(left+event.motion.xrel,top+event.motion.yrel);
}else if(resizing){
//Check what the resize direction is.
switch(resizeDirection){
case GUIResizeTop:
resize(left,top+event.motion.yrel,width,height-event.motion.yrel);
break;
case GUIResizeTopRight:
resize(left,top+event.motion.yrel,width+event.motion.xrel,height-event.motion.yrel);
break;
case GUIResizeRight:
resize(left,top,width+event.motion.xrel,height);
break;
case GUIResizeBottomRight:
resize(left,top,width+event.motion.xrel,height+event.motion.yrel);
break;
case GUIResizeBottom:
resize(left,top,width,height+event.motion.yrel);
break;
case GUIResizeBottomLeft:
resize(left+event.motion.xrel,top,width-event.motion.xrel,height+event.motion.yrel);
break;
case GUIResizeLeft:
resize(left+event.motion.xrel,top,width-event.motion.xrel,height);
break;
case GUIResizeTopLeft:
resize(left+event.motion.xrel,top+event.motion.yrel,width-event.motion.xrel,height-event.motion.yrel);
break;
}
}
}
//Also update the cursor type accordingly.
if(dragging)
currentCursor=CURSOR_DRAG;
}
//Process child controls event.
for (int i = childControls.size() - 1; i >= 0; i--) {
bool b1 = childControls[i]->handleEvents(renderer, x, y, enabled, visible, b);
//The event is processed when either our or the childs is true (or both).
b=b||b1;
}
//If we are visible, the event is a mouse event, and the mouse is inside the widget, we mark this event as processed.
if (visible &&
((event.type == SDL_MOUSEMOTION && event.motion.state == 0)
|| event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEWHEEL))
{
//The mouse location (x=i, y=j) and the mouse button (k).
int i, j, k;
k = SDL_GetMouseState(&i, &j);
//Check if the mouse is inside the widget.
if (i >= x && i < x + width && j >= y && j < y + height) {
b = true;
}
}
return b;
}
void GUIWindow::move(int x,int y){
//Check the horizontal bounds.
if(x>SCREEN_WIDTH-width)
x=SCREEN_WIDTH-width;
else if(x<0)
x=0;
//Check the vertical bounds.
if(y>SCREEN_HEIGHT-height)
y=SCREEN_HEIGHT-height;
else if(y<0)
y=0;
//And set the new position.
left=x;
top=y;
}
static inline int resizeBorder(int coord, int oldWidth, int newWidth, int gravity) {
switch (gravity) {
default:
return coord;
break;
case GUIGravityCenter:
return coord + newWidth / 2 - oldWidth / 2;
break;
case GUIGravityRight:
return coord + newWidth - oldWidth;
break;
}
}
void GUIWindow::resize(int x,int y,int width,int height){
//FIXME: In case of resizing to the left or top the window moves when the maximum size has been reached.
//Check for the minimum width.
if(minWidth){
if(width<minWidth)
width=minWidth;
}
//Check for the minimum height.
if(minHeight){
if(height<minHeight)
height=minHeight;
}
//Check for maximum width.
if(maxWidth){
if(width>maxWidth)
width=maxWidth;
}
//Check for maximum height.
if(maxHeight){
if(height>maxHeight)
height=maxHeight;
}
//Resize child widgets.
for (auto obj : childControls) {
int widgetLeft = obj->left;
int widgetTop = obj->top;
int widgetRight = widgetLeft + obj->width;
int widgetBottom = widgetTop + obj->height;
widgetLeft = resizeBorder(widgetLeft, this->width, width, obj->gravityLeft);
widgetTop = resizeBorder(widgetTop, this->height, height, obj->gravityTop);
widgetRight = resizeBorder(widgetRight, this->width, width, obj->gravityRight);
widgetBottom = resizeBorder(widgetBottom, this->height, height, obj->gravityBottom);
obj->left = widgetLeft;
obj->top = widgetTop;
int newWidth = widgetRight - widgetLeft;
int newHeight = widgetBottom - widgetTop;
if (newWidth != obj->width || newHeight != obj->height) {
obj->width = newWidth;
obj->height = newHeight;
obj->onResize();
}
}
//Now set the values.
this->left=x;
this->top=y;
this->width=width;
this->height=height;
//And we add a resize event to the queue.
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
void GUIWindow::render(SDL_Renderer& renderer,int x,int y,bool draw){
//Rectangle the size of the GUIObject, used to draw borders.
//SDL_Rect r; //Unused local variable :/
//There's no need drawing the GUIObject when it's invisible.
if(!visible||!draw)
return;
//Get the absolute x and y location.
x+=left;
y+=top;
//Draw the frame.
Uint32 color=0xFFFFFFFF;
drawGUIBox(x,y,width,height,renderer,color);
//Draw the titlebar.
color=0x00000033;
drawGUIBox(x,y,width,48,renderer,color);
//Get the mouse position.
int mouseX,mouseY;
SDL_GetMouseState(&mouseX,&mouseY);
SDL_Rect mouse={mouseX,mouseY,0,0};
//Draw the close button.
{
//check highlight
const SDL_Rect r={left+width-36,top+12,24,24};
if(pointOnRect(mouse,r)){
drawGUIBox(r.x,r.y,r.w,r.h,renderer,0x999999FFU);
}
const SDL_Rect srcRect={112,0,16,16};
const SDL_Rect dstRect={left+width-32, top+16, 16, 16};
SDL_RenderCopy(&renderer, bmGuiTex.get(), &srcRect, &dstRect);
}
//Draw the caption.
{
const SDL_Rect captionSize = rectFromTexture(*caption);
const SDL_Rect captionRect={6,8,width-16,32};
applyTexture(x+captionRect.x+(captionRect.w-captionSize.w)/2,
y+captionRect.y+(captionRect.h-captionSize.h)/2,
caption,
renderer);
}
//We now need to draw all the children of the GUIObject.
for(unsigned int i=0;i<childControls.size();i++){
childControls[i]->render(renderer,x,y,draw);
}
}
void GUIWindow::GUIEventCallback_OnEvent(ImageManager& imageManager,SDL_Renderer& renderer,string name,GUIObject* obj,int eventType){
//Check if we have a eventCallback.
if(eventCallback){
//We call the onEvent method of the callback, but change the GUIObject pointer to ourself.
eventCallback->GUIEventCallback_OnEvent(imageManager,renderer,name,this,eventType);
}
}

File Metadata

Mime Type
text/x-diff
Expires
Fri, May 8, 8:25 PM (1 w, 23 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
62733
Default Alt Text
(67 KB)

Event Timeline