Page MenuHomePhabricator (Chris)

No OneTemporary

Authored By
Unknown
Size
96 KB
Referenced Files
None
Subscribers
None
diff --git a/src/Commands.cpp b/src/Commands.cpp
index 4ec9de3..7e1576d 100644
--- a/src/Commands.cpp
+++ b/src/Commands.cpp
@@ -1,1035 +1,1057 @@
/*
* Copyright (C) 2018 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/>.
*/
// An undo/redo system based on <http://www.codeproject.com/Articles/2500/A-Basic-Undo-Redo-Framework-For-C>.
// Originally written by squarecross <https://forum.freegamedev.net/viewtopic.php?f=48&t=5432>.
#include "Commands.h"
#include "GameObjects.h"
#include "Block.h"
#include "LevelEditor.h"
#include "Globals.h"
#include "Functions.h"
#include <algorithm>
#include <string>
#include <vector>
#include <map>
#include <stdio.h>
#include "libs/tinyformat/tinyformat.h"
Command::~Command() {
}
//////////////////////////////MoveGameObjectCommand///////////////////////////////////
MoveGameObjectCommand::MoveGameObjectCommand(LevelEditor* levelEditor, GameObject* gameObject, int x, int y, int w, int h)
: editor(levelEditor), objects(1, gameObject)
, resizeCommand(NULL)
{
SDL_Rect r = gameObject->getBox();
r.x = x;
r.y = y;
if (w >= 0) r.w = w;
if (h >= 0) r.h = h;
newPosition.push_back(r);
init();
}
MoveGameObjectCommand::MoveGameObjectCommand(LevelEditor* levelEditor, std::vector<GameObject*>& gameObjects, int dx, int dy)
: editor(levelEditor), objects(gameObjects)
, resizeCommand(NULL)
{
for (auto obj : objects) {
SDL_Rect r = obj->getBox();
r.x += dx;
r.y += dy;
newPosition.push_back(r);
}
init();
}
void MoveGameObjectCommand::init() {
//Initialize old position.
for (auto obj : objects) {
SDL_Rect r = obj->getBox();
oldPosition.push_back(r);
}
// Create resize command if necessary.
resizeCommand = ResizeLevelCommand::createAndShiftIfNecessary(editor, newPosition);
}
MoveGameObjectCommand::~MoveGameObjectCommand(){
if (resizeCommand) delete resizeCommand;
}
void MoveGameObjectCommand::execute(){
// First resize the level if necessary.
if (resizeCommand) resizeCommand->execute();
// Set the obj at its new position.
for (int i = 0; i < (int)objects.size(); i++) {
const SDL_Rect &r = newPosition[i];
GameObject *obj = objects[i];
obj->setBaseLocation(r.x, r.y);
obj->setBaseSize(r.w, r.h);
}
}
void MoveGameObjectCommand::unexecute() {
// First undo the resize if necessary.
if (resizeCommand) resizeCommand->unexecute();
// Set the obj at its old position.
for (int i = 0; i < (int)objects.size(); i++) {
const SDL_Rect &r = oldPosition[i];
GameObject *obj = objects[i];
obj->setBaseLocation(r.x, r.y);
obj->setBaseSize(r.w, r.h);
}
}
ResizeLevelCommand::ResizeLevelCommand(LevelEditor* levelEditor, int newWidth, int newHeight, int diffx, int diffy)
: editor(levelEditor), newLevelWidth(newWidth), newLevelHeight(newHeight), diffx(diffx), diffy(diffy)
{
oldLevelWidth = LEVEL_WIDTH;
oldLevelHeight = LEVEL_HEIGHT;
}
ResizeLevelCommand::~ResizeLevelCommand() {
}
void ResizeLevelCommand::execute() {
resizeLevel(editor, newLevelWidth, newLevelHeight, diffx, diffy);
}
void ResizeLevelCommand::unexecute() {
resizeLevel(editor, oldLevelWidth, oldLevelHeight, -diffx, -diffy);
}
void ResizeLevelCommand::resizeLevel(LevelEditor* levelEditor, int newWidth, int newHeight, int diffx, int diffy) {
LEVEL_WIDTH = newWidth;
LEVEL_HEIGHT = newHeight;
if (diffx != 0 || diffy != 0) {
camera.x += diffx;
camera.y += diffy;
for (unsigned int o = 0; o < levelEditor->levelObjects.size(); o++){
SDL_Rect r = levelEditor->levelObjects[o]->getBox();
levelEditor->levelObjects[o]->setBaseLocation(r.x + diffx, r.y + diffy);
}
for (auto it = levelEditor->sceneryLayers.begin(); it != levelEditor->sceneryLayers.end(); ++it) {
for (auto o : it->second->objects){
SDL_Rect r = o->getBox();
o->setBaseLocation(r.x + diffx, r.y + diffy);
}
}
}
}
ResizeLevelCommand* ResizeLevelCommand::createAndShiftIfNecessary(LevelEditor* levelEditor, std::vector<SDL_Rect>& position) {
// Calculate new level size, shift, etc.
int newLevelWidth = LEVEL_WIDTH;
int newLevelHeight = LEVEL_HEIGHT;
int diffx = 0, diffy = 0;
for (int i = 0; i < (int)position.size(); i++) {
const SDL_Rect &r = position[i];
if (r.x + r.w > newLevelWidth) {
newLevelWidth = r.x + r.w;
}
if (r.y + r.h > newLevelHeight) {
newLevelHeight = r.y + r.h;
}
if (r.x + diffx < 0) diffx = -r.x;
if (r.y + diffy < 0) diffy = -r.y;
}
newLevelWidth += diffx;
newLevelHeight += diffy;
if (newLevelWidth != LEVEL_WIDTH || newLevelHeight != LEVEL_HEIGHT || diffx || diffy) {
if (diffx || diffy) {
for (int i = 0; i < (int)position.size(); i++) {
SDL_Rect &r = position[i];
r.x += diffx;
r.y += diffy;
}
}
return new ResizeLevelCommand(levelEditor, newLevelWidth, newLevelHeight, diffx, diffy);
} else {
return NULL;
}
}
std::string ResizeLevelCommand::describe() {
return _("Resize level");
}
//////////////////////////////AddRemoveGameObjectCommand///////////////////////////////////
AddRemoveGameObjectCommand::AddRemoveGameObjectCommand(LevelEditor* levelEditor, GameObject* gameObject, bool isAdd_)
: editor(levelEditor), objects(1, gameObject), isAdd(isAdd_), ownObject(isAdd_)
, resizeCommand(NULL), removeStartCommand(NULL), oldTriggers(NULL)
{
init();
}
AddRemoveGameObjectCommand::AddRemoveGameObjectCommand(LevelEditor* levelEditor, std::vector<GameObject*>& gameObjects, bool isAdd_)
: editor(levelEditor), objects(gameObjects), isAdd(isAdd_), ownObject(isAdd_)
, resizeCommand(NULL), removeStartCommand(NULL), oldTriggers(NULL)
{
init();
}
void AddRemoveGameObjectCommand::init() {
if (isAdd) {
theLayer = editor->selectedLayer;
// adjust the object position if necessary
std::vector<SDL_Rect> position;
for (auto obj : objects) {
position.push_back(obj->getBox());
}
resizeCommand = ResizeLevelCommand::createAndShiftIfNecessary(editor, position);
if (resizeCommand) {
for (int i = 0; i < (int)objects.size(); i++) {
const SDL_Rect &r = position[i];
objects[i]->setBaseLocation(r.x, r.y);
}
}
// remove the existing player/shadow start if necessary
std::vector<bool> typesToBeRemoved((int)TYPE_MAX, false);
bool removeSomething = false;
for (auto obj : objects) {
if (obj->type == TYPE_START_PLAYER || obj->type == TYPE_START_SHADOW) {
typesToBeRemoved[obj->type] = true;
removeSomething = true;
}
}
if (removeSomething) {
std::vector<GameObject*> objectsToBeRemoved;
for (auto obj : editor->levelObjects) {
if (typesToBeRemoved[obj->type]) objectsToBeRemoved.push_back(obj);
}
if (!objectsToBeRemoved.empty()) {
removeStartCommand = new AddRemoveGameObjectCommand(editor, objectsToBeRemoved, false);
}
}
}
}
void AddRemoveGameObjectCommand::backupTriggers() {
if (oldTriggers == NULL) oldTriggers = new LevelEditor::Triggers(editor->triggers);
}
AddRemoveGameObjectCommand::~AddRemoveGameObjectCommand(){
//Remove the objects if we own them.
if (ownObject) {
for (auto obj : objects) {
delete obj;
}
}
//Delete internal commands.
if (resizeCommand) delete resizeCommand;
if (removeStartCommand) delete removeStartCommand;
//Delete old triggers
if (oldTriggers) delete oldTriggers;
}
void AddRemoveGameObjectCommand::addGameObject(){
// some sanity check
assert(ownObject);
// Remove old start position if necessary.
if (removeStartCommand) removeStartCommand->execute();
// Resize the level if necessary.
if (resizeCommand) resizeCommand->execute();
// Add each object to level
for (int index = 0; index < (int)objects.size(); index++) {
GameObject *obj = objects[index];
//Increase totalCollectables everytime we add a new collectable
if (obj->type == TYPE_COLLECTABLE) {
editor->totalCollectables++;
}
Block* block = dynamic_cast<Block*>(obj);
Scenery* scenery = dynamic_cast<Scenery*>(obj);
//Add it to the levelObjects.
if (theLayer.empty()) {
assert(block != NULL);
editor->levelObjects.push_back(block);
} else {
assert(scenery != NULL);
auto it = editor->sceneryLayers.find(theLayer);
assert(it != editor->sceneryLayers.end());
it->second->objects.push_back(scenery);
}
//GameObject type specific stuff.
switch (obj->type){
case TYPE_BUTTON:
case TYPE_SWITCH:
case TYPE_PORTAL:
{
//If block doesn't have an id (new object).
if (block->getEditorProperty("id").empty()) {
//Give it it's own id.
char s[64];
sprintf(s, "%u", editor->currentId);
editor->currentId++;
block->setEditorProperty("id", s);
}
break;
}
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
{
//Add the object to the moving blocks.
vector<MovingPosition> positions;
editor->movingBlocks[block] = positions;
//Get the moving position.
const vector<SDL_Rect> &movingPos = block->movingPos;
//Add the object to the movingBlocks vector.
editor->movingBlocks[block].clear();
for (int i = 0, m = movingPos.size(); i < m; i++) {
MovingPosition position(movingPos[i].x, movingPos[i].y, movingPos[i].w);
editor->movingBlocks[block].push_back(position);
}
//If block doesn't have an id.
if (block->getEditorProperty("id").empty()) {
//Give it it's own id.
char s[64];
sprintf(s, "%u", editor->currentId);
editor->currentId++;
block->setEditorProperty("id", s);
}
break;
}
default:
break;
}
}
// now we doesn't own the object anymore.
ownObject = false;
}
void AddRemoveGameObjectCommand::removeGameObject(){
// some sanity check
assert(!ownObject);
//Make sure we don't access the removed object through
//moving/linking.
editor->movingBlock = nullptr;
editor->linkingTrigger = nullptr;
editor->moving = false;
editor->linking = false;
// Remove objects
for (int index = 0; index < (int)objects.size(); index++) {
GameObject *obj = objects[index];
std::vector<GameObject*>::iterator it;
std::map<Block*, vector<GameObject*> >::iterator mapIt;
//Decrease totalCollectables everytime we remove a collectable
if (obj->type == TYPE_COLLECTABLE){
editor->totalCollectables--;
}
//Check if the object is in the selection.
it = find(editor->selection.begin(), editor->selection.end(), obj);
if (it != editor->selection.end()){
//It is so we delete it.
editor->selection.erase(it);
}
Block *theBlock = dynamic_cast<Block*>(obj);
Scenery *theScenery = dynamic_cast<Scenery*>(obj);
//Check if the object is in the triggers.
if (theBlock) {
mapIt = editor->triggers.find(theBlock);
if (mapIt != editor->triggers.end()){
//Back up triggers if it's remove mode.
if (!isAdd) backupTriggers();
//Remove the object from triggers.
editor->triggers.erase(mapIt);
}
}
//Boolean if it could be a target.
if (obj->type == TYPE_MOVING_BLOCK || obj->type == TYPE_MOVING_SHADOW_BLOCK || obj->type == TYPE_MOVING_SPIKES
|| obj->type == TYPE_CONVEYOR_BELT || obj->type == TYPE_SHADOW_CONVEYOR_BELT || obj->type == TYPE_PORTAL)
{
for (mapIt = editor->triggers.begin(); mapIt != editor->triggers.end(); ++mapIt){
//Now loop the target vector.
for (int o = mapIt->second.size() - 1; o >= 0; o--){
//Check if the obj is in the target vector.
if (mapIt->second[o] == obj){
//Back up triggers if it's remove mode.
if (!isAdd) backupTriggers();
//Remove the object from triggers.
mapIt->second.erase(mapIt->second.begin() + o);
}
}
}
}
//Check if the object is in the movingObjects.
if (theBlock) {
std::map<Block*, vector<MovingPosition> >::iterator movIt;
movIt = editor->movingBlocks.find(theBlock);
if (movIt != editor->movingBlocks.end()){
//It is so we remove it.
editor->movingBlocks.erase(movIt);
}
}
//Check if the block isn't being configured with a window one way or another.
for (;;) {
std::map<GUIObject*, GameObject*>::iterator confIt;
for (confIt = editor->objectWindows.begin(); confIt != editor->objectWindows.end(); ++confIt){
if (confIt->second == obj) break;
}
if (confIt == editor->objectWindows.end()) break;
editor->destroyWindow(confIt->first);
}
//Now we remove the object from the levelObjects and/or scenery.
if (theBlock){
std::vector<Block*>::iterator it;
it = find(editor->levelObjects.begin(), editor->levelObjects.end(), theBlock);
if (it != editor->levelObjects.end()){
editor->levelObjects.erase(it);
theLayer.clear(); // record the layer of this object
}
} else if (theScenery) {
for (auto it = editor->sceneryLayers.begin(); it != editor->sceneryLayers.end(); ++it){
auto it2 = find(it->second->objects.begin(), it->second->objects.end(), theScenery);
if (it2 != it->second->objects.end()) {
it->second->objects.erase(it2);
theLayer = it->first; // record the layer of this object
break;
}
}
}
}
// Resize the level if necessary.
if (resizeCommand) resizeCommand->unexecute();
// Restore old start position if necessary.
if (removeStartCommand) removeStartCommand->unexecute();
// now we own this object
ownObject = true;
//Set dirty of selection popup
editor->selectionDirty();
}
void AddRemoveGameObjectCommand::execute(){
if (isAdd) {
addGameObject();
} else {
removeGameObject();
}
}
void AddRemoveGameObjectCommand::unexecute(){
if (isAdd) {
removeGameObject();
} else {
addGameObject();
}
// Restore old triggers if necessary
if (oldTriggers) editor->triggers = *oldTriggers;
}
//////////////////////////////AddRemovePathCommand///////////////////////////////////
AddRemovePathCommand::AddRemovePathCommand(LevelEditor* levelEditor, Block* movingBlock, MovingPosition movingPosition, bool isAdd_)
:editor(levelEditor), target(movingBlock), movePos(movingPosition), isAdd(isAdd_)
{
if (!isAdd) {
if (target->movingPos.empty()) {
assert(!"movingBlock->movingPos is empty!");
} else {
const SDL_Rect& r = target->movingPos.back();
movePos.x = r.x;
movePos.y = r.y;
movePos.time = r.w;
}
}
}
AddRemovePathCommand::~AddRemovePathCommand(){
}
void AddRemovePathCommand::execute(){
if (isAdd) addPath();
else removePath();
}
void AddRemovePathCommand::unexecute(){
if (isAdd) removePath();
else addPath();
}
void AddRemovePathCommand::addPath() {
//Add movePos to the path.
editor->movingBlocks[target].push_back(movePos);
//Write the path to the moving block.
setEditorData();
}
void AddRemovePathCommand::removePath() {
//Remove the last point in the path.
editor->movingBlocks[target].pop_back();
//Write the path to the moving block.
setEditorData();
}
void AddRemovePathCommand::setEditorData() {
target->movingPos.clear();
for (const MovingPosition& p : editor->movingBlocks[target]) {
SDL_Rect r = { p.x, p.y, p.time };
target->movingPos.push_back(r);
}
}
//////////////////////////////RemovePathCommand///////////////////////////////////
RemovePathCommand::RemovePathCommand(LevelEditor* levelEditor, Block* targetBlock)
:editor(levelEditor), target(targetBlock){
}
RemovePathCommand::~RemovePathCommand(){
}
void RemovePathCommand::execute(){
//Set the number of movingPositions to 0.
target->setEditorProperty("MovingPosCount","0");
std::map<Block*,vector<MovingPosition> >::iterator it;
//Check if target is in movingBlocks.
it = editor->movingBlocks.find(target);
if(it!=editor->movingBlocks.end()){
//Store the movingPositions for unexecute.
movePositions = it->second;
//Clear all its movingPositions
it->second.clear();
}
}
void RemovePathCommand::unexecute(){
std::map<Block*,vector<MovingPosition> >::iterator it;
//Find target in movingBlocks
it = editor->movingBlocks.find(target);
if(it!=editor->movingBlocks.end()){
//Restore its movingPositions
it->second = movePositions;
//Write the path to the moving block.
std::map<std::string,std::string> editorData;
char s[64], s0[64];
sprintf(s,"%d",int(editor->movingBlocks[target].size()));
editorData["MovingPosCount"]=s;
//Loop through the positions.
for(unsigned int o=0; o<editor->movingBlocks[target].size(); o++) {
sprintf(s0+1,"%u",o);
sprintf(s,"%d",editor->movingBlocks[target][o].x);
s0[0]='x';
editorData[s0]=s;
sprintf(s,"%d",editor->movingBlocks[target][o].y);
s0[0]='y';
editorData[s0]=s;
sprintf(s,"%d",editor->movingBlocks[target][o].time);
s0[0]='t';
editorData[s0]=s;
}
target->setEditorData(editorData);
}
}
//////////////////////////////AddLinkCommand///////////////////////////////////
AddLinkCommand::AddLinkCommand(LevelEditor* levelEditor, Block* linkingTrigger, GameObject* clickedObject)
- :editor(levelEditor), target(linkingTrigger), clickedObj(clickedObject),oldPortalLink(NULL), destination(""), id(""){
+ :editor(levelEditor), target(linkingTrigger), clickedObj(clickedObject),oldPortalLink(NULL), destination(""), id(""), oldTrigger(NULL){
}
AddLinkCommand::~AddLinkCommand(){
}
void AddLinkCommand::execute(){
//Check if the target can handle multiple or only one link.
switch(target->type) {
case TYPE_PORTAL: {
//Store the last portal link.
if(!editor->triggers[target].empty()){
oldPortalLink = editor->triggers[target].back();
}
//Portals can only link to one so remove all existing links.
editor->triggers[target].clear();
editor->triggers[target].push_back(clickedObj);
break;
}
default: {
//The others can handle multiple links.
editor->triggers[target].push_back(clickedObj);
break;
}
}
//Check if it's a portal.
if(target->type==TYPE_PORTAL) {
//Store the previous destination.
destination = target->getEditorProperty("destination");
//Portals need to get the id of the other instead of give it's own id.
char s[64];
sprintf(s,"%d",atoi(clickedObj->getEditorProperty("id").c_str()));
target->setEditorProperty("destination",s);
} else{
//Store the previous id.
id = clickedObj->getEditorProperty("id");
+ if(!id.empty()) {
+ //Another block might already be set as trigger for the clickedObj.
+ for(auto block : editor->levelObjects) {
+ std::string otherId = block->getEditorProperty("id");
+ if(id.compare(otherId) == 0) {
+ //IDs match, so either one of the trigger itself or one of its (other) targets.
+ std::vector<GameObject*>::iterator it;
+ //Find the clickedObj in the block's triggers.
+ it = std::find(editor->triggers[block].begin(), editor->triggers[block].end(), clickedObj);
+ if(it != editor->triggers[block].end()){
+ oldTrigger = block;
+ editor->triggers[block].erase(it);
+ break;
+ }
+ }
+ }
+ }
//Give the clickedObject the same id as the trigger.
char s[64];
sprintf(s,"%d",atoi(target->getEditorProperty("id").c_str()));
clickedObj->setEditorProperty("id",s);
}
}
void AddLinkCommand::unexecute(){
//Check if the target can handle multiple or only one link.
switch(target->type) {
case TYPE_PORTAL: {
//Portals can only link to one so remove all existing links.
editor->triggers[target].clear();
//Put the previous portal link back.
if(oldPortalLink != NULL)
editor->triggers[target].push_back(oldPortalLink);
break;
}
default:{
std::vector<GameObject*>::iterator it;
//Find the clickedObj in the target's triggers.
it = std::find(editor->triggers[target].begin(), editor->triggers[target].end(), clickedObj);
if(it != editor->triggers[target].end()){
//Remove it.
editor->triggers[target].erase(it);
}
+
+ //Restore old trigger if applicable
+ if(oldTrigger != NULL){
+ editor->triggers[oldTrigger].push_back(clickedObj);
+ }
break;
}
}
//Check if it's a portal.
if(target->type==TYPE_PORTAL) {
//Restore previous destination.
target->setEditorProperty("destination",destination);
} else{
//Restore clickedObj's previous id.
clickedObj->setEditorProperty("id",id);
}
}
//////////////////////////////RemoveLinkCommand///////////////////////////////////
RemoveLinkCommand::RemoveLinkCommand(LevelEditor* levelEditor, Block* targetBlock)
: editor(levelEditor), target(targetBlock), destination(""), id(""){
}
RemoveLinkCommand::~RemoveLinkCommand(){
}
void RemoveLinkCommand::execute(){
std::map<Block*,vector<GameObject*> >::iterator it;
//Find target in triggers.
it = editor->triggers.find(target);
if(it!=editor->triggers.end()) {
//Copy the objects the target was linked to.
links = it->second;
//Remove the links.
it->second.clear();
}
//In case of a portal remove its destination field.
if(target->type==TYPE_PORTAL) {
//Copy its previous destination.
destination = target->getEditorProperty("destination");
//Erase its destination.
target->setEditorProperty("destination","");
} else{
//Copy its previous id.
id = target->getEditorProperty("id");
//Give the trigger a new id to prevent activating unlinked targets.
char s[64];
sprintf(s,"%u",editor->currentId);
editor->currentId++;
target->setEditorProperty("id",s);
}
}
void RemoveLinkCommand::unexecute(){
std::map<Block*,vector<GameObject*> >::iterator it;
//Find target in triggers.
it = editor->triggers.find(target);
if(it!=editor->triggers.end()) {
//Restore objects it was linked to.
it->second = links;
}
if(target->type== TYPE_PORTAL){
//Restore old destination.
target->setEditorProperty("destination", destination);
} else{
//Restore old id.
target->setEditorProperty("id", id);
}
}
SetEditorPropertyCommand::SetEditorPropertyCommand(LevelEditor* levelEditor, ImageManager& imageManager, SDL_Renderer& renderer, GameObject* targetBlock,
const std::string& propertyName, const std::string& propertyValue, const std::string& propertyDescription)
: editor(levelEditor), imageManager(imageManager), renderer(renderer)
, target(targetBlock), prop(propertyName), newValue(propertyValue), desc(propertyDescription)
{
oldValue = target->getEditorProperty(prop);
}
SetEditorPropertyCommand::~SetEditorPropertyCommand() {
}
void SetEditorPropertyCommand::execute() {
target->setEditorProperty(prop, newValue);
updateCustomScenery();
}
void SetEditorPropertyCommand::unexecute() {
target->setEditorProperty(prop, oldValue);
updateCustomScenery();
}
void SetEditorPropertyCommand::updateCustomScenery() {
Scenery *scenery = dynamic_cast<Scenery*>(target);
if (target && prop == "customScenery") {
scenery->updateCustomScenery(imageManager, renderer);
}
}
SetLevelPropertyCommand::~SetLevelPropertyCommand() {
}
void SetLevelPropertyCommand::execute() {
setLevelProperty(newProperty);
}
void SetLevelPropertyCommand::unexecute() {
setLevelProperty(oldProperty);
}
std::string SetLevelPropertyCommand::describe() {
return _("Modify level property");
}
SetScriptCommand::SetScriptCommand(LevelEditor* levelEditor, Block* targetBlock, const std::map<int, std::string>& script, const std::string& id)
: editor(levelEditor), target(targetBlock), newScript(script), id(id)
{
if (target) {
oldScript = target->scripts;
oldId = target->id;
} else {
oldScript = editor->scripts;
}
}
SetScriptCommand::~SetScriptCommand() {
}
void SetScriptCommand::execute() {
setScript(newScript, id);
}
void SetScriptCommand::unexecute() {
setScript(oldScript, oldId);
}
void SetScriptCommand::setScript(const std::map<int, std::string>& script, const std::string& id) {
if (target) {
target->scripts = script;
//Set the new id for the target block.
//TODO: Check for trigger links etc...
target->id = id;
} else {
editor->scripts = script;
}
}
AddRemoveLayerCommand::AddRemoveLayerCommand(LevelEditor* levelEditor, const std::string& layerName, bool isAdd_)
: editor(levelEditor), layer(NULL), theLayer(layerName), isAdd(isAdd_), ownObject(isAdd_)
{
if (isAdd) {
layer = new SceneryLayer;
} else {
auto it = editor->sceneryLayers.find(theLayer);
if (it != editor->sceneryLayers.end()) {
layer = it->second;
}
}
}
void AddRemoveLayerCommand::execute() {
if (isAdd) {
addLayer();
} else {
removeLayer();
}
}
void AddRemoveLayerCommand::unexecute() {
if (isAdd) {
removeLayer();
} else {
addLayer();
}
}
void AddRemoveLayerCommand::addLayer() {
assert(ownObject && layer);
assert(editor->sceneryLayers.find(theLayer) == editor->sceneryLayers.end());
// add this layer
editor->sceneryLayers[theLayer] = layer;
// show and select the newly created layer
editor->layerVisibility[theLayer] = true;
editor->selectedLayer = theLayer;
// deselect all
editor->deselectAll();
// change the ownership
ownObject = false;
}
void AddRemoveLayerCommand::removeLayer() {
assert(!ownObject);
assert(editor->sceneryLayers.find(theLayer) != editor->sceneryLayers.end());
assert(editor->sceneryLayers.find(theLayer)->second == layer);
// deselect all
editor->deselectAll();
// select the Blocks layer
editor->selectedLayer.clear();
// remove this layer
editor->layerVisibility.erase(theLayer);
editor->sceneryLayers.erase(theLayer);
// change the ownership
ownObject = true;
}
AddRemoveLayerCommand::~AddRemoveLayerCommand() {
if (ownObject) {
delete layer;
}
}
std::string AddRemoveLayerCommand::describe() {
return tfm::format(isAdd ? _("Add scenery layer %s") : _("Delete scenery layer %s"), theLayer);
}
SetLayerPropertyCommand::SetLayerPropertyCommand(LevelEditor* levelEditor, const std::string& oldName, const LayerProperty& newProperty)
: editor(levelEditor), newProperty(newProperty)
{
auto it = editor->sceneryLayers.find(oldName);
assert(it != editor->sceneryLayers.end() && it->second);
oldProperty.name = oldName;
oldProperty.speedX = it->second->speedX;
oldProperty.speedY = it->second->speedY;
oldProperty.cameraX = it->second->cameraX;
oldProperty.cameraY = it->second->cameraY;
}
void SetLayerPropertyCommand::execute() {
setLayerProperty(oldProperty.name, newProperty);
}
void SetLayerPropertyCommand::unexecute() {
setLayerProperty(newProperty.name, oldProperty);
}
SetLayerPropertyCommand::~SetLayerPropertyCommand() {
}
std::string SetLayerPropertyCommand::describe() {
return tfm::format(_("Modify the property of scenery layer %s"), oldProperty.name);
}
void SetLayerPropertyCommand::setLayerProperty(const std::string& oldName, const LayerProperty& newProperty) {
SceneryLayer *tmp = NULL;
// Find the existing layer
{
auto it = editor->sceneryLayers.find(oldName);
assert(it != editor->sceneryLayers.end() && it->second);
tmp = it->second;
}
// Check if we need to rename scenery layer
if (oldName != newProperty.name) {
assert(editor->sceneryLayers.find(newProperty.name) == editor->sceneryLayers.end());
// remove the old layer
editor->sceneryLayers.erase(oldName);
// then save the temp variable to the new layer
editor->sceneryLayers[newProperty.name] = tmp;
// show and select the newly created layer
editor->layerVisibility[newProperty.name] = editor->layerVisibility[oldName];
editor->layerVisibility.erase(oldName);
editor->selectedLayer = newProperty.name;
}
// Now we update the properties of the layer
tmp->speedX = newProperty.speedX;
tmp->speedY = newProperty.speedY;
tmp->cameraX = newProperty.cameraX;
tmp->cameraY = newProperty.cameraY;
}
MoveToLayerCommand::MoveToLayerCommand(LevelEditor* levelEditor, std::vector<GameObject*>& gameObjects, const std::string& oldName, const std::string& newName)
: editor(levelEditor), oldName(oldName), newName(newName), createNewLayer(NULL)
{
if (editor->sceneryLayers.find(newName) == editor->sceneryLayers.end()) {
createNewLayer = new AddRemoveLayerCommand(levelEditor, newName, true);
}
for (auto obj : gameObjects) {
Scenery *scenery = dynamic_cast<Scenery*>(obj);
if (scenery) objects.push_back(scenery);
}
}
void MoveToLayerCommand::execute() {
if (createNewLayer) {
createNewLayer->execute();
}
removeGameObject();
addGameObject(newName);
// show and select the new layer
editor->layerVisibility[newName] = true;
editor->selectedLayer = newName;
// deselect all
editor->deselectAll();
}
void MoveToLayerCommand::unexecute() {
removeGameObject();
addGameObject(oldName);
if (createNewLayer) {
createNewLayer->unexecute();
}
// show and select the old layer
editor->layerVisibility[oldName] = true;
editor->selectedLayer = oldName;
// deselect all
editor->deselectAll();
}
MoveToLayerCommand::~MoveToLayerCommand() {
if (createNewLayer) {
delete createNewLayer;
}
}
std::string MoveToLayerCommand::describe() {
return tfm::format(_("Move %d object(s) from layer %s to layer %s"), objects.size(), oldName, newName);
}
void MoveToLayerCommand::removeGameObject() {
// Remove objects
for (int index = 0; index < (int)objects.size(); index++) {
Scenery *scenery = objects[index];
//Now we remove the object from scenery.
for (auto it = editor->sceneryLayers.begin(); it != editor->sceneryLayers.end(); ++it){
auto it2 = find(it->second->objects.begin(), it->second->objects.end(), scenery);
if (it2 != it->second->objects.end()) {
it->second->objects.erase(it2);
break;
}
}
}
}
void MoveToLayerCommand::addGameObject(const std::string& layer) {
// Add objects
const auto& target = editor->sceneryLayers[layer];
for (int index = 0; index < (int)objects.size(); index++) {
target->objects.push_back(objects[index]);
}
}
diff --git a/src/Commands.h b/src/Commands.h
index 67ccfbf..77e8027 100644
--- a/src/Commands.h
+++ b/src/Commands.h
@@ -1,493 +1,496 @@
/*
* Copyright (C) 2018 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/>.
*/
// An undo/redo system based on <http://www.codeproject.com/Articles/2500/A-Basic-Undo-Redo-Framework-For-C>.
// Originally written by squarecross <https://forum.freegamedev.net/viewtopic.php?f=48&t=5432>.
#ifndef COMMANDS_H
#define COMMANDS_H
#include "LevelEditor.h"
#include <vector>
#include <string>
#include <map>
class Block;
class GameObject;
class LevelEditor;
class MovingPosition;
// Abstract class for commands.
class Command {
public:
// Do the command.
virtual void execute() = 0;
// Undo the command.
virtual void unexecute() = 0;
// The short description to the command.
virtual std::string describe() = 0;
// Deconstructor for derived classes.
virtual ~Command();
};
//Class for resize level.
class ResizeLevelCommand : public Command {
private:
LevelEditor* editor;
//New level size.
int newLevelWidth, newLevelHeight;
//Differences used for map dimension changes to shift position of other objects.
int diffx, diffy;
//Old level size.
int oldLevelWidth, oldLevelHeight;
public:
ResizeLevelCommand(LevelEditor* levelEditor, int newWidth, int newHeight, int diffx, int diffy);
virtual ~ResizeLevelCommand();
virtual void execute();
virtual void unexecute();
static void resizeLevel(LevelEditor* levelEditor, int newWidth, int newHeight, int diffx, int diffy);
// create a ResizeLevelCommand and shift the input position if any of them is outside the map area
static ResizeLevelCommand* createAndShiftIfNecessary(LevelEditor* levelEditor, std::vector<SDL_Rect>& position);
virtual std::string describe();
};
//Class for moving game objects.
class MoveGameObjectCommand: public Command{
private:
LevelEditor* editor;
//Object being moved.
std::vector<GameObject*> objects;
//New position.
std::vector<SDL_Rect> newPosition;
//Old position.
std::vector<SDL_Rect> oldPosition;
//Internal resize command. Can be NULL.
ResizeLevelCommand* resizeCommand;
public:
// Move or resize one object.
MoveGameObjectCommand(LevelEditor* levelEditor, GameObject* gameObject, int x, int y, int w, int h);
// Move a bunch of objects
MoveGameObjectCommand(LevelEditor* levelEditor, std::vector<GameObject*>& gameObjects, int dx, int dy);
virtual ~MoveGameObjectCommand();
//Moves the object to the specified position.
virtual void execute();
//Moves object back to its previous position.
virtual void unexecute();
virtual std::string describe();
private:
// initialize new/old positions, etc. of objects
void init();
};
//Class for add/remove game objects.
class AddRemoveGameObjectCommand: public Command {
private:
LevelEditor* editor;
//Object being added or removed.
std::vector<GameObject*> objects;
//The layer of the object.
std::string theLayer;
//Internal resize command for object placement. Can be NULL.
ResizeLevelCommand* resizeCommand;
//Internal command used to remove player/shadow start if we are adding new start point. Can be NULL.
AddRemoveGameObjectCommand* removeStartCommand;
//The working mode.
bool isAdd;
//Boolean indicates if we own this object.
bool ownObject;
//A copy of the level editor's triggers. Can be NULL.
LevelEditor::Triggers *oldTriggers;
private:
//Adds the gameObject.
void addGameObject();
//Removes the gameObject.
void removeGameObject();
public:
AddRemoveGameObjectCommand(LevelEditor* levelEditor, GameObject* gameObject, bool isAdd_);
AddRemoveGameObjectCommand(LevelEditor* levelEditor, std::vector<GameObject*>& gameObjects, bool isAdd_);
virtual void execute();
virtual void unexecute();
virtual ~AddRemoveGameObjectCommand();
virtual std::string describe();
private:
// initialize new positions, etc. of objects
void init();
// Back up old triggers. It can be only called once.
void backupTriggers();
};
//Class that adds/removes a moving position to the target moving block.
class AddRemovePathCommand : public Command{
private:
LevelEditor* editor;
//Point on the path that is being added.
MovingPosition movePos;
//Block that the path is being added to.
Block* target;
//The working mode.
bool isAdd;
public:
AddRemovePathCommand(LevelEditor* levelEditor, Block* movingBlock, MovingPosition movingPosition, bool isAdd_);
virtual ~AddRemovePathCommand();
//Add the MovingPosition.
virtual void execute();
//Remove the MovingPosition.
virtual void unexecute();
virtual std::string describe();
private:
void addPath();
void removePath();
void setEditorData();
};
//Class that clears the moving position of the target moving block.
class RemovePathCommand : public Command{
private:
LevelEditor* editor;
//Block that the path is being removed from.
Block* target;
//The MovingPositions of the target to be restored when unexecuting.
std::vector<MovingPosition> movePositions;
public:
RemovePathCommand(LevelEditor* levelEditor, Block* targetBlock);
virtual ~RemovePathCommand();
//Remove the path.
virtual void execute();
//Re-add the path.
virtual void unexecute();
virtual std::string describe();
};
//Class that adds a link from the target object ot the selected object.
class AddLinkCommand : public Command{
private:
LevelEditor* editor;
//Object that was clicked upon when adding the link.
GameObject* clickedObj;
//Object that is linking to another.
Block* target;
//Previous link of the target block if it is a portal (portal has only 1 link).
GameObject* oldPortalLink;
//Previous destination of portal.
std::string destination;
//Previous id of target.
std::string id;
+
+ //Previous switch/button that was linked to target (can be NULL)
+ Block* oldTrigger;
public:
AddLinkCommand(LevelEditor* levelEditor, Block* linkingTrigger, GameObject* clickedObject);
virtual ~AddLinkCommand();
//Add the link, clicked object will get the same id as the target block.
virtual void execute();
//Remove the link.
virtual void unexecute();
virtual std::string describe();
};
//Class that clears the links from the target object.
class RemoveLinkCommand : public Command{
private:
LevelEditor* editor;
//Block whose links are being removed.
Block* target;
//Copy of the links being removed.
std::vector<GameObject*> links;
//Previous destination of portal.
std::string destination;
//Previous target id.
std::string id;
public:
RemoveLinkCommand(LevelEditor* levelEditor, Block* targetBlock);
virtual ~RemoveLinkCommand();
//Remove all the links, changes the id of the object.
virtual void execute();
//Add all the links, restores the object's previous id.
virtual void unexecute();
virtual std::string describe();
};
//Class that modifies an editor property of an object.
class SetEditorPropertyCommand : public Command {
private:
LevelEditor* editor;
ImageManager& imageManager;
SDL_Renderer& renderer;
//Object being modified.
GameObject* target;
//The property being modified.
std::string prop;
//The new value of the property.
std::string newValue;
//The old value of the property.
std::string oldValue;
//The description of the property.
std::string desc;
public:
SetEditorPropertyCommand(LevelEditor* levelEditor, ImageManager& imageManager, SDL_Renderer& renderer, GameObject* targetBlock,
const std::string& propertyName, const std::string& propertyValue, const std::string& propertyDescription);
virtual ~SetEditorPropertyCommand();
virtual void execute();
virtual void unexecute();
virtual std::string describe();
private:
void updateCustomScenery();
};
//Class that modifies the level settings.
class SetLevelPropertyCommand : public Command {
public:
struct LevelProperty {
std::string levelName;
std::string levelTheme;
std::string levelMusic;
int levelTime;
int levelRecordings;
};
private:
LevelEditor* editor;
LevelProperty oldProperty;
LevelProperty newProperty;
public:
SetLevelPropertyCommand(LevelEditor* levelEditor, const LevelProperty& levelProperty);
virtual ~SetLevelPropertyCommand();
virtual void execute();
virtual void unexecute();
virtual std::string describe();
private:
void setLevelProperty(const LevelProperty& levelProperty);
};
//Class that modifies the scripting for block or level
// NOTE: currently the scripting for scenery block is unsupported.
class SetScriptCommand : public Command {
private:
LevelEditor* editor;
//Object being modified. Can be NULL.
Block* target;
std::map<int, std::string> newScript;
std::map<int, std::string> oldScript;
//the new id for the target block.
std::string id;
std::string oldId;
public:
SetScriptCommand(LevelEditor* levelEditor, Block* targetBlock, const std::map<int, std::string>& script, const std::string& id = std::string());
virtual ~SetScriptCommand();
virtual void execute();
virtual void unexecute();
virtual std::string describe();
private:
void setScript(const std::map<int, std::string>& script, const std::string& id);
};
//Class for add/remove scenery layer.
class AddRemoveLayerCommand : public Command {
private:
LevelEditor* editor;
//Layer added or removed.
SceneryLayer* layer;
//The layer of the object.
std::string theLayer;
//The working mode.
bool isAdd;
//Boolean indicates if we own this object.
bool ownObject;
private:
//Adds the layer.
void addLayer();
//Removes the layer.
void removeLayer();
public:
AddRemoveLayerCommand(LevelEditor* levelEditor, const std::string& layerName, bool isAdd_);
virtual void execute();
virtual void unexecute();
virtual ~AddRemoveLayerCommand();
virtual std::string describe();
};
//Class for editing scenery layer properties (including rename scenery layer).
class SetLayerPropertyCommand : public Command {
public:
struct LayerProperty {
std::string name;
float speedX, speedY;
float cameraX, cameraY;
};
private:
LevelEditor* editor;
LayerProperty oldProperty;
LayerProperty newProperty;
public:
SetLayerPropertyCommand(LevelEditor* levelEditor, const std::string& oldName, const LayerProperty& newProperty);
virtual void execute();
virtual void unexecute();
virtual ~SetLayerPropertyCommand();
virtual std::string describe();
private:
void setLayerProperty(const std::string& oldName, const LayerProperty& newProperty);
};
//Class for move object between layers.
class MoveToLayerCommand : public Command {
private:
LevelEditor* editor;
std::vector<Scenery*> objects;
std::string oldName;
std::string newName;
AddRemoveLayerCommand* createNewLayer;
public:
MoveToLayerCommand(LevelEditor* levelEditor, std::vector<GameObject*>& gameObjects, const std::string& oldName, const std::string& newName);
virtual void execute();
virtual void unexecute();
virtual ~MoveToLayerCommand();
virtual std::string describe();
private:
void removeGameObject();
void addGameObject(const std::string& layer);
};
#endif
diff --git a/src/GUIObject.cpp b/src/GUIObject.cpp
index ca9ab25..1e3c6e5 100644
--- a/src/GUIObject.cpp
+++ b/src/GUIObject.cpp
@@ -1,1223 +1,1226 @@
/*
* 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 "GUIObject.h"
#include "ThemeManager.h"
#include "InputManager.h"
#include "GUISpinBox.h"
#include "GUIListBox.h"
#include "GUISlider.h"
#include <algorithm>
#include <iostream>
#include <list>
#include <SDL_ttf.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;
void GUIObjectHandleEvents(ImageManager& imageManager, SDL_Renderer& renderer, bool kill){
- //NOTE: This was already not doing anything so commenting it for now.
- /*
- //Check if user resizes the window.
- if(event.type==SDL_VIDEORESIZE){
- //onVideoResize();
-
- //Don't let other objects process this event (?)
- return;
- }*/
-
//Check if we need to reset the skip variable.
if (event.type == SDL_MOUSEBUTTONDOWN) {
GUISkipNextMouseUpEvent = false;
}
//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);
}
//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(unsigned int i=0;i<childControls.size();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;
}
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<GUIButton*>(obj) || dynamic_cast<GUICheckBox*>(obj)
- || dynamic_cast<GUITextBox*>(obj) || dynamic_cast<GUISpinBox*>(obj)
+ if ((dynamic_cast<GUITextBox*>(obj) || dynamic_cast<GUISpinBox*>(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) || dynamic_cast<GUISpinBox*>(obj)) {
+ } else if (dynamic_cast<GUITextBox*>(obj)) {
//It's a text box.
+ if(i == index) {
+ obj->state = 2;
+ } else {
+ dynamic_cast<GUITextBox*>(obj)->blur();
+ }
+ } else if (dynamic_cast<GUISpinBox*>(obj)) {
+ //It's a spin box.
obj->state = (i == index) ? 2 : 0;
} 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<GUISpinBox*>(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 & 8) != 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 & 16) != 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 focus movement
int m = SDL_GetModState();
if (((keyboardNavigationMode & 1) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_RIGHT))
|| ((keyboardNavigationMode & 2) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_DOWN))
|| ((keyboardNavigationMode & 4) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_TAB) && (m & KMOD_SHIFT) == 0)
)
{
isKeyboardOnly = true;
selectNextControl(1);
return true;
} else if (((keyboardNavigationMode & 1) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_LEFT))
|| ((keyboardNavigationMode & 2) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_UP))
|| ((keyboardNavigationMode & 4) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_TAB) && (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.
if (enabled && visible) {
//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 && !b) {
//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.
b = true;
}
}
}
}
//Also let the children handle their events.
for(unsigned int i=0;i<childControls.size();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;
}
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.
if (enabled&&visible){
//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 && !b){
//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.
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) {
//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 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){
+ 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))){
+ } else if (event.type == SDL_MOUSEMOTION && (k&SDL_BUTTON(1)) && state == 2){
state = 2;
highlightEnd = finalPos;
highlightEndX = finalX;
}
} 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_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_LEFT){
- //Set state to 0.
- state = 0;
+ 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(unsigned int i=0;i<childControls.size();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;
}
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 4b417ab..303fdba 100644
--- a/src/GUIObject.h
+++ b/src/GUIObject.h
@@ -1,405 +1,406 @@
/*
* 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;
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 type of the GUIObject.
int type;
//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;
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);
//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);
//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: a bit-field flags consists of
//1=left/right for focus movement
//2=up/down for focus movement
//4=tab/shift+tab for focus movement
//8=return for individual controls
//16=left/right for individual controls
//Return value: if this event is processed.
bool handleKeyboardNavigationEvents(ImageManager& imageManager, SDL_Renderer& renderer, int keyboardNavigationMode);
};
//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

File Metadata

Mime Type
text/x-diff
Expires
Sat, May 16, 7:15 PM (1 d, 1 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
63360
Default Alt Text
(96 KB)

Event Timeline