Page MenuHomePhabricator (Chris)

No OneTemporary

Authored By
Unknown
Size
482 KB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/data/gfx/gui.png b/data/gfx/gui.png
index c62ed21..522b204 100644
Binary files a/data/gfx/gui.png and b/data/gfx/gui.png differ
diff --git a/src/Addons.cpp b/src/Addons.cpp
index 5091f99..7042410 100644
--- a/src/Addons.cpp
+++ b/src/Addons.cpp
@@ -1,696 +1,696 @@
/*
* 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 "Addons.h"
#include "GameState.h"
#include "Functions.h"
#include "FileManager.h"
#include "Globals.h"
#include "Objects.h"
#include "GUIObject.h"
#include "GUIOverlay.h"
#include "GUIScrollBar.h"
#include "GUITextArea.h"
#include "GUIListBox.h"
#include "POASerializer.h"
#include "InputManager.h"
#include <string>
#include <sstream>
#include <iostream>
#include "libs/tinyformat/tinyformat.h"
#include <SDL/SDL.h>
#ifdef __APPLE__
#include <SDL_ttf/SDL_ttf.h>
#else
#include <SDL/SDL_ttf.h>
#endif
using namespace std;
Addons::Addons(){
//Render the title.
title=TTF_RenderUTF8_Blended(fontTitle,_("Addons"),themeTextColor);
//Load placeholder addon icons and screenshot.
addonIcon[0]=loadImage(getDataPath()+"/gfx/addon1.png");
SDL_SetAlpha(addonIcon[0],0,0);
addonIcon[1]=loadImage(getDataPath()+"/gfx/addon2.png");
SDL_SetAlpha(addonIcon[1],0,0);
addonIcon[2]=loadImage(getDataPath()+"/gfx/addon3.png");
SDL_SetAlpha(addonIcon[2],0,0);
screenshot=loadImage(getDataPath()+"/gfx/screenshot.png");
FILE* addon=fopen((getUserPath(USER_CACHE)+"addons").c_str(),"wb");
addons=NULL;
selected=NULL;
//Clear the GUI if any.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Try to get(download) the addonsList.
if(getAddonsList(addon)==false) {
//It failed so we show the error message.
GUIObjectRoot=new GUIObject(0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
- GUIObject* obj=new GUIObject(90,96,200,32,GUIObjectLabel,_("Unable to initialize addon menu:"));
+ GUIObject* obj=new GUILabel(90,96,200,32,_("Unable to initialize addon menu:"));
obj->name="lbl";
GUIObjectRoot->addChild(obj);
- obj=new GUIObject(120,130,200,32,GUIObjectLabel,error.c_str());
+ obj=new GUILabel(120,130,200,32,error.c_str());
obj->name="lbl";
GUIObjectRoot->addChild(obj);
- obj=new GUIObject(90,550,200,32,GUIObjectButton,_("Back"));
+ obj=new GUIButton(90,550,200,32,_("Back"));
obj->name="cmdBack";
obj->eventCallback=this;
GUIObjectRoot->addChild(obj);
return;
}
//Now create the GUI.
createGUI();
}
Addons::~Addons(){
delete addons;
//Free the title surface.
SDL_FreeSurface(title);
//If the GUIObjectRoot exist delete it.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
void Addons::createGUI(){
//Downloaded the addons file now we can create the GUI.
GUIObjectRoot=new GUIObject(0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
//Create list of categories
GUISingleLineListBox *listTabs=new GUISingleLineListBox((SCREEN_WIDTH-360)/2,100,360,36);
listTabs->name="lstTabs";
listTabs->addItem(_("Levels"));
listTabs->addItem(_("Level Packs"));
listTabs->addItem(_("Themes"));
listTabs->value=0;
listTabs->eventCallback=this;
GUIObjectRoot->addChild(listTabs);
//Create the list for the addons.
//By default levels will be selected.
list=new GUIListBox(SCREEN_WIDTH*0.1,160,SCREEN_WIDTH*0.8,SCREEN_HEIGHT-210);
addonsToList("levels");
list->name="lstAddons";
list->clickEvents=true;
list->eventCallback=this;
list->value=-1;
GUIObjectRoot->addChild(list);
type="levels";
//The back button.
- GUIObject* obj=new GUIObject(20,20,-1,32,GUIObjectButton,_("Back"));
+ GUIObject* obj=new GUIButton(20,20,-1,32,_("Back"));
obj->name="cmdBack";
obj->eventCallback=this;
GUIObjectRoot->addChild(obj);
}
bool Addons::getAddonsList(FILE* file){
//First we download the file.
if(downloadFile(getSettings()->getValue("addon_url"),file)==false){
//NOTE: We keep the console output English so we put the string literal here twice.
cerr<<"ERROR: unable to download addons file!"<<endl;
error=_("ERROR: unable to download addons file!");
return false;
}
fclose(file);
//Load the downloaded file.
ifstream addonFile;
addonFile.open((getUserPath(USER_CACHE)+"addons").c_str());
if(!addonFile.good()) {
//NOTE: We keep the console output English so we put the string literal here twice.
cerr<<"ERROR: unable to load addon_list file!"<<endl;
/// TRANSLATORS: addon_list is the name of a file and should not be translated.
error=_("ERROR: unable to load addon_list file!");
return false;
}
//Parse the addonsfile.
TreeStorageNode obj;
{
POASerializer objSerializer;
if(!objSerializer.readNode(addonFile,&obj,true)){
//NOTE: We keep the console output English so we put the string literal here twice.
cerr<<"ERROR: Invalid file format of addons file!"<<endl;
error=_("ERROR: Invalid file format of addons file!");
return false;
}
}
//Also load the installed_addons file.
ifstream iaddonFile;
iaddonFile.open((getUserPath(USER_CONFIG)+"installed_addons").c_str());
if(!iaddonFile) {
//The installed_addons file doesn't exist, so we create it.
ofstream iaddons;
iaddons.open((getUserPath(USER_CONFIG)+"installed_addons").c_str());
iaddons<<" "<<endl;
iaddons.close();
//Also load the installed_addons file.
iaddonFile.open((getUserPath(USER_CONFIG)+"installed_addons").c_str());
if(!iaddonFile) {
//NOTE: We keep the console output English so we put the string literal here twice.
cerr<<"ERROR: Unable to create the installed_addons file."<<endl;
/// TRANSLATORS: installed_addons is the name of a file and should not be translated.
error=_("ERROR: Unable to create the installed_addons file.");
return false;
}
}
//And parse the installed_addons file.
TreeStorageNode obj1;
{
POASerializer objSerializer;
if(!objSerializer.readNode(iaddonFile,&obj1,true)){
//NOTE: We keep the console output English so we put the string literal here twice.
cerr<<"ERROR: Invalid file format of the installed_addons!"<<endl;
error=_("ERROR: Invalid file format of the installed_addons!");
return false;
}
}
//Fill the vector.
addons = new std::vector<Addon>;
fillAddonList(*addons,obj,obj1);
//Close the files.
iaddonFile.close();
addonFile.close();
return true;
}
void Addons::fillAddonList(std::vector<Addons::Addon> &list, TreeStorageNode &addons, TreeStorageNode &installed_addons){
//Loop through the blocks of the addons file.
//These should contain the types levels, levelpacks, themes.
for(unsigned int i=0;i<addons.subNodes.size();i++){
TreeStorageNode* block=addons.subNodes[i];
if(block==NULL) continue;
string type;
type=block->name;
//Now loop the entries(subNodes) of the block.
for(unsigned int i=0;i<block->subNodes.size();i++){
TreeStorageNode* entry=block->subNodes[i];
if(entry==NULL) continue;
if(entry->name=="entry" && entry->value.size()==1){
//The entry is valid so create a new Addon.
Addon addon;
addon.icon=addon.screenshot=NULL;
addon.type=type;
addon.name=entry->value[0];
if(!entry->attributes["file"].empty())
addon.file=entry->attributes["file"][0];
if(!entry->attributes["folder"].empty())
addon.folder=entry->attributes["folder"][0];
if(!entry->attributes["author"].empty())
addon.author=entry->attributes["author"][0];
if(!entry->attributes["description"].empty())
addon.description=entry->attributes["description"][0];
if(entry->attributes["icon"].size()>1){
//There are (at least) two values, the url to the icon and its md5sum used for caching.
addon.icon=loadCachedImage(entry->attributes["icon"][0].c_str(),entry->attributes["icon"][1].c_str());
if(addon.icon)
SDL_SetAlpha(addon.icon,0,0);
}
if(entry->attributes["screenshot"].size()>1){
//There are (at least) two values, the url to the screenshot and its md5sum used for caching.
addon.screenshot=loadCachedImage(entry->attributes["screenshot"][0].c_str(),entry->attributes["screenshot"][1].c_str());
if(addon.screenshot)
SDL_SetAlpha(addon.screenshot,0,0);
}
if(!entry->attributes["version"].empty())
addon.version=atoi(entry->attributes["version"][0].c_str());
addon.upToDate=false;
addon.installed=false;
//Check if the addon is already installed.
for(unsigned int i=0;i<installed_addons.subNodes.size();i++){
TreeStorageNode* installed=installed_addons.subNodes[i];
if(installed==NULL) continue;
if(installed->name=="entry" && installed->value.size()==3){
if(addon.type.compare(installed->value[0])==0 && addon.name.compare(installed->value[1])==0) {
addon.installed=true;
addon.installedVersion=atoi(installed->value[2].c_str());
if(addon.installedVersion>=addon.version) {
addon.upToDate=true;
}
}
}
}
//Finally put him in the list.
list.push_back(addon);
}
}
}
}
void Addons::addonsToList(const std::string &type){
list->clearItems();
for(unsigned int i=0;i<addons->size();i++) {
//Check if the addon is from the right type.
if((*addons)[i].type==type) {
string entry = (*addons)[i].name + " by " + (*addons)[i].author;
if((*addons)[i].installed) {
if((*addons)[i].upToDate) {
entry += " *";
} else {
entry += " +";
}
}
SDL_Surface* surf=SDL_CreateRGBSurface(SDL_SWSURFACE,list->width,74,32,RMASK,GMASK,BMASK,AMASK);
//Check if there's an icon for the addon.
if((*addons)[i].icon){
applySurface(5,5,(*addons)[i].icon,surf,NULL);
}else{
if(type=="levels")
applySurface(5,5,addonIcon[0],surf,NULL);
else if(type=="levelpacks")
applySurface(5,5,addonIcon[1],surf,NULL);
else
applySurface(5,5,addonIcon[2],surf,NULL);
}
SDL_Color black={0,0,0,0};
SDL_Surface* nameSurf=TTF_RenderUTF8_Blended(fontGUI,(*addons)[i].name.c_str(),black);
SDL_SetAlpha(nameSurf,0,0xFF);
applySurface(74,-1,nameSurf,surf,NULL);
SDL_FreeSurface(nameSurf);
/// TRANSLATORS: indicates the author of an addon.
string authorLine = tfm::format(_("by %s"),(*addons)[i].author);
SDL_Surface* authorSurf=TTF_RenderUTF8_Blended(fontText,authorLine.c_str(),black);
SDL_SetAlpha(authorSurf,0,0xFF);
applySurface(74,43,authorSurf,surf,NULL);
SDL_FreeSurface(authorSurf);
if((*addons)[i].installed){
if((*addons)[i].upToDate){
SDL_Surface* infoSurf=TTF_RenderUTF8_Blended(fontText,_("Installed"),black);
SDL_SetAlpha(infoSurf,0,0xFF);
applySurface(surf->w-infoSurf->w-32,(surf->h-infoSurf->h)/2,infoSurf,surf,NULL);
SDL_FreeSurface(infoSurf);
}else{
SDL_Surface* infoSurf=TTF_RenderUTF8_Blended(fontText,_("Updatable"),black);
SDL_SetAlpha(infoSurf,0,0xFF);
applySurface(surf->w-infoSurf->w-32,(surf->h-infoSurf->h)/2,infoSurf,surf,NULL);
SDL_FreeSurface(infoSurf);
}
}else{
SDL_Color grey={127,127,127};
SDL_Surface* infoSurf=TTF_RenderUTF8_Blended(fontText,_("Not installed"),grey);
SDL_SetAlpha(infoSurf,0,0xFF);
applySurface(surf->w-infoSurf->w-32,(surf->h-infoSurf->h)/2,infoSurf,surf,NULL);
SDL_FreeSurface(infoSurf);
}
list->addItem(entry,surf);
}
}
}
bool Addons::saveInstalledAddons(){
if(!addons) return false;
//Open the file.
ofstream iaddons;
iaddons.open((getUserPath(USER_CONFIG)+"installed_addons").c_str());
if(!iaddons) return false;
//Loop all the levels.
TreeStorageNode installed;
for(unsigned int i=0;i<addons->size();i++) {
//Check if the level is installed or not.
if((*addons)[i].installed) {
TreeStorageNode *entry=new TreeStorageNode;
entry->name="entry";
entry->value.push_back((*addons)[i].type);
entry->value.push_back((*addons)[i].name);
char version[64];
sprintf(version,"%d",(*addons)[i].installedVersion);
entry->value.push_back(version);
installed.subNodes.push_back(entry);
}
}
//And write away the file.
POASerializer objSerializer;
objSerializer.writeNode(&installed,iaddons,true,true);
return true;
}
SDL_Surface* Addons::loadCachedImage(const char* url,const char* md5sum){
//Check if the image is cached.
string imageFile=getUserPath(USER_CACHE)+"images/"+md5sum;
if(fileExists(imageFile.c_str())){
//It is, so load the image.
return loadImage(imageFile);
}else{
//Download the image.
FILE* file=fopen(imageFile.c_str(),"wb");
//Downloading failed.
if(!downloadFile(url,file)){
cerr<<"ERROR: Unable to download image from "<<url<<endl;
fclose(file);
return NULL;
}
fclose(file);
//Load the image.
return loadImage(imageFile);
}
}
void Addons::handleEvents(){
//Check if we should quit.
if(event.type==SDL_QUIT){
//Save the installed addons before exiting.
saveInstalledAddons();
setNextState(STATE_EXIT);
}
//Check if escape is pressed, if so return to the main menu.
if(inputMgr.isKeyUpEvent(INPUTMGR_ESCAPE)){
setNextState(STATE_MENU);
}
}
void Addons::logic(){}
void Addons::render(){
//Draw background.
objThemes.getBackground(true)->draw(screen);
//Draw the title.
applySurface((SCREEN_WIDTH-title->w)/2,40-TITLE_FONT_RAISE,title,screen,NULL);
}
void Addons::resize(){
//Delete the gui (if any).
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Now create a new one.
createGUI();
}
void Addons::showAddon(){
//Make sure an addon is selected.
if(!selected)
return;
//Create a root object.
- GUIObject* root=new GUIObject((SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-400)/2,600,400,GUIObjectFrame,selected->name.c_str());
+ GUIObject* root=new GUIFrame((SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-400)/2,600,400,selected->name.c_str());
//Create the 'by creator' label.
- GUIObject* obj=new GUIObject(0,50,600,50,GUIObjectLabel,tfm::format(_("by %s"),selected->author).c_str(),0,true,true,GUIGravityCenter);
+ GUIObject* obj=new GUILabel(0,50,600,50,tfm::format(_("by %s"),selected->author).c_str(),0,true,true,GUIGravityCenter);
root->addChild(obj);
//Create the description text.
GUITextArea* description=new GUITextArea(10,100,370,200);
description->setString(selected->description.c_str());
description->editable=false;
description->resize();
root->addChild(description);
//Create the screenshot image.
- obj=new GUIObject(390,100,200,150,GUIObjectImage);
- obj->setImage(selected->screenshot?selected->screenshot:screenshot);
- root->addChild(obj);
+ GUIImage* img=new GUIImage(390,100,200,150);
+ img->setImage(selected->screenshot?selected->screenshot:screenshot);
+ root->addChild(img);
//Add buttons depending on the installed/update status.
if(selected->installed && !selected->upToDate){
- GUIObject* bRemove=new GUIObject(root->width*0.97,350,-1,32,GUIObjectButton,_("Remove"),0,true,true,GUIGravityRight);
+ GUIObject* bRemove=new GUIButton(root->width*0.97,350,-1,32,_("Remove"),0,true,true,GUIGravityRight);
bRemove->name="cmdRemove";
bRemove->eventCallback=this;
root->addChild(bRemove);
//Create a back button.
- GUIObject* bBack=new GUIObject(root->width*0.03,350,-1,32,GUIObjectButton,_("Back"),0,true,true,GUIGravityLeft);
+ GUIObject* bBack=new GUIButton(root->width*0.03,350,-1,32,_("Back"),0,true,true,GUIGravityLeft);
bBack->name="cmdCloseOverlay";
bBack->eventCallback=this;
root->addChild(bBack);
//Update widget sizes.
root->render(0,0,false);
//Create a nicely centered button.
- obj=new GUIObject((int)floor((bBack->left+bBack->width+bRemove->left-bRemove->width)*0.5),350,-1,32,GUIObjectButton,_("Update"),0,true,true,GUIGravityCenter);
+ obj=new GUIButton((int)floor((bBack->left+bBack->width+bRemove->left-bRemove->width)*0.5),350,-1,32,_("Update"),0,true,true,GUIGravityCenter);
obj->name="cmdUpdate";
obj->eventCallback=this;
root->addChild(obj);
}else{
if(!selected->installed){
- obj=new GUIObject(root->width*0.9,350,-1,32,GUIObjectButton,_("Install"),0,true,true,GUIGravityRight);
+ obj=new GUIButton(root->width*0.9,350,-1,32,_("Install"),0,true,true,GUIGravityRight);
obj->name="cmdInstall";
obj->eventCallback=this;
root->addChild(obj);
}else if(selected->upToDate){
- obj=new GUIObject(root->width*0.9,350,-1,32,GUIObjectButton,_("Remove"),0,true,true,GUIGravityRight);
+ obj=new GUIButton(root->width*0.9,350,-1,32,_("Remove"),0,true,true,GUIGravityRight);
obj->name="cmdRemove";
obj->eventCallback=this;
root->addChild(obj);
}
//Create a back button.
- obj=new GUIObject(root->width*0.1,350,-1,32,GUIObjectButton,_("Back"),0,true,true,GUIGravityLeft);
+ obj=new GUIButton(root->width*0.1,350,-1,32,_("Back"),0,true,true,GUIGravityLeft);
obj->name="cmdCloseOverlay";
obj->eventCallback=this;
root->addChild(obj);
}
new GUIOverlay(root);
}
void Addons::GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType){
if(name=="lstTabs"){
if(obj->value==0){
addonsToList("levels");
type="levels";
}else if(obj->value==1){
addonsToList("levelpacks");
type="levelpacks";
}else{
addonsToList("themes");
type="themes";
}
list->value=0;
GUIEventCallback_OnEvent("lstAddons",list,GUIEventChange);
}else if(name=="lstAddons"){
//Check which type of event.
if(eventType==GUIEventChange){
//Get the addon struct that belongs to it.
Addon* addon=NULL;
if(!list->item.empty()) {
string entry = list->getItem(list->value);
for(unsigned int i=0;i<addons->size();i++) {
std::string prefix=(*addons)[i].name;
if(!entry.compare(0, prefix.size(), prefix)) {
addon=&(*addons)[i];
}
}
}
selected=addon;
list->value=-1;
}else if(eventType==GUIEventClick){
//Make sure an addon is selected.
if(selected){
showAddon();
}
}
}else if(name=="cmdBack"){
saveInstalledAddons();
setNextState(STATE_MENU);
}else if(name=="cmdCloseOverlay"){
//We can safely delete the GUIObjectRoot, since it's handled by the GUIOverlay.
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}else if(name=="cmdUpdate"){
//First remove the addon and then install it again.
if(type.compare("levels")==0) {
if(downloadFile(selected->file,(getUserPath(USER_DATA)+"/levels/"))!=false){
selected->upToDate=true;
selected->installedVersion=selected->version;
addonsToList("levels");
}else{
cerr<<"ERROR: Unable to download addon!"<<endl;
msgBox(_("ERROR: Unable to download addon!"),MsgBoxOKOnly,_("ERROR:"));
return;
}
}else if(type.compare("levelpacks")==0) {
if(!removeDirectory((getUserPath(USER_DATA)+"levelpacks/"+selected->folder+"/").c_str())){
cerr<<"ERROR: Unable to remove the directory "<<(getUserPath(USER_DATA)+"levelpacks/"+selected->folder+"/")<<"."<<endl;
return;
}
if(downloadFile(selected->file,(getUserPath(USER_CACHE)+"/tmp/"))!=false){
extractFile(getUserPath(USER_CACHE)+"/tmp/"+fileNameFromPath(selected->file,true),getUserPath(USER_DATA)+"/levelpacks/"+selected->folder+"/");
selected->upToDate=true;
selected->installedVersion=selected->version;
addonsToList("levelpacks");
}else{
cerr<<"ERROR: Unable to download addon!"<<endl;
msgBox(_("ERROR: Unable to download addon!"),MsgBoxOKOnly,_("ERROR:"));
return;
}
}else if(type.compare("themes")==0) {
if(!removeDirectory((getUserPath(USER_DATA)+"themes/"+selected->folder+"/").c_str())){
cerr<<"ERROR: Unable to remove the directory "<<(getUserPath(USER_DATA)+"themes/"+selected->folder+"/")<<"."<<endl;
return;
}
if(downloadFile(selected->file,(getUserPath(USER_CACHE)+"/tmp/"))!=false){
extractFile((getUserPath(USER_CACHE)+"/tmp/"+fileNameFromPath(selected->file,true)),(getUserPath(USER_DATA)+"/themes/"+selected->folder+"/"));
selected->upToDate=true;
selected->installedVersion=selected->version;
addonsToList("themes");
}else{
cerr<<"ERROR: Unable to download addon!"<<endl;
msgBox(_("ERROR: Unable to download addon!"),MsgBoxOKOnly,_("ERROR:"));
return;
}
}
}else if(name=="cmdInstall"){
//Download the addon.
if(type.compare("levels")==0) {
if(downloadFile(selected->file,getUserPath(USER_DATA)+"/levels/")!=false){
selected->upToDate=true;
selected->installed=true;
selected->installedVersion=selected->version;
addonsToList("levels");
//And add the level to the levels levelpack.
LevelPack* levelsPack=getLevelPackManager()->getLevelPack("Levels");
levelsPack->addLevel(getUserPath(USER_DATA)+"/levels/"+fileNameFromPath(selected->file));
levelsPack->setLocked(levelsPack->getLevelCount()-1);
}else{
cerr<<"ERROR: Unable to download addon!"<<endl;
msgBox(_("ERROR: Unable to download addon!"),MsgBoxOKOnly,_("ERROR:"));
return;
}
}else if(type.compare("levelpacks")==0) {
if(downloadFile(selected->file,getUserPath(USER_CACHE)+"/tmp/")!=false){
extractFile(getUserPath(USER_CACHE)+"/tmp/"+fileNameFromPath(selected->file,true),getUserPath(USER_DATA)+"/levelpacks/"+selected->folder+"/");
selected->upToDate=true;
selected->installed=true;
selected->installedVersion=selected->version;
addonsToList("levelpacks");
//And add the levelpack to the levelpackManager.
getLevelPackManager()->loadLevelPack(getUserPath(USER_DATA)+"/levelpacks/"+selected->folder);
}else{
cerr<<"ERROR: Unable to download addon!"<<endl;
msgBox(_("ERROR: Unable to download addon!"),MsgBoxOKOnly,_("ERROR:"));
return;
}
}else if(type.compare("themes")==0) {
if(downloadFile(selected->file,getUserPath(USER_CACHE)+"/tmp/")!=false){
extractFile(getUserPath(USER_CACHE)+"/tmp/"+fileNameFromPath(selected->file,true),getUserPath(USER_DATA)+"/themes/"+selected->folder+"/");
selected->upToDate=true;
selected->installed=true;
selected->installedVersion=selected->version;
addonsToList("themes");
}else{
cerr<<"ERROR: Unable to download addon!"<<endl;
msgBox(_("ERROR: Unable to download addon!"),MsgBoxOKOnly,_("ERROR:"));
return;
}
}
}else if(name=="cmdRemove"){
//Uninstall the addon.
if(type.compare("levels")==0) {
if(remove((getUserPath(USER_DATA)+"levels/"+fileNameFromPath(selected->file)).c_str())){
cerr<<"ERROR: Unable to remove the file "<<(getUserPath(USER_DATA) + "levels/" + fileNameFromPath(selected->file))<<"."<<endl;
return;
}
selected->upToDate=false;
selected->installed=false;
addonsToList("levels");
//And remove the level from the levels levelpack.
LevelPack* levelsPack=getLevelPackManager()->getLevelPack("Levels");
for(int i=0;i<levelsPack->getLevelCount();i++){
if(levelsPack->getLevelFile(i)==(getUserPath(USER_DATA)+"levels/"+fileNameFromPath(selected->file))){
//Remove the level and break out of the loop.
levelsPack->removeLevel(i);
break;
}
}
}else if(type.compare("levelpacks")==0) {
if(!removeDirectory((getUserPath(USER_DATA)+"levelpacks/"+selected->folder+"/").c_str())){
cerr<<"ERROR: Unable to remove the directory "<<(getUserPath(USER_DATA)+"levelpacks/"+selected->folder+"/")<<"."<<endl;
return;
}
selected->upToDate=false;
selected->installed=false;
addonsToList("levelpacks");
//And remove the levelpack from the levelpack manager.
getLevelPackManager()->removeLevelPack(selected->folder);
}else if(type.compare("themes")==0) {
if(!removeDirectory((getUserPath(USER_DATA)+"themes/"+selected->folder+"/").c_str())){
cerr<<"ERROR: Unable to remove the directory "<<(getUserPath(USER_DATA)+"themes/"+selected->folder+"/")<<"."<<endl;
return;
}
selected->upToDate=false;
selected->installed=false;
addonsToList("themes");
}
}
//NOTE: In case of install/remove/update we can delete the GUIObjectRoot, since it's managed by the GUIOverlay.
if(name=="cmdUpdate" || name=="cmdInstall" || name=="cmdRemove"){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
diff --git a/src/Functions.cpp b/src/Functions.cpp
index d950b92..13b532b 100644
--- a/src/Functions.cpp
+++ b/src/Functions.cpp
@@ -1,1784 +1,1784 @@
/*
* 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 <stdio.h>
#include <math.h>
#include <locale.h>
#include <algorithm>
#include <SDL/SDL.h>
#ifdef __APPLE__
#include <SDL_mixer/SDL_mixer.h>
#include <SDL_gfx/SDL_gfxPrimitives.h>
#include <SDL_gfx/SDL_rotozoom.h>
#else
#include <SDL/SDL_mixer.h>
#include <SDL/SDL_gfxPrimitives.h>
#include <SDL/SDL_rotozoom.h>
#endif
#include <SDL/SDL_syswm.h>
#include <string>
#include "Globals.h"
#include "Functions.h"
#include "FileManager.h"
#include "Objects.h"
#include "Player.h"
#include "GameObjects.h"
#include "LevelPack.h"
#include "TitleMenu.h"
#include "LevelEditSelect.h"
#include "LevelEditor.h"
#include "Game.h"
#include "LevelPlaySelect.h"
#include "Addons.h"
#include "InputManager.h"
#include "ImageManager.h"
#include "MusicManager.h"
#include "LevelPackManager.h"
#include "ThemeManager.h"
#include "GUIListBox.h"
#include "GUIOverlay.h"
#include "StatisticsManager.h"
#include "StatisticsScreen.h"
#include "Cursors.h"
#include "ScriptAPI.h"
#include "libs/tinyformat/tinyformat.h"
#include "libs/tinygettext/tinygettext.hpp"
#include "libs/tinygettext/log.hpp"
#include "libs/findlocale/findlocale.h"
#ifdef HARDWARE_ACCELERATION
#include <GL/gl.h>
#include <GL/glu.h>
//fix some Windows header bug
#ifndef GL_BGR
#define GL_BGR GL_BGR_EXT
#endif
#ifndef GL_BGRA
#define GL_BGRA GL_BGRA_EXT
#endif
#endif
using namespace std;
#ifdef WIN32
#include <windows.h>
#include <shlobj.h>
#else
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>
#endif
//Workaround for the resizing below 800x600 for X systems.
#if defined(__linux__) && !defined(ANDROID)
#include<X11/Xlib.h>
#include<X11/Xutil.h>
#define __X11_INCLUDED__
#endif
//Initialise the imagemanager.
//The ImageManager is used to prevent loading images multiple times.
ImageManager imageManager;
//Initialise the musicManager.
//The MusicManager is used to prevent loading music files multiple times and for playing/fading music.
MusicManager musicManager;
//Initialise the levelPackManager.
//The LevelPackManager is used to prevent loading levelpacks multiple times and for the game to know which levelpacks there are.
LevelPackManager levelPackManager;
//The scriptExecutor used for executing scripts.
ScriptExecutor scriptExecutor;
//Map containing changed settings using command line arguments.
map<string,string> tmpSettings;
//Pointer to the settings object.
//It is used to load and save the settings file and change the settings.
Settings* settings=0;
#ifdef HARDWARE_ACCELERATION
GLuint screenTexture;
#endif
SDL_Surface* loadImage(string file){
//We use the imageManager to load the file.
return imageManager.loadImage(file);
}
void applySurface(int x,int y,SDL_Surface* source,SDL_Surface* dest,SDL_Rect* clip){
//The offset is needed to draw at the right location.
SDL_Rect offset;
offset.x=x;
offset.y=y;
//Let SDL do the drawing of the surface.
SDL_BlitSurface(source,clip,dest,&offset);
}
void drawRect(int x,int y,int w,int h,SDL_Surface* dest,Uint32 color){
//NOTE: We let SDL_gfx render it.
rectangleRGBA(dest,x,y,x+w,y+h,color >> 24,color >> 16,color >> 8,255);
}
//Draw a box with anti-aliased borders using SDL_gfx.
void drawGUIBox(int x,int y,int w,int h,SDL_Surface* dest,Uint32 color){
//Fill content's background color from function parameter
boxRGBA(dest,x+1,y+1,x+w-2,y+h-2,color >> 24,color >> 16,color >> 8,color >> 0);
//Draw first black borders around content and leave 1 pixel in every corner
lineRGBA(dest,x+1,y,x+w-2,y,0,0,0,255);
lineRGBA(dest,x+1,y+h-1,x+w-2,y+h-1,0,0,0,255);
lineRGBA(dest,x,y+1,x,y+h-2,0,0,0,255);
lineRGBA(dest,x+w-1,y+1,x+w-1,y+h-2,0,0,0,255);
//Fill the corners with transperent color to create anti-aliased borders
pixelRGBA(dest,x,y,0,0,0,160);
pixelRGBA(dest,x,y+h-1,0,0,0,160);
pixelRGBA(dest,x+w-1,y,0,0,0,160);
pixelRGBA(dest,x+w-1,y+h-1,0,0,0,160);
//Draw second lighter border around content
rectangleRGBA(dest,x+1,y+1,x+w-2,y+h-2,0,0,0,64);
//Create anti-aliasing in corners of second border
pixelRGBA(dest,x+1,y+1,0,0,0,50);
pixelRGBA(dest,x+1,y+h-2,0,0,0,50);
pixelRGBA(dest,x+w-2,y+1,0,0,0,50);
pixelRGBA(dest,x+w-2,y+h-2,0,0,0,50);
}
void drawLine(int x1,int y1,int x2,int y2,SDL_Surface* dest,Uint32 color){
//NOTE: We let SDL_gfx render it.
lineRGBA(dest,x1,y1,x2,y2,color >> 24,color >> 16,color >> 8,255);
}
void drawLineWithArrow(int x1,int y1,int x2,int y2,SDL_Surface* dest,Uint32 color,int spacing,int offset,int xsize,int ysize){
//Draw line first
drawLine(x1,y1,x2,y2,dest,color);
//calc delta and length
double dx=x2-x1;
double dy=y2-y1;
double length=sqrt(dx*dx+dy*dy);
if(length<0.001) return;
//calc the unit vector
dx/=length; dy/=length;
//Now draw arrows on it
for(double p=offset;p<length;p+=spacing){
drawLine(int(x1+p*dx+0.5),int(y1+p*dy+0.5),
int(x1+(p-xsize)*dx-ysize*dy+0.5),int(y1+(p-xsize)*dy+ysize*dx+0.5),dest,color);
drawLine(int(x1+p*dx+0.5),int(y1+p*dy+0.5),
int(x1+(p-xsize)*dx+ysize*dy+0.5),int(y1+(p-xsize)*dy-ysize*dx+0.5),dest,color);
}
}
bool createScreen(){
//Check if we are going fullscreen.
if(settings->getBoolValue("fullscreen"))
pickFullscreenResolution();
//Set the screen_width and height.
SCREEN_WIDTH=atoi(settings->getValue("width").c_str());
SCREEN_HEIGHT=atoi(settings->getValue("height").c_str());
//Update the camera.
camera.w=SCREEN_WIDTH;
camera.h=SCREEN_HEIGHT;
//Boolean if this is the first screen creation.
bool initial=true;
//Check if we should use gl or software rendering.
if(settings->getBoolValue("gl")){
#ifdef HARDWARE_ACCELERATION
SDL_GL_SetAttribute(SDL_GL_RED_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,16);
SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE,32);
SDL_GL_SetAttribute(SDL_GL_ACCUM_RED_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_ACCUM_GREEN_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_ACCUM_BLUE_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_ACCUM_ALPHA_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS,0);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES,0);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,1);
//Set the video mode.
Uint32 flags=SDL_HWSURFACE | SDL_OPENGL;
if(settings->getBoolValue("fullscreen"))
flags|=SDL_FULLSCREEN;
else if(settings->getBoolValue("resizable"))
flags|=SDL_RESIZABLE;
if(SDL_SetVideoMode(SCREEN_WIDTH,SCREEN_HEIGHT,SCREEN_BPP,flags)==NULL){
fprintf(stderr,"FATAL ERROR: SDL_SetVideoMode failed\n");
return false;
}
//Delete the old screen.
//Warning: only if previous mode is OpenGL mode.
//NOTE: The previous mode can't switch during runtime.
if(screen){
SDL_FreeSurface(screen);
screen=NULL;
//There was a screen so this isn't the initial screen creation.
initial=false;
}
//Create a screen
screen=SDL_CreateRGBSurface(SDL_HWSURFACE,SCREEN_WIDTH,SCREEN_HEIGHT,32,0x00FF0000,0x0000FF00,0x000000FF,0);
//Create a texture.
glDeleteTextures(1,&screenTexture);
glGenTextures(1,&screenTexture);
//And set up gl correctly.
glClearColor(0, 0, 0, 0);
glClearDepth(1.0f);
glViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 1, -1);
glMatrixMode(GL_MODELVIEW);
glEnable(GL_TEXTURE_2D);
glLoadIdentity();
#else
//NOTE: Hardware accelerated rendering requested but compiled without.
cerr<<"FATAL ERROR: Unable to use hardware acceleration (compiled without)."<<endl;
return false;
#endif
}else{
//Set the flags.
Uint32 flags=SCREEN_FLAGS;
#if !defined(ANDROID)
flags |= SDL_DOUBLEBUF;
#endif
if(settings->getBoolValue("fullscreen"))
flags|=SDL_FULLSCREEN;
else if(settings->getBoolValue("resizable"))
flags|=SDL_RESIZABLE;
//Check if there already was a screen.
if(screen)
initial=false;
//Create the screen and check if there weren't any errors.
screen=SDL_SetVideoMode(SCREEN_WIDTH,SCREEN_HEIGHT,SCREEN_BPP,flags);
if(screen==NULL){
fprintf(stderr,"FATAL ERROR: SDL_SetVideoMode failed\n");
return false;
}
}
//Now configure the newly created window (if windowed).
if(settings->getBoolValue("fullscreen")==false)
configureWindow(initial);
//Create the temp surface, just a replica of the screen surface, free the previous one if any.
if(tempSurface)
SDL_FreeSurface(tempSurface);
tempSurface=SDL_CreateRGBSurface(SCREEN_FLAGS|SDL_SRCALPHA,
screen->w,screen->h,screen->format->BitsPerPixel,
screen->format->Rmask,screen->format->Gmask,screen->format->Bmask,0);
//Set the the window caption.
SDL_WM_SetCaption(("Me and My Shadow "+version).c_str(),NULL);
SDL_EnableUNICODE(1);
//Nothing went wrong so return true.
return true;
}
//Workaround for the resizing below 800x600 for Windows.
#ifdef WIN32
static WNDPROC m_OldWindowProc=NULL;
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam){
if(msg==WM_GETMINMAXINFO){
if(m_OldWindowProc){
CallWindowProc(m_OldWindowProc,hwnd,msg,wParam,lParam);
}else{
DefWindowProc(hwnd,msg,wParam,lParam);
}
RECT r={0,0,800,600};
AdjustWindowRect(&r,GetWindowLong(hwnd,GWL_STYLE),FALSE);
MINMAXINFO *info=(MINMAXINFO*)lParam;
info->ptMinTrackSize.x=r.right-r.left;
info->ptMinTrackSize.y=r.bottom-r.top;
return 0;
}else{
if(m_OldWindowProc){
return CallWindowProc(m_OldWindowProc,hwnd,msg,wParam,lParam);
}else{
return DefWindowProc(hwnd,msg,wParam,lParam);
}
}
}
#endif
void pickFullscreenResolution(){
//Vector that will hold the resolutions to choose from.
vector<_res> resolutionList;
//Enumerate available resolutions using SDL_ListModes()
//Note: we enumerate fullscreen resolutions because
// windowed resolutions always can be arbitrary
if(resolutionList.empty()){
SDL_Rect **modes=SDL_ListModes(NULL,SDL_FULLSCREEN|SCREEN_FLAGS|SDL_ANYFORMAT);
if(modes==NULL || ((intptr_t)modes) == -1){
cout<<"Error: Can't enumerate available screen resolutions."
" Use predefined screen resolutions list instead."<<endl;
static const _res predefinedResolutionList[] = {
{800,600},
{1024,600},
{1024,768},
{1152,864},
{1280,720},
{1280,768},
{1280,800},
{1280,960},
{1280,1024},
{1360,768},
{1366,768},
{1440,900},
{1600,900},
{1600,1200},
{1680,1080},
{1920,1080},
{1920,1200},
{2560,1440},
{3840,2160}
};
//Fill the resolutionList.
for(unsigned int i=0;i<sizeof(predefinedResolutionList)/sizeof(_res);i++){
resolutionList.push_back(predefinedResolutionList[i]);
}
}else{
//Fill the resolutionList.
for(unsigned int i=0;modes[i]!=NULL;i++){
//Check if the resolution is higher than the minimum (800x600).
if(modes[i]->w>=800 && modes[i]->h>=600){
_res res={modes[i]->w, modes[i]->h};
resolutionList.push_back(res);
}
}
//Reverse it so that we begin with the lowest resolution.
reverse(resolutionList.begin(),resolutionList.end());
}
}
//The resolution that will hold the final result, we start with the minimum (800x600).
_res closestMatch={800,600};
int width=atoi(getSettings()->getValue("width").c_str());
//int height=atoi(getSettings()->getValue("height").c_str());
//Now loop through the resolutionList.
for(int i=0;i<(int)resolutionList.size();i++){
//The delta between the closestMatch and the resolution from the list.
int dM=(closestMatch.w-resolutionList[i].w);
//The delta between the target width and the resolution from the list.
int dT=(width-resolutionList[i].w);
//Since the resolutions are getting higher the lower (more negative) the further away it is.
//That's why we check if the deltaMatch is lower than the the deltaTarget.
if((dM)<(dT)){
closestMatch.w=resolutionList[i].w;
closestMatch.h=resolutionList[i].h;
}
}
//Now set the resolution to the closest match.
char s[64];
sprintf(s,"%d",closestMatch.w);
getSettings()->setValue("width",s);
sprintf(s,"%d",closestMatch.h);
getSettings()->setValue("height",s);
}
#ifdef __X11_INCLUDED__
int handleXError(Display* disp,XErrorEvent* event){
//NOTE: This is UNTESTED code, there are still some things that should be tested/changed.
//NOTE: It checks against hardcoded opcodes, this should be based on included defines from the xf86vid headers instead.
//NOTE: This code assumes Xlib is in use, just like the resize restriction code for Linux.
//Print out the error message as normal.
char output[256];
XGetErrorText(disp,event->error_code,output,256);
cerr<<output<<endl;
//Check if the game is fullscreen.
if(getSettings()->getBoolValue("fullscreen")){
//Check for the exact error we want to handle differently.
if(event->error_code==BadValue && event->minor_code==10/*X_XF86VidModeSwitchToMode*/){
//The cause of this problem has likely something to do with fullscreen mode, so fallback to windowed.
cerr<<"ERROR: Xlib error code "<<event->error_code<<", request code "<<event->request_code<<"."<<endl;
cerr<<"ERROR: Falling back to windowed mode!"<<endl;
getSettings()->setValue("fullscreen","false");
createScreen();
return 0;
}
}
//Do the normal Xlib behaviour.
exit(1);
return 0;
}
#endif
void configureWindow(bool initial){
//We only need to configure the window if it's resizable.
if(!getSettings()->getBoolValue("resizable"))
return;
//Retrieve the WM info from SDL containing the window handle.
struct SDL_SysWMinfo wmInfo;
SDL_VERSION(&wmInfo.version);
SDL_GetWMInfo(&wmInfo);
#ifdef __X11_INCLUDED__
//We assume that a linux system running meandmyshadow is also running an Xorg server.
if(wmInfo.subsystem==SDL_SYSWM_X11){
//Create the size hints to give to the window.
XSizeHints* sizeHints;
if(!(sizeHints=XAllocSizeHints())){
cerr<<"ERROR: Unable to allocate memory for XSizeHings."<<endl;
return;
}
//Configure the size hint.
sizeHints->flags=PMinSize;
sizeHints->min_width=800;
sizeHints->min_height=600;
//Set the normal hints of the window.
(void)wmInfo.info.x11.lock_func;
XSetNormalHints(wmInfo.info.x11.display,wmInfo.info.x11.wmwindow,sizeHints);
(void)wmInfo.info.x11.unlock_func;
//Free size hint structure
XFree(sizeHints);
}else{
//No X11 so an unsupported window manager.
cerr<<"WARNING: Unsupported window manager."<<endl;
}
#elif defined(WIN32)
//We overwrite the window proc of SDL
WNDPROC wndproc=(WNDPROC)GetWindowLong(wmInfo.window,GWL_WNDPROC);
if(wndproc!=NULL && wndproc!=(WNDPROC)WindowProc){
m_OldWindowProc=wndproc;
SetWindowLong(wmInfo.window,GWL_WNDPROC,(LONG)(WNDPROC)WindowProc);
}
#endif
}
void onVideoResize(){
//Check if the resize event isn't malformed.
if(event.resize.w<=0 || event.resize.h<=0)
return;
//Check the size limit.
if(event.resize.w<800)
event.resize.w=800;
if(event.resize.h<600)
event.resize.h=600;
//Check if it really resizes.
if(SCREEN_WIDTH==event.resize.w && SCREEN_HEIGHT==event.resize.h)
return;
char s[32];
//Set the new width and height.
sprintf(s,"%d",event.resize.w);
getSettings()->setValue("width",s);
sprintf(s,"%d",event.resize.h);
getSettings()->setValue("height",s);
//Do resizing.
if(!createScreen())
return;
//Tell the theme to resize.
if(!loadTheme(""))
return;
//The new resolution is valid.
//Now we can save the settings. (TODO: should we save?)
//saveSettings();
//And let the currentState update it's GUI to the new resolution.
currentState->resize();
}
bool init(){
//Initialze SDL.
if(SDL_Init(SDL_INIT_EVERYTHING)==-1) {
fprintf(stderr,"FATAL ERROR: SDL_Init failed\n");
return false;
}
//Initialze SDL_mixer (audio).
if(Mix_OpenAudio(22050,MIX_DEFAULT_FORMAT,2,512)==-1){
fprintf(stderr,"FATAL ERROR: Mix_OpenAudio failed\n");
return false;
}
//Set the volume.
Mix_Volume(-1,atoi(settings->getValue("sound").c_str()));
//Initialze SDL_ttf (fonts).
if(TTF_Init()==-1){
fprintf(stderr,"FATAL ERROR: TTF_Init failed\n");
return false;
}
#ifdef __X11_INCLUDED__
//Before creating the screen set the XErrorHandler in case of X11.
XSetErrorHandler(handleXError);
#endif
//Create the screen.
if(!createScreen())
return false;
//Load key config. Then initialize joystick support.
inputMgr.loadConfig();
inputMgr.openAllJoysitcks();
//Init tinygettext for translations for the right language
dictionaryManager = new tinygettext::DictionaryManager();
dictionaryManager->add_directory(getDataPath()+"locale");
dictionaryManager->set_charset("UTF-8");
//Check if user have defined own language. If not, find it out for the player using findlocale
string lang=getSettings()->getValue("lang");
if(lang.length()>0){
printf("Locale set by user to %s\n",lang.c_str());
language=lang;
}else{
FL_Locale *locale;
FL_FindLocale(&locale,FL_MESSAGES);
printf("Locale isn't set by user: %s\n",locale->lang);
language=locale->lang;
if(locale->country!=NULL){
language+=string("_")+string(locale->country);
}
if(locale->variant!=NULL){
language+=string("@")+string(locale->variant);
}
FL_FreeLocale(&locale);
}
//Now set the language in the dictionaryManager.
dictionaryManager->set_language(tinygettext::Language::from_name(language));
//Disable annoying 'Couldn't translate: blah blah blah'
tinygettext::Log::set_log_info_callback(NULL);
//Set time format to the user-preference of the system.
setlocale(LC_TIME,"");
//Create the types of blocks.
for(int i=0;i<TYPE_MAX;i++){
Game::blockNameMap[Game::blockName[i]]=i;
}
//Structure that holds the event type/name pair.
struct EventTypeName{
int type;
const char* name;
};
//Create the types of game object event types.
{
const EventTypeName types[]={
{GameObjectEvent_PlayerWalkOn,"playerWalkOn"},
{GameObjectEvent_PlayerIsOn,"playerIsOn"},
{GameObjectEvent_PlayerLeave,"playerLeave"},
{GameObjectEvent_OnCreate,"onCreate"},
{GameObjectEvent_OnEnterFrame,"onEnterFrame"},
{GameObjectEvent_OnToggle,"onToggle"},
{GameObjectEvent_OnSwitchOn,"onSwitchOn"},
{GameObjectEvent_OnSwitchOff,"onSwitchOff"},
{0,NULL}
};
for(int i=0;types[i].name;i++){
Game::gameObjectEventNameMap[types[i].name]=types[i].type;
Game::gameObjectEventTypeMap[types[i].type]=types[i].name;
}
}
//Create the types of level event types.
{
const EventTypeName types[]={
{LevelEvent_OnCreate,"onCreate"},
{LevelEvent_OnSave,"onSave"},
{LevelEvent_OnLoad,"onLoad"},
{LevelEvent_OnReset,"onReset"},
{0,NULL}
};
for(int i=0;types[i].name;i++){
Game::levelEventNameMap[types[i].name]=types[i].type;
Game::levelEventTypeMap[types[i].type]=types[i].name;
}
}
//Register the ScriptAPI's functions in the scriptExecutor.
registerFunctions(getScriptExecutor());
//Nothing went wrong so we return true.
return true;
}
static TTF_Font* loadFont(const char* name,int size){
TTF_Font* tmpFont=TTF_OpenFont((getDataPath()+"font/"+name+".ttf").c_str(),size);
if(tmpFont){
return tmpFont;
}else{
#if defined(ANDROID)
//Android has built-in DroidSansFallback.ttf. (?)
return TTF_OpenFont("/system/fonts/DroidSansFallback.ttf",size);
#else
return TTF_OpenFont((getDataPath()+"font/DroidSansFallback.ttf").c_str(),size);
#endif
}
}
bool loadFonts(){
//Load the fonts.
//NOTE: This is a separate method because it will be called separately when re-initing in case of language change.
//First close the fonts if needed.
if(fontTitle)
TTF_CloseFont(fontTitle);
if(fontGUI)
TTF_CloseFont(fontGUI);
if(fontGUISmall)
TTF_CloseFont(fontGUISmall);
if(fontText)
TTF_CloseFont(fontText);
/// TRANSLATORS: Font used in GUI:
/// - Use "knewave" for languages using Latin and Latin-derived alphabets
/// - "DroidSansFallback" can be used for non-Latin writing systems
fontTitle=loadFont(_("knewave"),55);
fontGUI=loadFont(_("knewave"),32);
fontGUISmall=loadFont(_("knewave"),24);
/// TRANSLATORS: Font used for normal text:
/// - Use "Blokletters-Viltstift" for languages using Latin and Latin-derived alphabets
/// - "DroidSansFallback" can be used for non-Latin writing systems
fontText=loadFont(_("Blokletters-Viltstift"),16);
if(fontTitle==NULL || fontGUI==NULL || fontGUISmall==NULL || fontText==NULL){
printf("ERROR: Unable to load fonts! \n");
return false;
}
//Nothing went wrong so return true.
return true;
}
//Generate small arrows used for some GUI widgets.
static void generateArrows(){
TTF_Font* fontArrow=loadFont(_("knewave"),18);
if(arrowLeft1){
SDL_FreeSurface(arrowLeft1);
SDL_FreeSurface(arrowRight1);
SDL_FreeSurface(arrowLeft2);
SDL_FreeSurface(arrowRight2);
}
arrowLeft1=TTF_RenderUTF8_Blended(fontArrow,"<",themeTextColor);
arrowRight1=TTF_RenderUTF8_Blended(fontArrow,">",themeTextColor);
arrowLeft2=TTF_RenderUTF8_Blended(fontArrow,"<",themeTextColorDialog);
arrowRight2=TTF_RenderUTF8_Blended(fontArrow,">",themeTextColorDialog);
TTF_CloseFont(fontArrow);
}
bool loadTheme(string name){
//Load default fallback theme if it isn't loaded yet
if(objThemes.themeCount()==0){
if(objThemes.appendThemeFromFile(getDataPath()+"themes/Cloudscape/theme.mnmstheme")==NULL){
printf("ERROR: Can't load default theme file\n");
return false;
}
}
//Resize background or load specific theme
bool success=true;
if(name==""||name.empty()){
objThemes.scaleToScreen();
}else{
string theme=processFileName(name);
if(objThemes.appendThemeFromFile(theme+"/theme.mnmstheme")==NULL){
printf("ERROR: Can't load theme %s\n",theme.c_str());
success=false;
}
}
generateArrows();
//Everything went fine so return true.
return success;
}
static Mix_Chunk* loadWAV(const char* s){
Mix_Chunk* c=Mix_LoadWAV(s);
if(c!=NULL) return c;
printf("ERROR: Can't load sound file %s: %s\n",s,SDL_GetError());
return NULL;
}
static SDL_Cursor* loadCursor(const char* image[]){
int i,row,col;
//The array that holds the data (0=white 1=black)
Uint8 data[4*32];
//The array that holds the alpha mask (0=transparent 1=visible)
Uint8 mask[4*32];
//The coordinates of the hotspot of the cursor.
int hotspotX, hotspotY;
i=-1;
//Loop through the rows and columns.
//NOTE: We assume a cursor size of 32x32.
for(row=0;row<32;++row){
for(col=0; col<32;++col){
if(col % 8) {
data[i]<<=1;
mask[i]<<=1;
}else{
++i;
data[i]=mask[i]=0;
}
switch(image[4+row][col]){
case '+':
data[i] |= 0x01;
mask[i] |= 0x01;
break;
case '.':
mask[i] |= 0x01;
break;
default:
break;
}
}
}
//Get the hotspot x and y locations from the last line of the cursor.
sscanf(image[4+row],"%d,%d",&hotspotX,&hotspotY);
return SDL_CreateCursor(data,mask,32,32,hotspotX,hotspotY);
}
bool loadFiles(){
//Load the fonts.
if(!loadFonts())
return false;
fontMono=loadFont("VeraMono",12);
//Show a loading screen
{
SDL_Rect r={0,0,screen->w,screen->h};
SDL_FillRect(screen,&r,0);
SDL_Color fg={255,255,255};
SDL_Surface *surface=TTF_RenderUTF8_Blended(fontTitle,_("Loading..."),fg);
if(surface!=NULL){
r.x=(screen->w-surface->w)/2;
r.y=(screen->h-surface->h)/2;
SDL_BlitSurface(surface,NULL,screen,&r);
SDL_FreeSurface(surface);
}
SDL_Flip(screen);
}
musicManager.destroy();
//Load the music and play it.
if(musicManager.loadMusic((getDataPath()+"music/menu.music")).empty()){
printf("WARNING: Unable to load background music! \n");
}
musicManager.playMusic("menu",false);
//Always load the default music list for fallback.
musicManager.loadMusicList((getDataPath()+"music/default.list"));
//Load the configured music list.
getMusicManager()->loadMusicList((getDataPath()+"music/"+getSettings()->getValue("musiclist")+".list"));
getMusicManager()->setMusicList(getSettings()->getValue("musiclist"));
//Check if music is enabled.
if(getSettings()->getBoolValue("music"))
getMusicManager()->setEnabled();
//Load the sound effects
jumpSound=loadWAV((getDataPath()+"sfx/jump.wav").c_str());
hitSound=loadWAV((getDataPath()+"sfx/hit.wav").c_str());
saveSound=loadWAV((getDataPath()+"sfx/checkpoint.wav").c_str());
swapSound=loadWAV((getDataPath()+"sfx/swap.wav").c_str());
toggleSound=loadWAV((getDataPath()+"sfx/toggle.wav").c_str());
errorSound=loadWAV((getDataPath()+"sfx/error.wav").c_str());
collectSound=loadWAV((getDataPath()+"sfx/collect.wav").c_str());
achievementSound=loadWAV((getDataPath()+"sfx/achievement.ogg").c_str());
//Load the cursor images from the Cursor.h file.
cursors[CURSOR_POINTER]=loadCursor(pointer);
cursors[CURSOR_CARROT]=loadCursor(ibeam);
cursors[CURSOR_DRAG]=loadCursor(closedhand);
cursors[CURSOR_SIZE_HOR]=loadCursor(size_hor);
cursors[CURSOR_SIZE_VER]=loadCursor(size_ver);
cursors[CURSOR_SIZE_FDIAG]=loadCursor(size_fdiag);
cursors[CURSOR_SIZE_BDIAG]=loadCursor(size_bdiag);
cursors[CURSOR_REMOVE]=loadCursor(remove_cursor);
//Set the default cursor right now.
SDL_SetCursor(cursors[CURSOR_POINTER]);
levelPackManager.destroy();
//Now sum up all the levelpacks.
vector<string> v=enumAllDirs(getDataPath()+"levelpacks/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelPackManager.loadLevelPack(getDataPath()+"levelpacks/"+*i);
}
v=enumAllDirs(getUserPath(USER_DATA)+"levelpacks/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelPackManager.loadLevelPack(getUserPath(USER_DATA)+"levelpacks/"+*i);
}
v=enumAllDirs(getUserPath(USER_DATA)+"custom/levelpacks/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelPackManager.loadLevelPack(getUserPath(USER_DATA)+"custom/levelpacks/"+*i);
}
//Now we add a special levelpack that will contain the levels not in a levelpack.
LevelPack* levelsPack=new LevelPack;
levelsPack->levelpackName="Levels";
levelsPack->levelpackPath="Levels";
//NOTE: Set the type of 'levels' to main so it won't be added to the custom packs, even though it contains non-main levels.
levelsPack->type=MAIN;
LevelPack* customLevelsPack=new LevelPack;
customLevelsPack->levelpackName="Custom Levels";
customLevelsPack->levelpackPath="Custom Levels";
//List the addon levels and add them one for one.
v=enumAllFiles(getUserPath(USER_DATA)+"levels/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelsPack->addLevel(getUserPath(USER_DATA)+"levels/"+*i);
levelsPack->setLocked(levelsPack->getLevelCount()-1);
}
//List the custom levels and add them one for one.
v=enumAllFiles(getUserPath(USER_DATA)+"custom/levels/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelsPack->addLevel(getUserPath(USER_DATA)+"custom/levels/"+*i);
levelsPack->setLocked(levelsPack->getLevelCount()-1);
customLevelsPack->addLevel(getUserPath(USER_DATA)+"custom/levels/"+*i);
customLevelsPack->setLocked(customLevelsPack->getLevelCount()-1);
}
//Add them to the manager.
levelPackManager.addLevelPack(levelsPack);
levelPackManager.addLevelPack(customLevelsPack);
//Load statistics
statsMgr.loadPicture();
statsMgr.registerAchievements();
statsMgr.loadFile(getUserPath(USER_CONFIG)+"statistics");
//Do something ugly and slow
statsMgr.reloadCompletedLevelsAndAchievements();
statsMgr.reloadOtherAchievements();
//Load the theme, both menu and default.
//NOTE: Loading theme may fail and returning false would stop everything, default theme will be used instead.
if (!loadTheme(getSettings()->getValue("theme"))){
getSettings()->setValue("theme","%DATA%/themes/Cloudscape");
saveSettings();
}
//Nothing failed so return true.
return true;
}
bool loadSettings(){
settings=new Settings(getUserPath(USER_CONFIG)+"meandmyshadow.cfg");
settings->parseFile();
//Now apply settings changed through command line arguments, if any.
map<string,string>::iterator it;
for(it=tmpSettings.begin();it!=tmpSettings.end();++it){
settings->setValue(it->first,it->second);
}
tmpSettings.clear();
//Always return true?
return true;
}
bool saveSettings(){
settings->save();
//Always return true?
return true;
}
Settings* getSettings(){
return settings;
}
MusicManager* getMusicManager(){
return &musicManager;
}
LevelPackManager* getLevelPackManager(){
return &levelPackManager;
}
ScriptExecutor* getScriptExecutor(){
return &scriptExecutor;
}
void flipScreen(){
if(settings->getBoolValue("gl")){
#ifdef HARDWARE_ACCELERATION
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
//Create a texture from the screen surface.
glBindTexture(GL_TEXTURE_2D,screenTexture);
//Set the texture's stretching properties
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D,0,screen->format->BytesPerPixel,screen->w,screen->h,0,GL_BGRA,GL_UNSIGNED_BYTE,screen->pixels);
glBegin(GL_QUADS);
glTexCoord2i(0,0); glVertex3f(0,0,0);
glTexCoord2i(1,0); glVertex3f(SCREEN_WIDTH,0,0);
glTexCoord2i(1,1); glVertex3f(SCREEN_WIDTH,SCREEN_HEIGHT,0);
glTexCoord2i(0,1); glVertex3f(0,SCREEN_HEIGHT,0);
glEnd();
SDL_GL_SwapBuffers();
#else
//NOTE: Trying to flip the screen using gl while compiled without.
cerr<<"FATAL ERROR: Unable to draw to screen using OpenGL (compiled without)."<<endl;
#endif
}else{
SDL_Flip(screen);
}
}
void clean(){
//Save statistics
statsMgr.saveFile(getUserPath(USER_CONFIG)+"statistics");
//We delete the settings.
if(settings){
delete settings;
settings=NULL;
}
//Delete dictionaryManager.
delete dictionaryManager;
//Get rid of the currentstate.
//NOTE: The state is probably already deleted by the changeState function.
if(currentState)
delete currentState;
//Destroy the GUI if present.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Destroy the imageManager.
imageManager.destroy();
//Destroy the musicManager.
musicManager.destroy();
//Destroy all sounds
Mix_FreeChunk(jumpSound);
Mix_FreeChunk(hitSound);
Mix_FreeChunk(saveSound);
Mix_FreeChunk(swapSound);
Mix_FreeChunk(toggleSound);
Mix_FreeChunk(errorSound);
Mix_FreeChunk(collectSound);
Mix_FreeChunk(achievementSound);
//Destroy the cursors.
for(int i=0;i<CURSOR_MAX;i++){
SDL_FreeCursor(cursors[i]);
cursors[i]=NULL;
}
//Destroy the levelPackManager.
levelPackManager.destroy();
levels=NULL;
//Close all joysticks.
inputMgr.closeAllJoysticks();
//Close the fonts and quit SDL_ttf.
TTF_CloseFont(fontTitle);
TTF_CloseFont(fontGUI);
TTF_CloseFont(fontGUISmall);
TTF_CloseFont(fontText);
TTF_CloseFont(fontMono);
TTF_Quit();
//Remove the temp surface.
SDL_FreeSurface(tempSurface);
//Stop audio.and quit
Mix_CloseAudio();
#ifndef __APPLE__
Mix_Quit();
#endif
//And finally quit SDL.
SDL_Quit();
}
void setNextState(int newstate){
//Only change the state when we aren't already exiting.
if(nextState!=STATE_EXIT){
nextState=newstate;
}
}
void changeState(){
//Check if there's a nextState.
if(nextState!=STATE_NULL){
//Delete the currentState.
delete currentState;
currentState=NULL;
//Set the currentState to the nextState.
stateID=nextState;
nextState=STATE_NULL;
//Init the state.
switch(stateID){
case STATE_GAME:
{
currentState=NULL;
Game* game=new Game();
currentState=game;
//Check if we should load record file or a level.
if(!Game::recordFile.empty()){
game->loadRecord(Game::recordFile.c_str());
Game::recordFile.clear();
}else{
game->loadLevel(levels->getLevelpackPath()+levels->getLevelFile());
levels->saveLevelProgress();
}
}
break;
case STATE_MENU:
currentState=new Menu();
break;
case STATE_LEVEL_SELECT:
currentState=new LevelPlaySelect();
break;
case STATE_LEVEL_EDIT_SELECT:
currentState=new LevelEditSelect();
break;
case STATE_LEVEL_EDITOR:
{
currentState=NULL;
LevelEditor* levelEditor=new LevelEditor();
currentState=levelEditor;
//Load the selected level.
levelEditor->loadLevel(levels->getLevelpackPath()+levels->getLevelFile());
}
break;
case STATE_OPTIONS:
currentState=new Options();
break;
case STATE_ADDONS:
currentState=new Addons();
break;
case STATE_CREDITS:
currentState=new Credits();
break;
case STATE_STATISTICS:
currentState=new StatisticsScreen();
break;
}
//NOTE: STATE_EXIT isn't mentioned, meaning that currentState is null.
//This way the game loop will break and the program will exit.
//Fade out, if fading is enabled.
int fade=0;
if(settings->getBoolValue("fading"))
fade=255;
SDL_BlitSurface(screen,NULL,tempSurface,NULL);
while(fade>0){
fade-=17;
if(fade<0)
fade=0;
SDL_FillRect(screen,NULL,0);
SDL_SetAlpha(tempSurface, SDL_SRCALPHA, fade);
SDL_BlitSurface(tempSurface,NULL,screen,NULL);
flipScreen();
SDL_Delay(25);
}
}
}
void musicStoppedHook(){
//We just call the musicStopped method of the MusicManager.
musicManager.musicStopped();
}
bool checkCollision(const SDL_Rect& a,const SDL_Rect& b){
//Check if the left side of box a isn't past the right side of b.
if(a.x>=b.x+b.w){
return false;
}
//Check if the right side of box a isn't left of the left side of b.
if(a.x+a.w<=b.x){
return false;
}
//Check if the top side of box a isn't under the bottom side of b.
if(a.y>=b.y+b.h){
return false;
}
//Check if the bottom side of box a isn't above the top side of b.
if(a.y+a.h<=b.y){
return false;
}
//We have collision.
return true;
}
void setCamera(const SDL_Rect* r,int count){
//SetCamera only works in the Level editor and when mouse is inside window.
if(stateID==STATE_LEVEL_EDITOR&&(SDL_GetAppState()&SDL_APPMOUSEFOCUS)){
//Get the mouse coordinates.
int x,y;
SDL_GetMouseState(&x,&y);
SDL_Rect mouse={x,y,0,0};
//Don't continue here if mouse is inside one of the boxes given as parameter.
for(int i=0;i<count;i++){
if(checkCollision(mouse,r[i]))
return;
}
//Check if the mouse is near the left edge of the screen.
//Else check if the mouse is near the right edge.
if(x<50){
//We're near the left edge so move the camera.
camera.x-=5;
}else if(x>SCREEN_WIDTH-50){
//We're near the right edge so move the camera.
camera.x+=5;
}
//Check if the mouse is near the top edge of the screen.
//Else check if the mouse is near the bottom edge.
if(y<50){
//We're near the top edge so move the camera.
camera.y-=5;
}else if(y>SCREEN_HEIGHT-50){
//We're near the bottom edge so move the camera.
camera.y+=5;
}
}
}
int parseArguments(int argc, char** argv){
//Loop through all arguments.
//We start at one since 0 is the command itself.
for(int i=1;i<argc;i++){
string argument=argv[i];
//Check if the argument is the data-dir.
if(argument=="--data-dir"){
//We need a second argument so we increase i.
i++;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//Configure the dataPath with the given path.
dataPath=argv[i];
if(!getDataPath().empty()){
char c=dataPath[dataPath.size()-1];
if(c!='/'&&c!='\\') dataPath+="/";
}
}else if(argument=="--user-dir"){
//We need a second argument so we increase i.
i++;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//Configure the userPath with the given path.
userPath=argv[i];
if(!userPath.empty()){
char c=userPath[userPath.size()-1];
if(c!='/'&&c!='\\') userPath+="/";
}
}else if(argument=="-f" || argument=="-fullscreen" || argument=="--fullscreen"){
tmpSettings["fullscreen"]="1";
}else if(argument=="-w" || argument=="-windowed" || argument=="--windowed"){
tmpSettings["fullscreen"]="0";
}else if(argument=="-mv" || argument=="-music" || argument=="--music"){
//We need a second argument so we increase i.
i++;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//Now set the music volume.
tmpSettings["music"]=argv[i];
}else if(argument=="-sv" || argument=="-sound" || argument=="--sound"){
//We need a second argument so we increase i.
i++;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//Now set sound volume.
tmpSettings["sound"]=argv[i];
}else if(argument=="-set" || argument=="--set"){
//We need a second and a third argument so we increase i.
i+=2;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//And set the setting.
tmpSettings[argv[i-1]]=argv[i];
}else if(argument=="-v" || argument=="-version" || argument=="--version"){
//Print the version.
printf("%s\n",version.c_str());
return 0;
}else if(argument=="-h" || argument=="-help" || argument=="--help"){
//If the help is requested we'll return false without printing an error.
//This way the usage/help text will be printed.
return -1;
}else{
//Any other argument is unknow so we return false.
printf("ERROR: Unknown argument %s\n\n",argument.c_str());
return -1;
}
}
//If everything went well we can return true.
return 1;
}
//Special structure that will recieve the GUIEventCallbacks of the messagebox.
struct msgBoxHandler:public GUIEventCallback{
public:
//Integer containing the ret(urn) value of the messageBox.
int ret;
public:
//Constructor.
msgBoxHandler():ret(0){}
void GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType){
//Make sure it's a click event.
if(eventType==GUIEventClick){
//Set the return value.
ret=obj->value;
//After a click event we can delete the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
}
};
msgBoxResult msgBox(string prompt,msgBoxButtons buttons,const string& title){
//Create the event handler.
msgBoxHandler objHandler;
//The GUI objects.
GUIObject* obj;
//Create the GUIObjectRoot, the height and y location is temp.
//It depends on the content what it will be.
- GUIObject* root=new GUIObject((SCREEN_WIDTH-600)/2,200,600,200,GUIObjectFrame,title.c_str());
+ GUIObject* root=new GUIFrame((SCREEN_WIDTH-600)/2,200,600,200,title.c_str());
//Integer containing the current y location used to grow dynamic depending on the content.
int y=50;
//Now process the prompt.
{
//Pointer to the string.
char* lps=(char*)prompt.c_str();
//Pointer to a character.
char* lp=NULL;
//We keep looping forever.
//The only way out is with the break statement.
for(;;){
//As long as it's still the same sentence we continue.
//It will stop when there's a newline or end of line.
for(lp=lps;*lp!='\n'&&*lp!='\r'&&*lp!=0;lp++);
//Store the character we stopped on. (End or newline)
char c=*lp;
//Set the character in the string to 0, making lps a string containing one sentence.
*lp=0;
//Add a GUIObjectLabel with the sentence.
- root->addChild(new GUIObject(0,y,root->width,25,GUIObjectLabel,lps,0,true,true,GUIGravityCenter));
+ root->addChild(new GUILabel(0,y,root->width,25,lps,0,true,true,GUIGravityCenter));
//Increase y with 25, about the height of the text.
y+=25;
//Check the stored character if it was a stop.
if(c==0){
//It was so break out of the for loop.
lps=lp;
break;
}
//It wasn't meaning more will follow.
//We set lps to point after the "newline" forming a new string.
lps=lp+1;
}
}
//Add 70 to y to leave some space between the content and the buttons.
y+=70;
//Recalc the size of the message box.
root->top=(SCREEN_HEIGHT-y)/2;
root->height=y;
//Now we need to add the buttons.
//Integer containing the number of buttons to add.
int count=0;
//Array with the return codes for the buttons.
int value[3]={0};
//Array containing the captation for the buttons.
string button[3]={"","",""};
switch(buttons){
case MsgBoxOKCancel:
count=2;
button[0]=_("OK");value[0]=MsgBoxOK;
button[1]=_("Cancel");value[1]=MsgBoxCancel;
break;
case MsgBoxAbortRetryIgnore:
count=3;
button[0]=_("Abort");value[0]=MsgBoxAbort;
button[1]=_("Retry");value[1]=MsgBoxRetry;
button[2]=_("Ignore");value[2]=MsgBoxIgnore;
break;
case MsgBoxYesNoCancel:
count=3;
button[0]=_("Yes");value[0]=MsgBoxYes;
button[1]=_("No");value[1]=MsgBoxNo;
button[2]=_("Cancel");value[2]=MsgBoxCancel;
break;
case MsgBoxYesNo:
count=2;
button[0]=_("Yes");value[0]=MsgBoxYes;
button[1]=_("No");value[1]=MsgBoxNo;
break;
case MsgBoxRetryCancel:
count=2;
button[0]=_("Retry");value[0]=MsgBoxRetry;
button[1]=_("Cancel");value[1]=MsgBoxCancel;
break;
default:
count=1;
button[0]=_("OK");value[0]=MsgBoxOK;
break;
}
//Now we start making the buttons.
{
//Reduce y so that the buttons fit inside the frame.
y-=40;
double places[3]={0.0};
if(count==1){
places[0]=0.5;
}else if(count==2){
places[0]=0.4;
places[1]=0.6;
}else if(count==3){
places[0]=0.3;
places[1]=0.5;
places[2]=0.7;
}
//Loop to add the buttons.
for(int i=0;i<count;i++){
- obj=new GUIObject(root->width*places[i],y,-1,36,GUIObjectButton,button[i].c_str(),value[i],true,true,GUIGravityCenter);
+ obj=new GUIButton(root->width*places[i],y,-1,36,button[i].c_str(),value[i],true,true,GUIGravityCenter);
obj->eventCallback=&objHandler;
root->addChild(obj);
}
}
//Now we dim the screen and keep the GUI rendering/updating.
GUIOverlay* overlay=new GUIOverlay(root);
overlay->enterLoop(true);
//And return the result.
return (msgBoxResult)objHandler.ret;
}
struct fileDialogHandler:public GUIEventCallback{
public:
//The ret(urn) value, true=ok and false=cancel
bool ret;
//Boolean if it's a save dialog.
bool isSave;
//Boolean if the file should be verified.
bool verifyFile;
//Boolean if files should be listed instead of directories.
bool files;
//Pointer to the textfield containing the filename.
GUIObject* txtName;
//Pointer to the listbox containing the different files.
GUIListBox* lstFile;
//The extension the files listed should have.
const char* extension;
//The current filename.
string fileName;
//The current search path.
string path;
//Vector containing the search paths.
vector<string> searchPath;
public:
//Constructor.
fileDialogHandler(bool isSave=false,bool verifyFile=false, bool files=true):ret(false),
isSave(isSave),verifyFile(verifyFile),
files(files),txtName(NULL),lstFile(NULL),extension(NULL){}
void GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType){
//Check for the ok event.
if(name=="cmdOK"){
//Get the entered fileName from the text field.
std::string s=txtName->caption;
//If it doesn't contain a slash we need to add the path to the fileName.
if(s.find_first_of("/")==string::npos)
s=path+s;
//If the string empty we return.
if(s.empty() || s.find_first_of("*?")!=string::npos)
return;
//We only need to check for extensions if it isn't a folder dialog.
if(files){
//If there isn't right extension add it.
size_t found=s.find_first_of(".");
if(found!=string::npos)
s.replace(s.begin()+found+1,s.end(),extension);
else if (s.substr(found+1)!=extension)
s.append(string(".")+extension);
}
//Check if we should save or load the file.
//
if(isSave){
//Open the file with read permission to check if it already exists.
FILE* f;
f=fopen(processFileName(s).c_str(),"rb");
//Check if it exists.
if(f){
//Close the file.
fclose(f);
//Let the currentState render once to prevent multiple GUI overlapping and prevent the screen from going black.
currentState->render();
//Prompt the user with a Yes or No question.
/// TRANSLATORS: Filename is coming before this text
if(msgBox(tfm::format(_("%s already exists.\nDo you want to overwrite it?"),s),MsgBoxYesNo,_("Overwrite Prompt"))!=MsgBoxYes){
//He answered no, so we return.
return;
}
}
//Check if we should verify the file.
//Verifying only applies to files not to directories.
if(verifyFile && files){
//Open the file with write permission.
f=fopen(processFileName(s).c_str(),"wb");
//Check if their aren't problems.
if(f){
//Close the file.
fclose(f);
}else{
//Let the currentState render once to prevent multiple GUI overlapping and prevent the screen from going black.
currentState->render();
//The file can't be opened so tell the user.
msgBox(tfm::format(_("Can't open file %s."),s),MsgBoxOKOnly,_("Error"));
return;
}
}
}else if(verifyFile && files){
//We need to verify a file for opening.
FILE *f;
f=fopen(processFileName(s).c_str(),"rb");
//Check if it didn't fail.
if(f){
//Succes, so close the file.
fclose(f);
}else{
//Let the currentState render once to prevent multiple GUI overlapping and prevent the screen from going black.
currentState->render();
//Unable to open file so tell the user.
msgBox(tfm::format(_("Can't open file %s."),s),MsgBoxOKOnly,_("Error"));
return;
}
}
//If we haven't returned then it's fine.
//Set the fileName to the chosen file.
fileName=s;
//Delete the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Set return to true.
ret=true;
}else if(name=="cmdCancel"){
//Cancel means we can kill the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}else if(name=="lstFile"){
//Get a pointer to the listbox.
GUIListBox* obj1=lstFile;
//Make sure the option exist and change textfield to it.
if(obj1!=NULL && txtName!=NULL && obj1->value>=0 && obj1->value<(int)obj1->item.size()){
txtName->caption=obj1->item[obj1->value];
}
}else if(name=="lstSearchIn"){
//Get the searchpath listbox.
GUISingleLineListBox *obj1=dynamic_cast<GUISingleLineListBox*>(obj);
//Check if the entry exists.
if(obj1!=NULL && lstFile!=NULL && obj1->value>=0 && obj1->value<(int)searchPath.size()){
//Temp string.
string s;
//Get the new search path.
path=searchPath[obj1->value];
//Make sure it isn't empty.
if(!path.empty()){
//Process the filename.
s=processFileName(path);
}else{
//It's empty so we give the userpath.
s=getUserPath();
}
//Fill the list with files or directories.
if(files) {
lstFile->item=enumAllFiles(s,extension);
}else
lstFile->item=enumAllDirs(s);
//Remove any selection from the list.
lstFile->value=-1;
}
}
}
};
bool fileDialog(string& fileName,const char* title,const char* extension,const char* path,bool isSave,bool verifyFile,bool files){
//Pointer to GUIObject to make the GUI with.
GUIObject* obj;
//Create the fileDialogHandler, used for event handling.
fileDialogHandler objHandler(isSave,verifyFile,files);
//Vector containing the pathNames.
vector<string> pathNames;
//Set the extension of the objHandler.
objHandler.extension=extension;
//We now need to splits the given path into multiple path names.
if(path && path[0]){
//The string isn't empty.
//Pointer to the paths string.
char* lp=(char*)path;
//Pointer to the first newline.
char* lps=strchr(lp,'\n');
//Pointer used for checking if their's another newline.
//It will indicate if it's the last set or not.
char* lpe;
//Check for a newline.
if(lps){
//We have newline(s) so loop forever.
//We can only break out of the loop when the string ends.
for(;;){
//Add the first searchpath.
//This is the beginning of the string (lp) to the first newline. (lps)
objHandler.searchPath.push_back(string(lp,lps-lp));
//We should have another newline so search for it.
lpe=strchr(lps+1,'\n');
if(lpe){
//We found it so we add that to the pathname.
pathNames.push_back(string(lps+1,lpe-lps-1));
//And start over again by setting lp to the start of a new set of searchPath/pathName.
lp=lpe+1;
}else{
//There is no newline anymore, meaning the last entry, the rest of the string must be the pathName.
pathNames.push_back(string(lps+1));
//And break out of the loop.
break;
}
//We haven't broken out so search for a newline.
lps=strchr(lp,'\n');
//If there isn't a newline break.
if(!lps)
break;
}
}else{
//There is no newline thus the whole string is the searchPath.
objHandler.searchPath.push_back(path);
}
}else{
//Empty so put an empty string as searchPath.
objHandler.searchPath.push_back(string());
}
//It's time to create the GUI.
//If there are more than one pathNames we need to add a GUISingleLineListBox.
int base_y=pathNames.empty()?20:60;
//Create the frame.
- GUIObject* root=new GUIObject(100,100-base_y/2,600,400+base_y,GUIObjectFrame,title?title:(isSave?_("Save File"):_("Load File")));
+ GUIObject* root=new GUIFrame(100,100-base_y/2,600,400+base_y,title?title:(isSave?_("Save File"):_("Load File")));
//Create the search path list box if needed.
if(!pathNames.empty()){
- root->addChild(new GUIObject(8,40,184,36,GUIObjectLabel,_("Search In")));
+ root->addChild(new GUILabel(8,40,184,36,_("Search In")));
GUISingleLineListBox* obj1=new GUISingleLineListBox(160,40,432,36);
obj1->addItems(pathNames);
obj1->value=0;
obj1->name="lstSearchIn";
obj1->eventCallback=&objHandler;
root->addChild(obj1);
}
//Add the FileName label and textfield.
- root->addChild(new GUIObject(8,20+base_y,184,36,GUIObjectLabel,_("File Name")));
+ root->addChild(new GUILabel(8,20+base_y,184,36,_("File Name")));
{
//Fill the textbox with the given fileName.
string s=fileName;
if(!isSave){
//But only if it isn't empty.
if(s.empty() && extension && extension[0])
s=string("*.")+string(extension);
}
//Create the textbox and add it to the GUI.
- objHandler.txtName=new GUIObject(160,20+base_y,432,36,GUIObjectTextBox,s.c_str());
+ objHandler.txtName=new GUITextBox(160,20+base_y,432,36,s.c_str());
root->addChild(objHandler.txtName);
}
//Now we add the ListBox containing the files or directories.
{
GUIListBox* obj1=new GUIListBox(8,60+base_y,584,292);
//Get the searchPath.
string s=objHandler.searchPath[0];
//Make sure it isn't empty.
if(!s.empty()){
objHandler.path=s;
s=processFileName(s);
}else{
s=getUserPath();
}
//Check if we should list files or directories.
if(files){
//Fill the list with files.
obj1->item=enumAllFiles(s,extension);
}else{
//Fill the list with directories.
obj1->item=enumAllDirs(s);
}
obj1->name="lstFile";
obj1->eventCallback=&objHandler;
root->addChild(obj1);
objHandler.lstFile=obj1;
}
//Now create the OK and Cancel buttons.
- obj=new GUIObject(200,360+base_y,192,36,GUIObjectButton,_("OK"));
+ obj=new GUIButton(200,360+base_y,192,36,_("OK"));
obj->name="cmdOK";
obj->eventCallback=&objHandler;
root->addChild(obj);
- obj=new GUIObject(400,360+base_y,192,36,GUIObjectButton,_("Cancel"));
+ obj=new GUIButton(400,360+base_y,192,36,_("Cancel"));
obj->name="cmdCancel";
obj->eventCallback=&objHandler;
root->addChild(obj);
//Create the gui overlay.
GUIOverlay* overlay=new GUIOverlay(root);
overlay->enterLoop();
//Now determine what the return value is (and if there is one).
if(objHandler.ret)
fileName=objHandler.fileName;
return objHandler.ret;
}
diff --git a/src/GUIListBox.cpp b/src/GUIListBox.cpp
index c6e4c2d..7a4de6d 100644
--- a/src/GUIListBox.cpp
+++ b/src/GUIListBox.cpp
@@ -1,536 +1,524 @@
/*
* 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 "GUIListBox.h"
using namespace std;
GUIListBox::GUIListBox(int left,int top,int width,int height,bool enabled,bool visible,int gravity):
-GUIObject(left,top,width,height,0,NULL,-1,enabled,visible,gravity),itemHeight(24),selectable(true),clickEvents(false){
+GUIObject(left,top,width,height,NULL,-1,enabled,visible,gravity),itemHeight(24),selectable(true),clickEvents(false){
//Set the state -1.
state=-1;
//Create the scrollbar and add it to the children.
scrollBar=new GUIScrollBar(width-16,0,16,height,1,0,0,0,1,1,true,true);
childControls.push_back(scrollBar);
}
GUIListBox::~GUIListBox(){
//Remove all items and cache.
clearItems();
//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();
}
bool GUIListBox::handleEvents(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;
//Update the scrollbar.
if(scrollBar->visible)
b=b||scrollBar->handleEvents(x,y,enabled,visible,b);
//Set state negative.
state=-1;
//Check if the GUIListBox is visible, enabled and no event has been processed before.
if(enabled&&visible&&!b){
//The mouse location (x=i, y=j) and the mouse button (k).
int i,j;
SDL_GetMouseState(&i,&j);
//Convert the mouse location to a relative location.
i-=x;
j-=y;
//Check if the mouse is inside the GUIListBox.
if(i>=0&&i<width-4&&j>=0&&j<height-4){
//Calculate selected item.
int idx=0;
if(scrollBar->value==scrollBar->maxValue&&scrollBar->visible){
int over=height-(itemHeight*(height/itemHeight));
if(j>over)
idx=((j-over)/itemHeight)+scrollBar->value;
else
idx=scrollBar->value-1;
}else{
idx=(j/itemHeight)+scrollBar->value;
}
//If the entry isn't above the max we have an event.
if(idx>=0&&idx<(int)item.size()&&selectable){
state=idx;
//Check if the left mouse button is pressed.
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_LEFT){
//Check if the slected item changed.
if(value!=idx){
value=idx;
//If event callback is configured then add an event to the queue.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
}
//After possibly a change event, there will always be a click event.
if(eventCallback && clickEvents){
GUIEvent e={eventCallback,name,this,GUIEventClick};
GUIEventQueue.push_back(e);
}
}
}
//Check for mouse wheel scrolling.
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_WHEELDOWN && scrollBar->enabled){
scrollBar->value++;
if(scrollBar->value>scrollBar->maxValue)
scrollBar->value=scrollBar->maxValue;
}else if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_WHEELUP && scrollBar->enabled){
scrollBar->value--;
if(scrollBar->value<0)
scrollBar->value=0;
}
}
}
//Process child controls event except for the scrollbar.
//That's why i starts at one.
for(unsigned int i=1;i<childControls.size();i++){
bool b1=childControls[i]->handleEvents(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 GUIListBox::render(int x,int y,bool draw){
if(updateScrollbar){
//Calculate the height of the content.
int maxY=0;
for(int i=0;i<images.size();i++){
maxY+=images.at(i)->h;
}
//Check if we need to show the scrollbar for many entries.
if(maxY<height){
scrollBar->maxValue=0;
scrollBar->value=0;
scrollBar->visible=false;
}else{
scrollBar->visible=true;
scrollBar->maxValue=item.size();
int yy=0;
for(int i=images.size()-1;i>0;i--){
yy+=images.at(i)->h;
if(yy>height)
break;
else
scrollBar->maxValue--;
}
scrollBar->largeChange=item.size()/4;
}
updateScrollbar=false;
}
//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 background box.
SDL_Rect r={x,y,width,height};
SDL_FillRect(screen,&r,0xFFFFFFFF);
//Loop through the entries and draw them.
if(scrollBar->value==scrollBar->maxValue&&scrollBar->visible){
int lowNumber=height;
int currentItem=images.size()-1;
while(lowNumber>=0&&currentItem>=0){
lowNumber-=images.at(currentItem)->h;
if(lowNumber>0){
if(selectable){
//Check if the mouse is hovering on current entry. If so draw borders around it.
if(state==currentItem)
drawGUIBox(x,y+lowNumber-1,width,images.at(currentItem)->h+1,screen,0x00000000);
//Check if the current entry is selected. If so draw a gray background.
if(value==currentItem)
drawGUIBox(x,y+lowNumber-1,width,images.at(currentItem)->h+1,screen,0xDDDDDDFF);
}
applySurface(x,y+lowNumber,images.at(currentItem),screen,NULL);
}else{
if(selectable){
//Check if the mouse is hovering on current entry. If so draw borders around it.
if(state==currentItem)
drawGUIBox(x,y,width,images.at(currentItem)->h+lowNumber+1,screen,0x00000000);
//Check if the current entry is selected. If so draw a gray background.
if(value==currentItem)
drawGUIBox(x,y,width,images.at(currentItem)->h+lowNumber+1,screen,0xDDDDDDFF);
}
SDL_Rect clip;
clip.x=0;
clip.y=-lowNumber;
clip.w=images.at(currentItem)->w;
clip.h=images.at(currentItem)->h+lowNumber;
applySurface(x,y,images.at(currentItem),screen,&clip);
break;
}
currentItem--;
}
}else{
for(int i=scrollBar->value,j=y+1;j>height,i<item.size();i++){
//Check if the current item is out side of the widget.
int yOver=images[i]->h;
if(j+images[i]->h>y+height)
yOver=y+height-j;
if(yOver>0){
if(selectable){
//Check if the mouse is hovering on current entry. If so draw borders around it.
if(state==i)
drawGUIBox(x,j-1,width,yOver+1,screen,0x00000000);
//Check if the current entry is selected. If so draw a gray background.
if(value==i)
drawGUIBox(x,j-1,width,yOver+1,screen,0xDDDDDDFF);
}
//Draw the image.
SDL_Rect clip;
clip.x=0;
clip.y=0;
clip.w=images[i]->w;
clip.h=yOver;
applySurface(x,j,images[i],screen,&clip);
}else{
break;
}
j+=images[i]->h;
}
}
//Draw borders around the whole thing.
drawGUIBox(x,y,width,height,screen,0x00000000);
//We now need to draw all the children of the GUIObject.
for(unsigned int i=0;i<childControls.size();i++){
childControls[i]->render(x,y,draw);
}
}
void GUIListBox::clearItems(){
item.clear();
for(unsigned int i=0;i<images.size();i++){
SDL_FreeSurface(images[i]);
}
images.clear();
}
void GUIListBox::addItem(std::string name, SDL_Surface* image){
item.push_back(name);
if(image){
itemHeight=image->h;
images.push_back(image);
}else if(!image&&!name.empty()){
SDL_Color black={0,0,0,0};
SDL_Surface* tmp=TTF_RenderUTF8_Blended(fontText,name.c_str(),black);
images.push_back(tmp);
}
updateScrollbar=true;
}
void GUIListBox::updateItem(int index, std::string newText, SDL_Surface* newImage){
item.at(index)=newText;
if(newImage){
itemHeight=newImage->h;
SDL_FreeSurface(images.at(index));
images.at(index)=newImage;
}else if(!newImage&&!newText.empty()){
SDL_FreeSurface(images.at(index));
SDL_Color black={0,0,0,0};
SDL_Surface* tmp=TTF_RenderUTF8_Blended(fontText,newText.c_str(),black);
images.at(index)=tmp;
}
updateScrollbar=true;
}
std::string GUIListBox::getItem(int index){
return item.at(index);
}
GUISingleLineListBox::GUISingleLineListBox(int left,int top,int width,int height,bool enabled,bool visible,int gravity):
-GUIObject(left,top,width,height,0,NULL,-1,enabled,visible,gravity),animation(0){}
+GUIObject(left,top,width,height,NULL,-1,enabled,visible,gravity),animation(0){}
void GUISingleLineListBox::addItem(string name,string label){
//Check if the label is set, if not use the name.
if(label.size()==0)
label=name;
item.push_back(pair<string,string>(name,label));
}
void GUISingleLineListBox::addItems(vector<pair<string,string> > items){
vector<pair<string,string> >::iterator it;
for(it=items.begin();it!=items.end();++it){
addItem(it->first,it->second);
}
}
void GUISingleLineListBox::addItems(vector<string> items){
vector<string>::iterator it;
for(it=items.begin();it!=items.end();++it){
addItem(*it);
}
}
string GUISingleLineListBox::getName(unsigned int index){
if(index==-1)
index=value;
if(index<0||index>item.size())
return "";
return item[index].first;
}
bool GUISingleLineListBox::handleEvents(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-gravityX;
y+=top;
state&=~0xF;
if(enabled&&visible){
//The mouse location (x=i, y=j) and the mouse button (k).
int i,j,k;
k=SDL_GetMouseState(&i,&j);
//Convert the mouse location to a relative location.
i-=x;
j-=y;
//The selected button.
//0=nothing 1=left 2=right.
int idx=0;
//Check which button the mouse is above.
if(i>=0&&i<width&&j>=0&&j<height){
if(i<26 && i<width/2){
//The left arrow.
idx=1;
}else if(i>=width-26){
//The right arrow.
idx=2;
}
}
//If idx is 0 it means the mous doesn't hover any arrow so reset animation.
if(idx==0)
animation=0;
//Check if there's a mouse button press or not.
if(k&SDL_BUTTON(1)){
if(((state>>4)&0xF)==idx)
state|=idx;
}else{
state|=idx;
}
//Check if there's a mouse press.
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_LEFT && idx){
state=idx|(idx<<4);
}else if(event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT && idx && ((state>>4)&0xF)==idx){
int m=(int)item.size();
if(m>0){
if(idx==2){
idx=value+1;
if(idx<0||idx>=m) idx=0;
if(idx!=value){
value=idx;
//If there is an event callback then call it.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventClick};
GUIEventQueue.push_back(e);
}
}
}else if(idx==1){
idx=value-1;
if(idx<0||idx>=m) idx=m-1;
if(idx!=value){
value=idx;
//If there is an event callback then call it.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventClick};
GUIEventQueue.push_back(e);
}
}
}
}
}
if(event.type==SDL_MOUSEBUTTONUP) state&=0xF;
}else{
//Set state zero.
state=0;
}
- //Also let the children handle their events.
- for(unsigned int i=0;i<childControls.size();i++){
- bool b1=childControls[i]->handleEvents(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 GUISingleLineListBox::render(int x,int y,bool draw){
//Rectangle the size of the GUIObject, used to draw borders.
SDL_Rect r;
//There's no need drawing the GUIObject when it's invisible.
if(!visible)
return;
//NOTE: logic in the render method since it's the only part that gets called every frame.
if((state&0xF)==0x1 || (state&0xF)==0x2){
animation++;
if(animation>20)
animation=-20;
}
//Get the absolute x and y location.
x+=left;
y+=top;
if(gravity==GUIGravityCenter)
gravityX=int(width/2);
else if(gravity==GUIGravityRight)
gravityX=width;
x-=gravityX;
//Check if the enabled state changed or the caption, if so we need to clear the (old) cache.
if(enabled!=cachedEnabled || item[value].second.compare(cachedCaption)!=0){
//Free the cache.
SDL_FreeSurface(cache);
cache=NULL;
//And cache the new values.
cachedEnabled=enabled;
cachedCaption=item[value].second;
}
//Draw the text.
if(value>=0 && value<(int)item.size()){
//Get the text.
const char* lp=item[value].second.c_str();
//Check if the text is empty or not.
if(lp!=NULL && lp[0]){
if(!cache){
SDL_Color color;
if(inDialog)
color=themeTextColorDialog;
else
color=themeTextColor;
cache=TTF_RenderUTF8_Blended(fontGUI,lp,color);
//If the text is too wide then we change to smaller font (?)
//NOTE: The width is 32px smaller (2x16px for the arrows).
if(cache->w>width-32){
SDL_FreeSurface(cache);
cache=TTF_RenderUTF8_Blended(fontGUISmall,lp,color);
}
}
if(draw){
//Center the text both vertically as horizontally.
r.x=x+(width-cache->w)/2;
r.y=y+(height-cache->h)/2-GUI_FONT_RAISE;
//Draw the text and free the surface afterwards.
SDL_BlitSurface(cache,NULL,screen,&r);
}
}
}
if(draw){
//Draw the arrows.
SDL_Rect r2={48,0,16,16};
r.x=x;
if((state&0xF)==0x1)
r.x+=abs(animation/2);
r.y=y+4;
if(inDialog)
applySurface(r.x,r.y,arrowLeft2,screen,NULL);
else
applySurface(r.x,r.y,arrowLeft1,screen,NULL);
r.x=x+width-16;
if((state&0xF)==0x2)
r.x-=abs(animation/2);
if(inDialog)
applySurface(r.x,r.y,arrowRight2,screen,NULL);
else
applySurface(r.x,r.y,arrowRight1,screen,NULL);
}
-
- //We now need to draw all the children of the GUIObject.
- for(unsigned int i=0;i<childControls.size();i++){
- childControls[i]->render(x,y,draw);
- }
}
diff --git a/src/GUIObject.cpp b/src/GUIObject.cpp
index a8d27f9..72e4a7e 100644
--- a/src/GUIObject.cpp
+++ b/src/GUIObject.cpp
@@ -1,774 +1,960 @@
/*
* 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 "GUIObject.h"
#include <iostream>
#include <list>
using namespace std;
//Set the GUIObjectRoot to NULL.
GUIObject* GUIObjectRoot=NULL;
//Initialise the event queue.
list<GUIEvent> GUIEventQueue;
-
void GUIObjectHandleEvents(bool kill){
//Check if user resizes the window.
if(event.type==SDL_VIDEORESIZE){
//onVideoResize();
//Don't let other objects process this event (?)
return;
}
//Make sure that GUIObjectRoot isn't null.
if(GUIObjectRoot)
GUIObjectRoot->handleEvents();
//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(e.name,e.obj,e.eventType);
}
}
//We empty the event queue just to be sure.
GUIEventQueue.clear();
}
GUIObject::~GUIObject(){
//The cache is used as the actual image for GUIObjectImage and shouldn't be freed.
- if(cache && type!=GUIObjectImage){
+ if(cache){
SDL_FreeSurface(cache);
cache=NULL;
}
//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();
}
bool GUIObject::handleEvents(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.
+ //The GUIObject is only enabled when its parent are enabled.
enabled=enabled && this->enabled;
- //The GUIObject is only enabled when he and his parent are enabled.
+ //The GUIObject is only enabled when its parent are enabled.
visible=visible && this->visible;
//Get the absolute position.
x+=left-gravityX;
y+=top;
- //Type specific event handling.
- switch(type){
- case GUIObjectButton:
- //Set state to 0.
- state=0;
+ //Also let the children handle their events.
+ for(unsigned int i=0;i<childControls.size();i++){
+ bool b1=childControls[i]->handleEvents(x,y,enabled,visible,b);
- //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 GUIObject.
- 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;
- }
- }
- }
- break;
- case GUIObjectCheckBox:
- //Set state to 0.
- state=0;
+ //The event is processed when either our or the childs is true (or both).
+ b=b||b1;
+ }
+ return b;
+}
+
+void GUIObject::render(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(x,y,draw);
+ }
+}
+
+//////////////GUIButton///////////////////////////////////////////////////////////////////
+
+bool GUIButton::handleEvents(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;
+
+ //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);
- //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 the mouse is inside the GUIObject.
- 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;
+ //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);
}
- }
- }
- break;
- case GUIObjectTextBox:
- //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.
- int key=(int)event.key.keysym.unicode;
- //Check if the key is supported.
- if(key>=32&&key<=126){
- if(highlightStart==highlightEnd){
- caption.insert((size_t)highlightStart,1,char(key));
- highlightStart++;
- highlightEnd=highlightStart;
- }else if(highlightStart<highlightEnd){
- caption.erase(highlightStart,highlightEnd-highlightStart);
- caption.insert((size_t)highlightStart,1,char(key));
- highlightStart++;
- highlightEnd=highlightStart;
- }else{
- caption.erase(highlightEnd,highlightStart-highlightEnd);
- caption.insert((size_t)highlightEnd,1,char(key));
- highlightEnd++;
- highlightStart=highlightEnd;
- }
-
- //If there is an event callback then call it.
- if(eventCallback){
- GUIEvent e={eventCallback,name,this,GUIEventChange};
- GUIEventQueue.push_back(e);
- }
- }else if(event.key.keysym.sym==SDLK_BACKSPACE){
- //We need to remove a character so first make sure that there is text.
- if(caption.length()>0){
- if(highlightStart==highlightEnd&&highlightStart>0){
- highlightEnd=highlightStart=clamp(highlightEnd-1,0,caption.length());
- caption.erase((size_t)highlightEnd,1);
- }else if(highlightStart<highlightEnd){
- caption.erase(highlightStart,highlightEnd-highlightStart);
- highlightEnd=highlightStart;
- }else{
- caption.erase(highlightEnd,highlightStart-highlightEnd);
- highlightStart=highlightEnd;
- }
-
- this->key=SDLK_BACKSPACE;
- keyHoldTime=0;
- keyTime=5;
-
- //If there is an event callback then call it.
- if(eventCallback){
- GUIEvent e={eventCallback,name,this,GUIEventChange};
- GUIEventQueue.push_back(e);
- }
- }
- }else if(event.key.keysym.sym==SDLK_DELETE){
- //We need to remove a character so first make sure that there is text.
- if(caption.length()>0){
- if(highlightStart==highlightEnd){
- highlightEnd=highlightStart=clamp(highlightEnd,0,caption.length());
- caption.erase((size_t)highlightEnd,1);
- }else if(highlightStart<highlightEnd){
- caption.erase(highlightStart,highlightEnd-highlightStart);
- highlightEnd=highlightStart;
- }else{
- caption.erase(highlightEnd,highlightStart-highlightEnd);
- highlightStart=highlightEnd;
- }
-
- this->key=SDLK_DELETE;
- keyHoldTime=0;
- keyTime=5;
-
- //If there is an event callback then call it.
- if(eventCallback){
- GUIEvent e={eventCallback,name,this,GUIEventChange};
- GUIEventQueue.push_back(e);
- }
- }
- }else if(event.key.keysym.sym==SDLK_RIGHT){
- highlightEnd=highlightStart=clamp(highlightEnd+1,0,caption.length());
-
- this->key=SDLK_RIGHT;
- keyHoldTime=0;
- keyTime=5;
- }else if(event.key.keysym.sym==SDLK_LEFT){
- highlightEnd=highlightStart=clamp(highlightEnd-1,0,caption.length());
-
- this->key=SDLK_LEFT;
- keyHoldTime=0;
- keyTime=5;
- }
-
- //The event has been processed.
+ //Event has been processed.
b=true;
- }else if(state==2 && event.type==SDL_KEYUP && !b){
- //Check if released key is the same as the holded key.
- if(event.key.keysym.sym==key){
- //It is so stop the key.
- key=-1;
- }
- }
-
- //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 GUIObject.
- 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 click=i-x-2;
- int clickPos=0;
-
- if(cache&&!caption.empty()){
- clickPos=caption.length();
- unsigned int wid=0;
- for(unsigned int i=0;i<caption.length();i++){
- int advance;
- TTF_GlyphMetrics(fontText,caption[i],NULL,NULL,NULL,NULL,&advance);
- wid+=advance;
-
- if(click<(int)wid-(int)advance/2){
- clickPos=i;
- break;
- }
- }
- }
-
- if(event.type==SDL_MOUSEBUTTONUP){
- state=2;
- highlightEnd=clickPos;
- }else if(event.type==SDL_MOUSEBUTTONDOWN){
- state=2;
- highlightStart=clickPos;
- highlightEnd=clickPos;
- }else if(event.type==SDL_MOUSEMOTION&&(k&SDL_BUTTON(1))){
- state=2;
- highlightEnd=clickPos;
- }
- }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;
- }
}
}
- break;
}
//Also let the children handle their events.
for(unsigned int i=0;i<childControls.size();i++){
bool b1=childControls[i]->handleEvents(x,y,enabled,visible,b);
//The event is processed when either our or the childs is true (or both).
b=b||b1;
}
return b;
}
//Found on gmane.comp.lib.sdl mailing list, see: http://comments.gmane.org/gmane.comp.lib.sdl/33664
//Original code by "Patricia Curtis" and later modified by "Jason"
static void SetSurfaceTrans(SDL_Surface* Src,double PercentTrans){
Uint8 Sbpp = Src->format->BytesPerPixel;
Uint8 *Sbits;
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
int amask = 0x000000ff;
int cmask = 0xffffff00;
#else
int amask = 0xff000000;
int cmask = 0x00ffffff;
int Shift = 24;
#endif
int x,y;
Uint32 Pixels;
Uint32 Alpha;
for(y=0;y<Src->h;y++)
{
for(x=0;x<Src->w;x++)
{
Sbits = ((Uint8 *)Src->pixels+(y*Src->pitch)+(x*Sbpp));
Pixels = *((Uint32 *)(Sbits));
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
Alpha = Pixels & mask;
#else
Alpha = (Pixels&amask)>>Shift;
#endif
Alpha*=PercentTrans;
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
*((Uint32 *)(Sbits)) = (Pixels & cmask)|Alpha;
#else
*((Uint32 *)(Sbits)) = (Pixels & cmask)|(Alpha<<Shift);
#endif
}
}
}
-void GUIObject::render(int x,int y,bool draw){
- //Rectangle the size of the GUIObject, used to draw borders.
- SDL_Rect r;
-
- //There's no need drawing the GUIObject when it's invisible.
+void GUIButton::render(int x,int y,bool draw){
+ //There's no need drawing the widget when it's invisible.
if(!visible)
return;
+ //Rectangle the size of the widget.
+ SDL_Rect r;
+
//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.
SDL_FreeSurface(cache);
cache=NULL;
//And cache the new values.
cachedEnabled=enabled;
cachedCaption=caption;
//Finally resize the widget
if(autoWidth)
width=-1;
}
- //Now do the type specific rendering.
- switch(type){
- case GUIObjectLabel:
- {
- //The rectangle is simple.
- r.x=x;
- r.y=y;
- r.w=width;
- r.h=height;
-
- //We don't draw a background and/or border since that label is transparent.
- //Get the caption and make sure it isn't empty.
- const char* lp=caption.c_str();
- if(lp!=NULL && lp[0]){
- //Render the text using the small font.
- if(cache==NULL){
- SDL_Color color;
- if(inDialog)
- color=themeTextColorDialog;
- else
- color=themeTextColor;
+ //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(!cache){
+ SDL_Color color;
+ if(inDialog)
+ color=themeTextColorDialog;
+ else
+ color=themeTextColor;
- cache=TTF_RenderUTF8_Blended(fontText,lp,color);
-
- if(width<=0)
- width=cache->w;
+ if(!smallFont)
+ cache=TTF_RenderUTF8_Blended(fontGUI,lp,color);
+ else
+ cache=TTF_RenderUTF8_Blended(fontGUISmall,lp,color);
+
+ //Make the widget transparent if it's disabled.
+ if(!enabled)
+ SetSurfaceTrans(cache,0.5);
+
+ //Calculate proper size for the widget.
+ if(width<=0){
+ width=cache->w+50;
+ if(gravity==GUIGravityCenter){
+ gravityX=int(width/2);
+ }else if(gravity==GUIGravityRight){
+ gravityX=width;
+ }else{
+ gravityX=0;
}
-
- if(draw){
- if(gravity==GUIGravityCenter)
- gravityX=(width-cache->w)/2;
- else if(gravity==GUIGravityRight)
- gravityX=width-cache->w;
- else
- gravityX=0;
-
- //Center the text vertically and draw it to the screen.
- r.y=y+(height - cache->h)/2;
- r.x+=gravityX;
- SDL_BlitSurface(cache,NULL,screen,&r);
+ }
+ }
+
+ if(draw){
+ //Center the text both vertically as horizontally.
+ r.x=x-gravityX+(width-cache->w)/2;
+ r.y=y+(height-cache->h)/2-GUI_FONT_RAISE;
+
+ //Check if the arrows don't fall of.
+ if(cache->w+32<=width){
+ //Create a rectangle that selects the right image from bmGUI.
+ SDL_Rect r2={64,0,16,16};
+ if(state==1){
+ if(inDialog){
+ applySurface(x-gravityX+(width-cache->w)/2+4+cache->w+5,y+2,arrowLeft2,screen,NULL);
+ applySurface(x-gravityX+(width-cache->w)/2-25,y+2,arrowRight2,screen,NULL);
+ }else{
+ applySurface(x-gravityX+(width-cache->w)/2+4+cache->w+5,y+2,arrowLeft1,screen,NULL);
+ applySurface(x-gravityX+(width-cache->w)/2-25,y+2,arrowRight1,screen,NULL);
+ }
+ }else if(state==2){
+ if(inDialog){
+ applySurface(x-gravityX+(width-cache->w)/2+4+cache->w,y+2,arrowLeft2,screen,NULL);
+ applySurface(x-gravityX+(width-cache->w)/2-20,y+2,arrowRight2,screen,NULL);
+ }else{
+ applySurface(x-gravityX+(width-cache->w)/2+4+cache->w,y+2,arrowLeft1,screen,NULL);
+ applySurface(x-gravityX+(width-cache->w)/2-20,y+2,arrowRight1,screen,NULL);
+ }
}
}
+
+ //Draw the text and free the surface.
+ SDL_BlitSurface(cache,NULL,screen,&r);
}
- break;
- case GUIObjectCheckBox:
- {
- //The rectangle is simple.
- r.x=x;
- r.y=y;
- r.w=width;
- r.h=height;
+ }
+}
+
+//////////////GUICheckBox///////////////////////////////////////////////////////////////////
+
+bool GUICheckBox::handleEvents(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;
+
+ //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;
- //Get the text.
- const char* lp=caption.c_str();
- //Make sure it isn't empty.
- if(lp!=NULL && lp[0]){
- //We render black text.
- if(!cache){
- SDL_Color color;
- if(inDialog)
- color=themeTextColorDialog;
- else
- color=themeTextColor;
+ //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;
- cache=TTF_RenderUTF8_Blended(fontText,lp,color);
+ //If event callback is configured then add an event to the queue.
+ if(eventCallback){
+ GUIEvent e={eventCallback,name,this,GUIEventClick};
+ GUIEventQueue.push_back(e);
}
- if(draw){
- //Calculate the location, center it vertically.
- r.x=x;
- r.y=y+(height - cache->h)/2;
-
- //Draw the text and free the surface.
- SDL_BlitSurface(cache,NULL,screen,&r);
- }
+ //Event has been processed.
+ b=true;
}
+ }
+ }
+
+ return b;
+}
+
+void GUICheckBox::render(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.
+ if(enabled!=cachedEnabled || caption.compare(cachedCaption)!=0 || width<=0){
+ //Free the cache.
+ SDL_FreeSurface(cache);
+ cache=NULL;
+
+ //And cache the new values.
+ cachedEnabled=enabled;
+ cachedCaption=caption;
+
+ //Finally resize the widget
+ if(autoWidth)
+ width=-1;
+ }
+
+ //Rectangle the size of the widget.
+ SDL_Rect r;
+ r.x=x;
+ r.y=y;
+ r.w=width;
+ r.h=height;
+
+ //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(!cache){
+ SDL_Color color;
+ if(inDialog)
+ color=themeTextColorDialog;
+ else
+ color=themeTextColor;
+
+ cache=TTF_RenderUTF8_Blended(fontText,lp,color);
+ }
+
+ if(draw){
+ //Calculate the location, center it vertically.
+ r.x=x;
+ r.y=y+(height - cache->h)/2;
+
+ //Draw the text and free the surface.
+ SDL_BlitSurface(cache,NULL,screen,&r);
+ }
+ }
+
+ if(draw){
+ //Draw the check (or not).
+ SDL_Rect r1={0,0,16,16};
+ if(value==1||value==2)
+ r1.x=value*16;
+ r.x=x+width-20;
+ r.y=y+(height-16)/2;
+ SDL_BlitSurface(bmGUI,&r1,screen,&r);
+ }
+}
+
+//////////////GUILabel///////////////////////////////////////////////////////////////////
+
+bool GUILabel::handleEvents(int x,int y,bool enabled,bool visible,bool processed){
+ return processed;
+}
+
+void GUILabel::render(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.
+ if(enabled!=cachedEnabled || caption.compare(cachedCaption)!=0 || width<=0){
+ //Free the cache.
+ SDL_FreeSurface(cache);
+ cache=NULL;
+
+ //And cache the new values.
+ cachedEnabled=enabled;
+ cachedCaption=caption;
+
+ //Finally resize the widget
+ if(autoWidth)
+ width=-1;
+ }
+
+ //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(cache==NULL){
+ SDL_Color color;
+ if(inDialog)
+ color=themeTextColorDialog;
+ else
+ color=themeTextColor;
- if(draw){
- //Draw the check (or not).
- SDL_Rect r1={0,0,16,16};
- if(value==1||value==2)
- r1.x=value*16;
- r.x=x+width-20;
- r.y=y+(height-16)/2;
- SDL_BlitSurface(bmGUI,&r1,screen,&r);
- }
+ cache=TTF_RenderUTF8_Blended(fontText,lp,color);
+
+ if(width<=0)
+ width=cache->w;
}
- break;
- case GUIObjectButton:
- {
- //Get the text.
- const char* lp=caption.c_str();
- //Make sure the text isn't empty.
- if(lp!=NULL && lp[0]){
- if(!cache){
- SDL_Color color;
- if(inDialog)
- color=themeTextColorDialog;
- else
- color=themeTextColor;
-
- if(!smallFont)
- cache=TTF_RenderUTF8_Blended(fontGUI,lp,color);
- else
- cache=TTF_RenderUTF8_Blended(fontGUISmall,lp,color);
+
+ //Align the text properly and draw it.
+ if(draw){
+ if(gravity==GUIGravityCenter)
+ gravityX=(width-cache->w)/2;
+ else if(gravity==GUIGravityRight)
+ gravityX=width-cache->w;
+ else
+ gravityX=0;
+
+ r.y=y+(height - cache->h)/2;
+ r.x+=gravityX;
+ SDL_BlitSurface(cache,NULL,screen,&r);
+ }
+ }
+}
+
+//////////////GUITextBox///////////////////////////////////////////////////////////////////
+
+bool GUITextBox::handleEvents(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.
+ int key=(int)event.key.keysym.unicode;
+
+ //Check if the key is supported.
+ if(key>=32&&key<=126){
+ if(highlightStart==highlightEnd){
+ caption.insert((size_t)highlightStart,1,char(key));
+ highlightStart++;
+ highlightEnd=highlightStart;
+ }else if(highlightStart<highlightEnd){
+ caption.erase(highlightStart,highlightEnd-highlightStart);
+ caption.insert((size_t)highlightStart,1,char(key));
+ highlightStart++;
+ highlightEnd=highlightStart;
+ }else{
+ caption.erase(highlightEnd,highlightStart-highlightEnd);
+ caption.insert((size_t)highlightEnd,1,char(key));
+ highlightEnd++;
+ highlightStart=highlightEnd;
+ }
+
+ //If there is an event callback then call it.
+ if(eventCallback){
+ GUIEvent e={eventCallback,name,this,GUIEventChange};
+ GUIEventQueue.push_back(e);
+ }
+ }else if(event.key.keysym.sym==SDLK_BACKSPACE){
+ //We need to remove a character so first make sure that there is text.
+ if(caption.length()>0){
+ if(highlightStart==highlightEnd&&highlightStart>0){
+ highlightEnd=highlightStart=clamp(highlightEnd-1,0,caption.length());
+ caption.erase((size_t)highlightEnd,1);
+ }else if(highlightStart<highlightEnd){
+ caption.erase(highlightStart,highlightEnd-highlightStart);
+ highlightEnd=highlightStart;
+ }else{
+ caption.erase(highlightEnd,highlightStart-highlightEnd);
+ highlightStart=highlightEnd;
+ }
- if(!enabled)
- SetSurfaceTrans(cache,0.5);
+ this->key=SDLK_BACKSPACE;
+ keyHoldTime=0;
+ keyTime=5;
- if(width<=0){
- width=cache->w+50;
- if(gravity==GUIGravityCenter){
- gravityX=int(width/2);
- }else if(gravity==GUIGravityRight){
- gravityX=width;
- }else{
- gravityX=0;
- }
+ //If there is an event callback then call it.
+ if(eventCallback){
+ GUIEvent e={eventCallback,name,this,GUIEventChange};
+ GUIEventQueue.push_back(e);
}
}
-
- if(draw){
- //Center the text both vertically as horizontally.
- r.x=x-gravityX+(width-cache->w)/2;
- r.y=y+(height-cache->h)/2-GUI_FONT_RAISE;
-
- //Check if the arrows don't fall of.
- if(cache->w+32<=width){
- //Create a rectangle that selects the right image from bmGUI,
- SDL_Rect r2={64,0,16,16};
- if(state==1){
- if(inDialog){
- applySurface(x-gravityX+(width-cache->w)/2+4+cache->w+5,y+2,arrowLeft2,screen,NULL);
- applySurface(x-gravityX+(width-cache->w)/2-25,y+2,arrowRight2,screen,NULL);
- }else{
- applySurface(x-gravityX+(width-cache->w)/2+4+cache->w+5,y+2,arrowLeft1,screen,NULL);
- applySurface(x-gravityX+(width-cache->w)/2-25,y+2,arrowRight1,screen,NULL);
- }
- }else if(state==2){
- if(inDialog){
- applySurface(x-gravityX+(width-cache->w)/2+4+cache->w,y+2,arrowLeft2,screen,NULL);
- applySurface(x-gravityX+(width-cache->w)/2-20,y+2,arrowRight2,screen,NULL);
- }else{
- applySurface(x-gravityX+(width-cache->w)/2+4+cache->w,y+2,arrowLeft1,screen,NULL);
- applySurface(x-gravityX+(width-cache->w)/2-20,y+2,arrowRight1,screen,NULL);
- }
- }
+ }else if(event.key.keysym.sym==SDLK_DELETE){
+ //We need to remove a character so first make sure that there is text.
+ if(caption.length()>0){
+ if(highlightStart==highlightEnd){
+ highlightEnd=highlightStart=clamp(highlightEnd,0,caption.length());
+ caption.erase((size_t)highlightEnd,1);
+ }else if(highlightStart<highlightEnd){
+ caption.erase(highlightStart,highlightEnd-highlightStart);
+ highlightEnd=highlightStart;
+ }else{
+ caption.erase(highlightEnd,highlightStart-highlightEnd);
+ highlightStart=highlightEnd;
}
- //Draw the text and free the surface.
- SDL_BlitSurface(cache,NULL,screen,&r);
+ this->key=SDLK_DELETE;
+ keyHoldTime=0;
+ keyTime=5;
+
+ //If there is an event callback then call it.
+ if(eventCallback){
+ GUIEvent e={eventCallback,name,this,GUIEventChange};
+ GUIEventQueue.push_back(e);
+ }
}
+ }else if(event.key.keysym.sym==SDLK_RIGHT){
+ highlightEnd=highlightStart=clamp(highlightEnd+1,0,caption.length());
+
+ this->key=SDLK_RIGHT;
+ keyHoldTime=0;
+ keyTime=5;
+ }else if(event.key.keysym.sym==SDLK_LEFT){
+ highlightEnd=highlightStart=clamp(highlightEnd-1,0,caption.length());
+
+ this->key=SDLK_LEFT;
+ keyHoldTime=0;
+ keyTime=5;
+ }
+
+ //The event has been processed.
+ b=true;
+ }else if(state==2 && event.type==SDL_KEYUP && !b){
+ //Check if released key is the same as the holded key.
+ if(event.key.keysym.sym==key){
+ //It is so stop the key.
+ key=-1;
}
}
- break;
- case GUIObjectTextBox:
- {
- //FIXME: Logic in the render method since that is update constant.
- if(key!=-1){
- //Increase the key time.
- keyHoldTime++;
- //Make sure the deletionTime isn't to short.
- if(keyHoldTime>=keyTime){
- keyHoldTime=0;
- keyTime--;
- if(keyTime<1)
- keyTime=1;
+
+ //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 click=i-x-2;
+ int clickPos=0;
+
+ if(cache&&!caption.empty()){
+ clickPos=caption.length();
+ unsigned int wid=0;
+ for(unsigned int i=0;i<caption.length();i++){
+ int advance;
+ TTF_GlyphMetrics(fontText,caption[i],NULL,NULL,NULL,NULL,&advance);
+ wid+=advance;
- //Now check the which key it was.
- switch(key){
- case SDLK_BACKSPACE:
- {
- if(caption.length()>0){
- if(highlightStart==highlightEnd&&highlightStart>0){
- highlightEnd=highlightStart=clamp(highlightEnd-1,0,caption.length());
- caption.erase((size_t)highlightEnd,1);
- }else if(highlightStart<highlightEnd){
- caption.erase(highlightStart,highlightEnd-highlightStart);
- highlightEnd=highlightStart;
- }else{
- caption.erase(highlightEnd,highlightStart-highlightEnd);
- highlightStart=highlightEnd;
- }
- }
- break;
- }
- case SDLK_DELETE:
- {
- if(caption.length()>0){
- if(highlightStart==highlightEnd){
- highlightEnd=highlightStart=clamp(highlightEnd,0,caption.length());
- caption.erase((size_t)highlightEnd,1);
- }else if(highlightStart<highlightEnd){
- caption.erase(highlightStart,highlightEnd-highlightStart);
- highlightEnd=highlightStart;
- }else{
- caption.erase(highlightEnd,highlightStart-highlightEnd);
- highlightStart=highlightEnd;
- }
- }
- break;
- }
- case SDLK_LEFT:
- {
- highlightEnd=highlightStart=clamp(highlightEnd-1,0,caption.length());
- tick=15;
- break;
- }
- case SDLK_RIGHT:
- {
- highlightEnd=highlightStart=clamp(highlightEnd+1,0,caption.length());
- tick=15;
- break;
- }
+ if(click<(int)wid-(int)advance/2){
+ clickPos=i;
+ break;
}
}
}
- 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;
+ if(event.type==SDL_MOUSEBUTTONUP){
+ state=2;
+ highlightEnd=clickPos;
+ }else if(event.type==SDL_MOUSEBUTTONDOWN){
+ state=2;
+ highlightStart=clickPos;
+ highlightEnd=clickPos;
+ }else if(event.type==SDL_MOUSEMOTION&&(k&SDL_BUTTON(1))){
+ state=2;
+ highlightEnd=clickPos;
+ }
+ }else{
+ //The mouse is outside the TextBox.
+ //If we don't have focus but only hover we lose it.
+ if(state==1){
+ state=0;
+ }
- //Draw the box.
- Uint32 color=0xFFFFFF00|clr;
- drawGUIBox(x,y,width,height,screen,color);
+ //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;
}
+ }
+ }
+
+ return b;
+}
+
+void GUITextBox::render(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.
+ if(enabled!=cachedEnabled || caption.compare(cachedCaption)!=0 || width<=0){
+ //Free the cache.
+ SDL_FreeSurface(cache);
+ cache=NULL;
+
+ //And cache the new values.
+ cachedEnabled=enabled;
+ cachedCaption=caption;
+
+ //Finally resize the widget
+ if(autoWidth)
+ width=-1;
+ }
+
+ //FIXME: Logic in the render method since that is update constant.
+ if(key!=-1){
+ //Increase the key time.
+ keyHoldTime++;
+ //Make sure the deletionTime isn't to short.
+ if(keyHoldTime>=keyTime){
+ keyHoldTime=0;
+ keyTime--;
+ if(keyTime<1)
+ keyTime=1;
- //Get the text.
- const char* lp=caption.c_str();
- //Make sure it isn't empty.
- if(lp!=NULL && lp[0]){
- if(!cache){
- //Draw the black text.
- SDL_Color black={0,0,0,0};
- cache=TTF_RenderUTF8_Blended(fontText,lp,black);
- }
-
- if(draw){
- //Only draw the carrot and highlight when focus.
- if(state==2){
- r.x=x+4;
- r.y=y+3;
- r.h=height-6;
- r.w=0;
-
- int advance;
- int carrotX=2;
-
- //Find out the highlighted area.
- //NOTE: Start and end positions can be in any order so we have different code for both options.
- if(highlightStart>highlightEnd){
- for(int n=0;n<highlightStart;n++){
- TTF_GlyphMetrics(fontText,caption[n],NULL,NULL,NULL,NULL,&advance);
- if(n<highlightEnd){
- r.x+=advance;
- carrotX+=advance;
- }else{
- r.w+=advance;
- }
- }
- }else{
- for(int n=0;n<highlightEnd;n++){
- TTF_GlyphMetrics(fontText,caption[n],NULL,NULL,NULL,NULL,&advance);
- if(n<highlightStart){
- r.x+=advance;
- }else{
- r.w+=advance;
- }
- carrotX+=advance;
- }
+ //Now check the which key it was.
+ switch(key){
+ case SDLK_BACKSPACE:
+ {
+ if(caption.length()>0){
+ if(highlightStart==highlightEnd&&highlightStart>0){
+ highlightEnd=highlightStart=clamp(highlightEnd-1,0,caption.length());
+ caption.erase((size_t)highlightEnd,1);
+ }else if(highlightStart<highlightEnd){
+ caption.erase(highlightStart,highlightEnd-highlightStart);
+ highlightEnd=highlightStart;
+ }else{
+ caption.erase(highlightEnd,highlightStart-highlightEnd);
+ highlightStart=highlightEnd;
}
-
- //Draw the highlighted area.
- SDL_FillRect(screen,&r,SDL_MapRGB(screen->format,128,128,128));
-
- //Ticking carrot.
- if(tick<16){
- //Show carrot: 15->0.
- r.x=x+carrotX;
- r.y=y+3;
- r.h=height-6;
- r.w=2;
- SDL_FillRect(screen,&r,SDL_MapRGB(screen->format,0,0,0));
-
- //Reset: 32 or count down.
- if(tick<=0)
- tick=32;
- else
- tick--;
+ }
+ break;
+ }
+ case SDLK_DELETE:
+ {
+ if(caption.length()>0){
+ if(highlightStart==highlightEnd){
+ highlightEnd=highlightStart=clamp(highlightEnd,0,caption.length());
+ caption.erase((size_t)highlightEnd,1);
+ }else if(highlightStart<highlightEnd){
+ caption.erase(highlightStart,highlightEnd-highlightStart);
+ highlightEnd=highlightStart;
}else{
- //Hide carrot: 32->16.
- tick--;
+ caption.erase(highlightEnd,highlightStart-highlightEnd);
+ highlightStart=highlightEnd;
}
}
-
- //Calculate the location, center it vertically.
- r.x=x+4;
- r.y=y+(height - cache->h)/2;
-
- //Draw the text.
- SDL_Rect tmp={0,0,width-2,25};
- SDL_BlitSurface(cache,&tmp,screen,&r);
+ break;
}
- }else{
- //Only draw the carrot when focus.
- if(state==2&&draw){
- r.x=x+4;
- r.y=y+4;
- r.w=2;
- r.h=height-8;
- SDL_FillRect(screen,&r,0);
+ case SDLK_LEFT:
+ {
+ highlightEnd=highlightStart=clamp(highlightEnd-1,0,caption.length());
+ tick=15;
+ break;
+ }
+ case SDLK_RIGHT:
+ {
+ highlightEnd=highlightStart=clamp(highlightEnd+1,0,caption.length());
+ tick=15;
+ break;
}
}
}
- break;
- case GUIObjectFrame:
- {
- if(draw){
- //Create a rectangle the size of the button and fill it.
- Uint32 color=0xDDDDDDFF;
- drawGUIBox(x,y,width,height,screen,color);
- }
-
- //Get the title text.
- const char* lp=caption.c_str();
- //Make sure it isn't empty.
- if(lp!=NULL && lp[0]){
- if(!cache){
- cache=TTF_RenderUTF8_Blended(fontGUI,lp,themeTextColorDialog);
+ }
+
+ 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,screen,color);
+ }
+
+ //Rectangle used for drawing.
+ SDL_Rect r;
+
+ //Get the text and make sure it isn't empty.
+ const char* lp=caption.c_str();
+ if(lp!=NULL && lp[0]){
+ if(!cache){
+ //Draw the black text.
+ SDL_Color black={0,0,0,0};
+ cache=TTF_RenderUTF8_Blended(fontText,lp,black);
+ }
+
+ if(draw){
+ //Only draw the carrot and highlight when focus.
+ if(state==2){
+ r.x=x+4;
+ r.y=y+3;
+ r.h=height-6;
+ r.w=0;
+
+ int advance;
+ int carrotX=2;
+
+ //Find out the highlighted area.
+ //NOTE: Start and end positions can be in any order so we have different code for both options.
+ if(highlightStart>highlightEnd){
+ for(int n=0;n<highlightStart;n++){
+ TTF_GlyphMetrics(fontText,caption[n],NULL,NULL,NULL,NULL,&advance);
+ if(n<highlightEnd){
+ r.x+=advance;
+ carrotX+=advance;
+ }else{
+ r.w+=advance;
+ }
+ }
+ }else{
+ for(int n=0;n<highlightEnd;n++){
+ TTF_GlyphMetrics(fontText,caption[n],NULL,NULL,NULL,NULL,&advance);
+ if(n<highlightStart){
+ r.x+=advance;
+ }else{
+ r.w+=advance;
+ }
+ carrotX+=advance;
+ }
}
- if(draw){
- //Calculate the location, center horizontally and vertically relative to the top.
- r.x=x+(width-cache->w)/2;
- r.y=y+6-GUI_FONT_RAISE;
+ //Draw the highlighted area.
+ SDL_FillRect(screen,&r,SDL_MapRGB(screen->format,128,128,128));
- //Draw the text and free the surface.
- SDL_BlitSurface(cache,NULL,screen,&r);
+ //Ticking carrot.
+ if(tick<16){
+ //Show carrot: 15->0.
+ r.x=x+carrotX;
+ r.y=y+3;
+ r.h=height-6;
+ r.w=2;
+ SDL_FillRect(screen,&r,SDL_MapRGB(screen->format,0,0,0));
+
+ //Reset: 32 or count down.
+ if(tick<=0)
+ tick=32;
+ else
+ tick--;
+ }else{
+ //Hide carrot: 32->16.
+ tick--;
}
}
- }
- break;
- case GUIObjectImage:
- {
- r.x=x;
- r.y=y;
- r.w=width;
- r.h=height;
- //Make sure the image isn't null.
- if(cache){
- applySurface(r.x,r.y,cache,screen,NULL);
- }
+ //Calculate the location, center it vertically.
+ r.x=x+4;
+ r.y=y+(height - cache->h)/2;
+
+ //Draw the text.
+ SDL_Rect tmp={0,0,width-2,25};
+ SDL_BlitSurface(cache,&tmp,screen,&r);
+ }
+ }else{
+ //Only draw the carrot when focus.
+ if(state==2&&draw){
+ r.x=x+4;
+ r.y=y+4;
+ r.w=2;
+ r.h=height-8;
+ SDL_FillRect(screen,&r,0);
}
- break;
}
+}
+
+//////////////GUIFrame///////////////////////////////////////////////////////////////////
+
+bool GUIFrame::handleEvents(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(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(int x,int y,bool draw){
+ //There's no need drawing this widget when it's invisible.
+ if(!visible)
+ return;
- //We now need to draw all the children of the GUIObject.
+ //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.
+ SDL_FreeSurface(cache);
+ cache=NULL;
+
+ //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,screen,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(!cache)
+ cache=TTF_RenderUTF8_Blended(fontGUI,lp,themeTextColorDialog);
+
+ //Draw the text.
+ if(draw)
+ applySurface(x+(width-cache->w)/2,y+6-GUI_FONT_RAISE,cache,screen,NULL);
+ }
+
+ //We now need to draw all the children.
for(unsigned int i=0;i<childControls.size();i++){
childControls[i]->render(x,y,draw);
}
}
+
+//////////////GUIImage///////////////////////////////////////////////////////////////////
+
+bool GUIImage::handleEvents(int x,int y,bool enabled,bool visible,bool processed){
+ return processed;
+}
+
+void GUIImage::render(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;
+
+ //Make sure the image isn't null.
+ if(image)
+ applySurface(x,y,image,screen,NULL);
+}
diff --git a/src/GUIObject.h b/src/GUIObject.h
index 1d9a425..a618ba8 100644
--- a/src/GUIObject.h
+++ b/src/GUIObject.h
@@ -1,255 +1,363 @@
/*
* 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 "Globals.h"
#include "Functions.h"
#include "FileManager.h"
#include <string>
#include <vector>
#include <list>
-//Ids for the different GUIObject types.
-//None is a special type, it has no visual form.
-const int GUIObjectNone=0;
-//A label used to dispaly text.
-const int GUIObjectLabel=1;
-//Button which will invoke an event when pressed.
-const int GUIObjectButton=2;
-//Checkbox which represents a boolean value and can be toggled.
-const int GUIObjectCheckBox=3;
-//A text box used to enter text.
-const int GUIObjectTextBox=5;
-//Frame which is like a container.
-const int GUIObjectFrame=6;
-//Image that can be positioned, but won't cause any events.
-const int GUIObjectImage=7;
-
//Widget gravity properties
const int GUIGravityLeft=0;
const int GUIGravityCenter=1;
const int GUIGravityRight=2;
//The event id's.
//A click event used for e.g. buttons.
const int GUIEventClick=0;
//A change event used for e.g. textboxes.
const int GUIEventChange=1;
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(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;
//Widget's gravity to centering
int gravity;
int gravityX;
bool autoWidth;
- //Text highlights.
- int highlightStart;
- int highlightEnd;
-
- //Carrot ticking.
- int tick;
-
- //Use small font
- bool smallFont;
-
//Is the parent widget a dialog?
bool inDialog;
protected:
//The state of the GUIObject.
//It depends on the type of GUIObject where it's used for.
int state;
//Surface containing some gui images.
SDL_Surface* bmGUI;
//Surface that can be used to cache rendered text.
SDL_Surface* cache;
//String containing the old caption to detect if it changed.
std::string cachedCaption;
//Boolean containing the previous enabled state.
bool cachedEnabled;
-
- //Integer containing the key that is holded.
- int key;
-
- //Integer containing the time the key is pressed.
- int keyHoldTime;
- //The time it takes to invoke the key action again.
- int keyTime;
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.
- //type: The type 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(int left=0,int top=0,int width=0,int height=0,int type=0,
+ GUIObject(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):
left(left),top(top),width(width),height(height),
- type(type),gravity(gravity),value(value),
+ gravity(gravity),value(value),
enabled(enabled),visible(visible),
eventCallback(NULL),state(0),
- cache(NULL),cachedEnabled(enabled),gravityX(0),smallFont(false),
- key(-1),keyHoldTime(0),keyTime(5),highlightStart(0),highlightEnd(0),tick(0)
+ cache(NULL),cachedEnabled(enabled),gravityX(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.
bmGUI=loadImage(getDataPath()+"gfx/gui.png");
}
//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(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(int x=0,int y=0,bool draw=true);
void addChild(GUIObject* obj){
//Add widget add a child
childControls.push_back(obj);
- //Check if parent (this class) is a dialog. If not copy value from parent.
- if(type==GUIObjectFrame)
- obj->inDialog=true;
- else
- obj->inDialog=inDialog;
+ //Copy inDialog boolean from parent.
+ obj->inDialog=inDialog;
}
//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(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;
}
-
- //Method for setting the image of the GUIObjectImage.
- //NOTE: This method should only be used for GUIObjectImages.
- //image: SDL_Surface containing the image.
- void setImage(SDL_Surface* image){
- if(type!=GUIObjectImage)
- return;
-
- cache=image;
- }
};
//Method used to handle the GUIEvents from the GUIEventQueue.
//kill: Boolean if an SDL_QUIT event may kill the GUIObjectRoot.
void GUIObjectHandleEvents(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(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(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(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(int x=0,int y=0,bool draw=true);
+
+ //Boolean if small font is used.
+ bool smallFont;
+};
+
+class GUICheckBox:public GUIObject{
+public:
+ GUICheckBox(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(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(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(int x=0,int y=0,bool draw=true);
+};
+
+class GUILabel:public GUIObject{
+public:
+ GUILabel(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(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(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(int x=0,int y=0,bool draw=true);
+};
+
+class GUITextBox:public GUIObject{
+public:
+ GUITextBox(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(left,top,width,height,caption,value,enabled,visible,gravity),
+ highlightStart(0),highlightEnd(0),tick(15),key(-1),keyHoldTime(0),keyTime(0){ };
+ //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(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(int x=0,int y=0,bool draw=true);
+private:
+ //Text highlights.
+ int highlightStart;
+ int highlightEnd;
+
+ //Carrot ticking.
+ int tick;
+
+ //Integer containing the key that is holded.
+ int key;
+
+ //Integer containing the time the key is pressed.
+ int keyHoldTime;
+ //The time it takes to invoke the key action again.
+ int keyTime;
+};
+
+class GUIFrame:public GUIObject{
+public:
+ GUIFrame(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(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(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(int x=0,int y=0,bool draw=true);
+};
+
+class GUIImage:public GUIObject{
+public:
+ GUIImage(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(left,top,width,height,caption,value,enabled,visible,gravity),
+ image(NULL){ };
+ //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(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(int x=0,int y=0,bool draw=true);
+
+ //Method for setting the image of the widget.
+ //image: SDL_Surface containing the image.
+ void setImage(SDL_Surface* surface){
+ image=surface;
+ }
+private:
+ SDL_Surface* image;
+};
+
#endif
diff --git a/src/GUIScrollBar.cpp b/src/GUIScrollBar.cpp
index 0b1f144..76a0c0e 100644
--- a/src/GUIScrollBar.cpp
+++ b/src/GUIScrollBar.cpp
@@ -1,390 +1,370 @@
/*
* 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 "GUIScrollBar.h"
using namespace std;
void GUIScrollBar::calcPos(){
//Floats ...
float f,f2;
//The value can't be below the minimum value or above the maximum.
if(value<minValue)
value=minValue;
else if(value>maxValue)
value=maxValue;
//
if(orientation){
f=(float)(top+16);
f2=(float)(height-32);
}else{
f=(float)(left+16);
f2=(float)(width-32);
}
if(largeChange<=0) f2=-1;
if(f2>0){
float f1=0.0f;
valuePerPixel = (maxValue - minValue + largeChange) / f2;
if(valuePerPixel > 0.0001f) f1 = largeChange / valuePerPixel;
if(f1 < 4 && f2 > 4){
valuePerPixel = (maxValue - minValue) / (f2 - 4);
f1 = 4;
}
thumbStart = f + (value - minValue) / valuePerPixel;
thumbEnd = thumbStart + f1;
}else{
valuePerPixel = -1;
thumbStart = f;
thumbEnd = f - 1;
}
}
bool GUIScrollBar::handleEvents(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;
//Check if the mouse button is released.
if(event.type==SDL_MOUSEBUTTONUP || !(enabled&&visible)){
//It so we have lost any focus at all.
state=0;
}else if(event.type==SDL_MOUSEMOTION || event.type==SDL_MOUSEBUTTONDOWN){
//The mouse button is down and it's moving
int i,j,k,f,f1;
state&=~0xFF;
k=SDL_GetMouseState(&i,&j);
i-=x;
j-=y;
bool bInControl_0;
if(orientation){
f=top;
f1=f+height;
bInControl_0=(i>=left&&i<left+width);
i=j;
}else{
f=left;
f1=f+width;
bInControl_0=(j>=top&&j<top+height);
}
//===
if((state&0x0000FF00)==0x300&&(k&SDL_BUTTON(1))&&event.type==SDL_MOUSEMOTION&&valuePerPixel>0){
//drag thumb
state|=3;
int val = criticalValue + (int)(((float)i - startDragPos) * valuePerPixel + 0.5f);
if(val<minValue) val=minValue;
else if(val>maxValue) val=maxValue;
if(value!=val){
value=val;
changed=true;
}
b=true;
}else if(bInControl_0){
int f2,f3;
if(valuePerPixel > 0){
f2=f+16;
f3=f1-16;
}else{
f2=f3=(f+f1)/2;
}
if(i<f){ //do nothing
}else if(i<f2){ //-smallchange
state=(state&~0xFF)|1;
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT) state=(state&~0x0000FF00)|((state&0xFF)<<8);
else if((state&0x0000FF00)&&((state&0xFF)!=((state>>8)&0xFF))) state&=~0xFF;
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT){
int val=value-smallChange;
if(val<minValue) val=minValue;
if(value!=val){
value=val;
changed=true;
}
timer=8;
}
b=true;
}else if(i>=f3 && i<f1){ //+smallchange
state=(state&~0xFF)|5;
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT) state=(state&~0x0000FF00)|((state&0xFF)<<8);
else if((state&0x0000FF00)&&((state&0xFF)!=((state>>8)&0xFF))) state&=~0xFF;
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT){
int val=value+smallChange;
if(val>maxValue) val=maxValue;
if(value!=val){
value=val;
changed=true;
}
timer=8;
}
b=true;
}else if(valuePerPixel<=0){ //do nothing
}else if(i<(int)thumbStart){ //-largechange
state=(state&~0xFF)|2;
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT) state=(state&~0x0000FF00)|((state&0xFF)<<8);
else if((state&0x0000FF00)&&((state&0xFF)!=((state>>8)&0xFF))) state&=~0xFF;
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT){
int val=value-largeChange;
if(val<minValue) val=minValue;
if(value!=val){
value=val;
changed=true;
}
timer=8;
}
if(state&0xFF) criticalValue = minValue + (int)(float(i - f2) * valuePerPixel + 0.5f);
b=true;
}else if(i<(int)thumbEnd){ //start drag
state=(state&~0xFF)|3;
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT) state=(state&~0x0000FF00)|((state&0xFF)<<8);
else if((state&0x0000FF00)&&((state&0xFF)!=((state>>8)&0xFF))) state&=~0xFF;
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT){
criticalValue=value;
startDragPos = (float)i;
}
b=true;
}else if(i<f3){ //+largechange
state=(state&~0xFF)|4;
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT) state=(state&~0x0000FF00)|((state&0xFF)<<8);
else if((state&0x0000FF00)&&((state&0xFF)!=((state>>8)&0xFF))) state&=~0xFF;
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT){
int val=value+largeChange;
if(val>maxValue) val=maxValue;
if(value!=val){
value=val;
changed=true;
}
timer=8;
}
if(state&0xFF) criticalValue = minValue - largeChange + (int)(float(i - f2) * valuePerPixel + 0.5f);
b=true;
}
}
}
- //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(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 GUIScrollBar::renderScrollBarButton(int index,int x1,int y1,int x2,int y2,int srcleft,int srctop){
//Make sure the button isn't inverted.
if(x2<=x1||y2<=y1)
return;
//The color.
int clr=-1;
//Rectangle the size of the button.
SDL_Rect r={x1,y1,x2-x1,y2-y1};
//Check
if((state&0xFF)==index){
if(((state>>8)&0xFF)==index){
//Set the color gray.
clr=0xDDDDDDFF;
}else{
//Set the color to lightgray.
clr=0xFFFFFFFF;
}
}
//Draw a box.
drawGUIBox(r.x,r.y,r.w,r.h,screen,clr);
//Boolean if there should be an image on the button.
bool b;
//The check depends on the orientation.
if(orientation)
b=(y2-y1>=14);
else
b=(x2-x1>=14);
//Check if the image can be drawn.
if(b&&srcleft>=0&&srctop>=0){
//It can thus draw it.
SDL_Rect r1={srcleft,srctop,16,16};
r.x=(x1+x2)/2-8;
r.y=(y1+y2)/2-8;
SDL_BlitSurface(bmGUI,&r1,screen,&r);
}
}
void GUIScrollBar::render(int x,int y,bool draw){
//There's no use in rendering the scrollbar when invisible.
if(!visible)
return;
//Check if the scrollbar is enabled.
if(enabled){
//Check if the state is right.
if((state&0xFF)==((state>>8)&0xFF)){
//Switch the state (change)/
switch(state&0xFF){
case 1:
//It's a small negative change.
//Check if it's time.
if((--timer)<=0){
//Reduce the value.
int val=value-smallChange;
//Make sure it doesn't go too low.
if(val<minValue)
val=minValue;
if(value!=val){
value=val;
changed=true;
}
//Set the time to two.
timer=2;
}
break;
case 2:
//It's a lager negative change.
//Check if it's time.
if((--timer)<=0){
if(value<criticalValue)
state&=~0xFF;
else{
//Reduce the value.
int val=value-largeChange;
//Make sure it doesn't go too low.
if(val<minValue)
val=minValue;
if(value!=val){
value=val;
changed=true;
}
//Set the time to two.
timer=2;
}
}
break;
case 4:
//It's a lager positive change.
//Check if it's time.
if((--timer)<=0){
if(value>criticalValue)
state&=~0xFF;
else{
//Increase the value.
int val=value+largeChange;
//Make sure it doesn't go too high.
if(val>maxValue)
val=maxValue;
if(value!=val){
value=val;
changed=true;
}
//Set the time to two.
timer=2;
}
}
break;
case 5:
//It's a small positive change.
//Check if it's time.
if((--timer)<=0){
//Increase the value.
int val=value+smallChange;
//Make sure ti doesn't go too high.
if(val>maxValue)
val=maxValue;
if(value!=val){
value=val;
changed=true;
}
//Set the time to two.
timer=2;
}
break;
}
}
}
//If the scrollbar changed then invoke a GUIEvent.
if(changed){
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
changed=false;
}
//We calculate the position since it could have changed.
calcPos();
//Now the actual drawing begins.
if(orientation&&draw){
//The scrollbar is vertically orientated.
if(valuePerPixel>0){
//There are four buttons so draw them.
renderScrollBarButton(2,x+left,y+top,x+left+width,y+top+height,-1,-1);
renderScrollBarButton(1,x+left,y+top,x+left+width,y+top+16,80,0);
renderScrollBarButton(3,x+left,y-1+(int)thumbStart,x+left+width,y+1+(int)thumbEnd,0,16);
renderScrollBarButton(5,x+left,y+top+height-16,x+left+width,y+top+height,96,0);
}else{
//There are two buttons so draw them.
int f=top+height/2;
renderScrollBarButton(1,x+left,y+top,x+left+width,y+1+f,80,0);
renderScrollBarButton(5,x+left,y+f,x+left+width,y+top+height,96,0);
}
}else{
//The scrollbar is horizontally orientated.
if(valuePerPixel>0){
//There are five buttons so draw them.
renderScrollBarButton(1,x+left,y+top,x+left+16,y+top+height,80,16);
renderScrollBarButton(2,x+left+15,y+top,x+(int)thumbStart,y+top+height,-1,-1);
renderScrollBarButton(3,x-1+(int)thumbStart,y+top,x+1+(int)thumbEnd,y+top+height,16,16);
renderScrollBarButton(4,x+(int)thumbEnd,y+top,x+left+width-15,y+top+height,-1,-1);
renderScrollBarButton(5,x+left+width-16,y+top,x+left+width,y+top+height,96,16);
}else{
//There are two buttons so draw them.
int f=left+width/2;
renderScrollBarButton(1,x+left,y+top,x+1+f,y+top+height,80,16);
renderScrollBarButton(5,x+f,y+top,x+left+width,y+top+height,96,16);
}
}
-
- //Get the absolute position.
- 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(x,y,draw);
- }
}
diff --git a/src/GUIScrollBar.h b/src/GUIScrollBar.h
index db23b6b..25cb214 100644
--- a/src/GUIScrollBar.h
+++ b/src/GUIScrollBar.h
@@ -1,97 +1,97 @@
/*
* 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 GUISCROLLBAR_H
#define GUISCROLLBAR_H
#include "GUIObject.h"
//Constant integers containing the two possible orientations of the scrollbar.
const int ScrollBarHorizontal=0;
const int ScrollBarVertical=1;
class GUIScrollBar:public GUIObject{
public:
//The minimum value of the scrollbar.
int minValue;
//The maximum value of the scrollbar.
int maxValue;
//The step size when a small step is made.
int smallChange;
//The step size when a large step is made.
int largeChange;
//The orientation of the scrollbar.
int orientation;
private:
//
float thumbStart;
//
float thumbEnd;
float valuePerPixel;
//Float containing the start position when dragging the scrollbar.
float startDragPos;
int criticalValue;
int timer;
//Boolean if the scrollbar position has changed.
bool changed;
//Method that will calculate the position of the scrollbar.
void calcPos();
//Method that will render a scrollbar button.
//index:
//x1:
//y1:
//x2:
//y2:
//srcLeft:
//srcRight:
void renderScrollBarButton(int index,int x1,int y1,int x2,int y2,int srcLeft,int srcTop);
public:
GUIScrollBar(int left=0,int top=0,int width=0,int height=0,int orientation=0,
int value=0,int minValue=0,int maxValue=100,int smallChange=10,int largeChange=50,
bool enabled=true,bool visible=true):
- GUIObject(left,top,width,height,0,NULL,value,enabled,visible),
+ GUIObject(left,top,width,height,NULL,value,enabled,visible),
minValue(minValue),maxValue(maxValue),smallChange(smallChange),largeChange(largeChange),orientation(orientation),
thumbStart(0.0f),thumbEnd(0.0f),valuePerPixel(0.0f),startDragPos(0.0f),criticalValue(0),timer(0),changed(false)
{
//In the constructor we simply call calcPos().
calcPos();
}
//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(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)
virtual void render(int x=0,int y=0,bool draw=true);
};
#endif
diff --git a/src/GUISlider.cpp b/src/GUISlider.cpp
index 7d54bc9..e4ec48d 100644
--- a/src/GUISlider.cpp
+++ b/src/GUISlider.cpp
@@ -1,291 +1,271 @@
/*
* 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 "GUISlider.h"
using namespace std;
void GUISlider::calcPos(){
//Floats ...
float f,f2;
//The value can't be below the minimum value or above the maximum.
if(value<minValue)
value=minValue;
else if(value>maxValue)
value=maxValue;
//
f=(float)(left+1);
f2=(float)(width-2);
if(largeChange<=0) f2=-1;
if(f2>0){
float f1=0.0f;
valuePerPixel = (maxValue - minValue + largeChange) / f2;
if(valuePerPixel > 0.0001f) f1 = largeChange / valuePerPixel;
if(f1 < 4 && f2 > 4){
valuePerPixel = (maxValue - minValue) / (f2 - 4);
f1 = 4;
}
thumbStart = f + (value - minValue) / valuePerPixel;
thumbEnd = thumbStart + f1;
}else{
valuePerPixel = -1;
thumbStart = f;
thumbEnd = f - 1;
}
}
bool GUISlider::handleEvents(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;
//Check if the mouse button is released.
if(event.type==SDL_MOUSEBUTTONUP || !(enabled&&visible)){
//It so we have lost any focus at all.
state=0;
}else if(event.type==SDL_MOUSEMOTION || event.type==SDL_MOUSEBUTTONDOWN){
//The mouse button is down and it's moving
int i,j,k,f,f1;
state&=~0xFF;
k=SDL_GetMouseState(&i,&j);
i-=x;
j-=y;
bool bInControl_0;
f=left;
f1=f+width;
bInControl_0=(j>=top&&j<top+height);
//===
if((state&0x0000FF00)==0x300&&(k&SDL_BUTTON(1))&&event.type==SDL_MOUSEMOTION&&valuePerPixel>0){
//drag thumb
state|=3;
int val = criticalValue + (int)(((float)i - startDragPos) * valuePerPixel + 0.5f);
if(val<minValue) val=minValue;
else if(val>maxValue) val=maxValue;
if(value!=val){
value=val;
changed=true;
}
b=true;
}else if(bInControl_0){
int f2,f3;
if(valuePerPixel > 0){
f2=f+16;
f3=f1-16;
}else{
f2=f3=(f+f1)/2;
}
if(i<f){ //do nothing
}else if(valuePerPixel<=0){ //do nothing
}else if(i<(int)thumbStart){ //-largechange
state=(state&~0xFF)|2;
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT) state=(state&~0x0000FF00)|((state&0xFF)<<8);
else if((state&0x0000FF00)&&((state&0xFF)!=((state>>8)&0xFF))) state&=~0xFF;
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT){
int val=value-largeChange;
if(val<minValue) val=minValue;
if(value!=val){
value=val;
changed=true;
}
timer=8;
}
if(state&0xFF) criticalValue = minValue + (int)(float(i - f2) * valuePerPixel + 0.5f);
b=true;
}else if(i<(int)thumbEnd){ //start drag
state=(state&~0xFF)|3;
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT) state=(state&~0x0000FF00)|((state&0xFF)<<8);
else if((state&0x0000FF00)&&((state&0xFF)!=((state>>8)&0xFF))) state&=~0xFF;
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT){
criticalValue=value;
startDragPos = (float)i;
}
b=true;
}else if(i<f3){ //+largechange
state=(state&~0xFF)|4;
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT) state=(state&~0x0000FF00)|((state&0xFF)<<8);
else if((state&0x0000FF00)&&((state&0xFF)!=((state>>8)&0xFF))) state&=~0xFF;
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT){
int val=value+largeChange;
if(val>maxValue) val=maxValue;
if(value!=val){
value=val;
changed=true;
}
timer=8;
}
if(state&0xFF) criticalValue = minValue - largeChange + (int)(float(i - f2) * valuePerPixel + 0.5f);
b=true;
}
}
}
- //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(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 GUISlider::renderScrollBarButton(int index,int x1,int y1,int x2,int y2,int srcleft,int srctop){
//Make sure the button isn't inverted.
if(x2<=x1||y2<=y1)
return;
//The color.
int clr=-1;
//Rectangle the size of the button.
SDL_Rect r={x1,y1,x2-x1,y2-y1};
//Check
if((state&0xFF)==index){
if(((state>>8)&0xFF)==index){
//Set the color gray.
clr=0xDDDDDDFF;
}else{
//Set the color to lightgray.
clr=0xFFFFFFFF;
}
}
//Draw a box.
drawGUIBox(r.x,r.y,r.w,r.h,screen,clr);
//Boolean if there should be an image on the button.
bool b;
b=(x2-x1>=14);
//Check if the image can be drawn.
if(b&&srcleft>=0&&srctop>=0){
//It can thus draw it.
SDL_Rect r1={srcleft,srctop,16,16};
r.x=(x1+x2)/2-8;
r.y=(y1+y2)/2-8;
SDL_BlitSurface(bmGUI,&r1,screen,&r);
}
}
void GUISlider::render(int x,int y,bool draw){
//There's no use in rendering the scrollbar when invisible.
if(!visible||!draw)
return;
//Check if the scrollbar is enabled.
if(enabled){
//Check if the state is right.
if((state&0xFF)==((state>>8)&0xFF)){
//Switch the state (change)/
switch(state&0xFF){
case 2:
//It's a lager negative change.
//Check if it's time.
if((--timer)<=0){
if(value<criticalValue)
state&=~0xFF;
else{
//Reduce the value.
int val=value-largeChange;
//Make sure it doesn't go too low.
if(val<minValue)
val=minValue;
if(value!=val){
value=val;
changed=true;
}
//Set the time to two.
timer=2;
}
}
break;
case 4:
//It's a lager positive change.
//Check if it's time.
if((--timer)<=0){
if(value>criticalValue)
state&=~0xFF;
else{
//Increase the value.
int val=value+largeChange;
//Make sure it doesn't go too high.
if(val>maxValue)
val=maxValue;
if(value!=val){
value=val;
changed=true;
}
//Set the time to two.
timer=2;
}
}
break;
}
}
}
//If the scrollbar changed then invoke a GUIEvent.
if(changed){
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
changed=false;
}
//We calculate the position since it could have changed.
calcPos();
//Now the actual drawing begins.
if(valuePerPixel>0){
//Draw the line the slider moves along.
drawGUIBox(x+left,y+top+(height-4)/2,width,4,screen,0);
renderScrollBarButton(2,x-1+(int)thumbStart,y+top+(height/4),x+1+(int)thumbEnd,y+top+(height/4)*3,16,16);
}else{
//There are two buttons so draw them.
int f=left+width/2;
renderScrollBarButton(1,x+left,y+top,x+1+f,y+top+height,48,0);
renderScrollBarButton(5,x+f,y+top,x+left+width,y+top+height,64,0);
}
-
- //Get the absolute position.
- 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(x,y,draw);
- }
}
diff --git a/src/GUISlider.h b/src/GUISlider.h
index 95ebf12..eb7af89 100644
--- a/src/GUISlider.h
+++ b/src/GUISlider.h
@@ -1,89 +1,89 @@
/*
* 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/>.
*/
#ifndef GUISLIDER_H
#define GUISLIDER_H
#include "GUIObject.h"
class GUISlider:public GUIObject{
public:
//The minimum value of the slider.
int minValue;
//The maximum value of the slider.
int maxValue;
//The step size when a large step is made.
int largeChange;
private:
//
float thumbStart;
//
float thumbEnd;
//Float containing the value per pixel factor.
float valuePerPixel;
//Float containing the start position when dragging the scrollbar.
float startDragPos;
int criticalValue;
int timer;
//Boolean if the scrollbar position has changed.
bool changed;
//Method that will calculate the position of the scrollbar.
void calcPos();
//Method that will render a scrollbar button.
//index:
//x1:
//y1:
//x2:
//y2:
//srcLeft:
//srcRight:
void renderScrollBarButton(int index,int x1,int y1,int x2,int y2,int srcLeft,int srcTop);
public:
GUISlider(int left=0,int top=0,int width=0,int height=0,
int value=0,int minValue=0,int maxValue=100,int largeChange=50,
bool enabled=true,bool visible=true):
- GUIObject(left,top,width,height,0,NULL,value,enabled,visible),
+ GUIObject(left,top,width,height,NULL,value,enabled,visible),
minValue(minValue),maxValue(maxValue),largeChange(largeChange),
thumbStart(0.0f),thumbEnd(0.0f),valuePerPixel(0.0f),startDragPos(0.0f),criticalValue(0),timer(0),changed(false)
{
//In the constructor we simply call calcPos().
calcPos();
}
//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(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)
virtual void render(int x=0,int y=0,bool draw=true);
};
#endif
diff --git a/src/GUISpinBox.cpp b/src/GUISpinBox.cpp
index a79f43f..eeb0a18 100644
--- a/src/GUISpinBox.cpp
+++ b/src/GUISpinBox.cpp
@@ -1,402 +1,390 @@
/*
* 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 "GUISpinBox.h"
bool GUISpinBox::handleEvents(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-gravityX;
y+=top;
//Reset "key" to stop contant update of "number" in render().
//If the mouse is still on the button, the "key" will be reassigned later.
key=-1;
//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.
int key=(int)event.key.keysym.unicode;
//Check if the key is supported.
if(key>=32&&key<=126){
//Add the key to the text after the carrot.
caption.insert((size_t)value,1,char(key));
value=clamp(value+1,0,caption.length());
//If there is an event callback then call it.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
}else if(event.key.keysym.sym==SDLK_BACKSPACE){
//We need to remove a character so first make sure that there is text.
if(caption.length()>0&&value>0){
//Remove the character before the carrot.
value=clamp(value-1,0,caption.length());
caption.erase((size_t)value,1);
this->key=SDLK_BACKSPACE;
keyHoldTime=0;
keyTime=5;
//If there is an event callback then call it.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
}
}else if(event.key.keysym.sym==SDLK_DELETE){
//We need to remove a character so first make sure that there is text.
if(caption.length()>0){
//Remove the character after the carrot.
value=clamp(value,0,caption.length());
caption.erase((size_t)value,1);
this->key=SDLK_DELETE;
keyHoldTime=0;
keyTime=5;
//If there is an event callback then call it.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
}
}else if(event.key.keysym.sym==SDLK_RIGHT){
value=clamp(value+1,0,caption.length());
this->key=SDLK_RIGHT;
keyHoldTime=0;
keyTime=5;
}else if(event.key.keysym.sym==SDLK_LEFT){
value=clamp(value-1,0,caption.length());
this->key=SDLK_LEFT;
keyHoldTime=0;
keyTime=5;
}
//The event has been processed.
b=true;
}else if(state==2 && event.type==SDL_KEYUP && !b){
//Check if released key is the same as the holded key.
if(event.key.keysym.sym==key){
//It is so stop the key.
key=-1;
}
}
//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 GUIObject.
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.
if(i<x+width-16)
currentCursor=CURSOR_CARROT;
//Check for a mouse button press.
if(k&SDL_BUTTON(1)){
//We have focus.
state=2;
//Handle buttons.
if(i>x+width-16){
if(j<y+17){
//Set the key values correct.
this->key=SDLK_UP;
keyHoldTime=0;
keyTime=5;
//Update once to prevent a lag.
updateValue(true);
}else{
//Set the key values correct.
this->key=SDLK_DOWN;
keyHoldTime=0;
keyTime=5;
//Update once to prevent a lag.
updateValue(false);
}
}else{
//Move carrot to the place clicked
int click=i-x;
if(!cache){
value=0;
}else if(click>cache->w){
value=caption.length();
}else{
unsigned int wid=0;
for(unsigned int i=0;i<caption.length();i++){
int advance;
TTF_GlyphMetrics(fontText,caption[i],NULL,NULL,NULL,NULL,&advance);
wid+=advance;
if(click<(int)wid-(int)advance/2){
value=i;
break;
}
}
}
}
}
//Allow mouse wheel to change value.
if(event.type==SDL_MOUSEBUTTONUP){
if(event.button.button==SDL_BUTTON_WHEELUP){
updateValue(true);
}else if(event.button.button==SDL_BUTTON_WHEELDOWN){
updateValue(false);
}
}
}else{
//The mouse is outside the TextBox.
//If we don't have focus but only hover we lose it.
if(state==1){
state=0;
update();
}
//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;
update();
}
}
}
- //Also let the children handle their events.
- for(unsigned int i=0;i<childControls.size();i++){
- bool b1=childControls[i]->handleEvents(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 GUISpinBox::render(int x,int y,bool draw){
//FIXME: Logic in the render method since that is update constant.
if(key!=-1){
//Increase the key time.
keyHoldTime++;
//Make sure the deletionTime isn't to short.
if(keyHoldTime>=keyTime){
keyHoldTime=0;
keyTime--;
if(keyTime<1)
keyTime=1;
//Now check the which key it was.
switch(key){
case SDLK_UP:
{
updateValue(true);
break;
}
case SDLK_DOWN:
{
updateValue(false);
break;
}
case SDLK_BACKSPACE:
{
//Remove the character before the carrot.
value=clamp(value-1,0,caption.length());
caption.erase((size_t)value,1);
break;
}
case SDLK_DELETE:
{
//Remove the character after the carrot.
value=clamp(value,0,caption.length());
caption.erase((size_t)value,1);
break;
}
case SDLK_LEFT:
{
value=clamp(value-1,0,caption.length());
break;
}
case SDLK_RIGHT:
{
value=clamp(value+1,0,caption.length());
break;
}
}
}
}
//Rectangle.
SDL_Rect r;
//There's no need drawing 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.
SDL_FreeSurface(cache);
cache=NULL;
//And cache the new values.
cachedEnabled=enabled;
cachedCaption=caption;
//Finally resize the widget
if(autoWidth)
width=-1;
}
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 a background box.
Uint32 color=0xFFFFFF00|clr;
drawGUIBox(x,y,width,height,screen,color);
//Draw arrow buttons.
r.y=y+1;
r.x=x+width-18;
SDL_Rect r1={80,0,16,16};
SDL_BlitSurface(bmGUI,&r1,screen,&r);
r.y+=16;
r1.x=96;
SDL_BlitSurface(bmGUI,&r1,screen,&r);
}
if(!caption.empty()){
//Update graphic cache if empty.
if(!cache){
SDL_Color black={0,0,0,0};
cache=TTF_RenderUTF8_Blended(fontText,caption.c_str(),black);
}
if(draw){
r.x=x+2;
r.y=y+(height-cache->h)/2;
//Draw the text.
SDL_Rect tmp={0,0,width-2,25};
SDL_BlitSurface(cache,&tmp,screen,&r);
//Only draw the carrot when focus.
if(state==2){
r.x=x;
r.y=y+4;
r.w=2;
r.h=height-8;
int advance;
for(int n=0;n<value;n++){
TTF_GlyphMetrics(fontText,caption[n],NULL,NULL,NULL,NULL,&advance);
r.x+=advance;
}
//Make sure that the carrot is inside the textbox.
if(r.x<x+width)
SDL_FillRect(screen,&r,0);
}
}else{
//Only draw the carrot when focus.
if(state==2&&draw){
r.x=x+4;
r.y=y+4;
r.w=2;
r.h=height-8;
SDL_FillRect(screen,&r,0);
}
}
}
-
- //We now need to draw all the children.
- for(unsigned int i=0;i<childControls.size();i++){
- childControls[i]->render(x,y,draw);
- }
}
void GUISpinBox::update(){
//Read number from the caption string.
float number=(float)atof(caption.c_str());
//Stay in the limits.
if(number>limitMax){
number=limitMax;
}else if(number<limitMin){
number=limitMin;
}
//Write the number to the caption string.
char str[32];
sprintf(str,format,number);
caption=str;
}
void GUISpinBox::updateValue(bool positive){
//Read number from the caption string.
float number=(float)atof(caption.c_str());
//Apply change.
if(positive)
number+=change;
else
number-=change;
//Stay in the limits.
if(number>limitMax){
number=limitMax;
}else if(number<limitMin){
number=limitMin;
}
//Write the number to the caption string.
char str[32];
sprintf(str,format,number);
caption=str;
}
diff --git a/src/GUISpinBox.h b/src/GUISpinBox.h
index fea6ef5..945f6f7 100644
--- a/src/GUISpinBox.h
+++ b/src/GUISpinBox.h
@@ -1,61 +1,70 @@
/*
* 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 GUISPINBOX_H
#define GUISPINBOX_H
#include "GUIObject.h"
class GUISpinBox:public GUIObject{
public:
GUISpinBox(int left=0,int top=0,int width=0,int height=0,
bool enabled=true,bool visible=true):
- GUIObject(left,top,width,height,0,NULL,0,enabled,visible),
- change(1.0f),limitMax(100),limitMin(-100),format("%-.2f"){ };
+ GUIObject(left,top,width,height,NULL,0,enabled,visible),
+ change(1.0f),limitMax(100),limitMin(-100),format("%-.2f"),
+ key(-1),keyHoldTime(0),keyTime(0){ };
//Amount of single change.
float change;
//Widget's value stays between these values.
float limitMax,limitMin;
//Standard C printf format used for displaying the number.
const char* format;
//Method to update widget's value.
void update();
//Method to change widget's value.
//positive: Boolean if add or remove change.
void updateValue(bool positive);
//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(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)
virtual void render(int x=0,int y=0,bool draw=true);
+private:
+ //Integer containing the key that is holded.
+ int key;
+
+ //Integer containing the time the key is pressed.
+ int keyHoldTime;
+ //The time it takes to invoke the key action again.
+ int keyTime;
};
#endif
diff --git a/src/GUITextArea.cpp b/src/GUITextArea.cpp
index c018555..20952e7 100644
--- a/src/GUITextArea.cpp
+++ b/src/GUITextArea.cpp
@@ -1,741 +1,744 @@
/*
* 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 "GUITextArea.h"
#include <cmath>
using namespace std;
GUITextArea::GUITextArea(int left,int top,int width,int height,bool enabled,bool visible):
- GUIObject(left,top,width,height,0,NULL,-1,enabled,visible),editable(true){
+ GUIObject(left,top,width,height,NULL,-1,enabled,visible),editable(true){
+
+ key=-1;
+ keyHoldTime=keyTime=0;
//Set some default values.
state=value=currentLine=0;
setFont(fontText);
//Add empty text.
lines.push_back("");
linesCache.push_back(NULL);
//Create scrollbar widget.
scrollBar=new GUIScrollBar(width-16,0,16,height,1,0,0,0);
childControls.push_back(scrollBar);
scrollBarH=new GUIScrollBar(0,height-16,width-16,16,0,0,0,0,100,500,true,false);
childControls.push_back(scrollBarH);
}
GUITextArea::~GUITextArea(){
//Free cached images.
for(unsigned int i=0;i<linesCache.size();i++){
SDL_FreeSurface(linesCache[i]);
}
linesCache.clear();
}
void GUITextArea::setFont(TTF_Font* font){
//NOTE: This fuction shouldn't be called after adding items, so no need to update the whole cache.
widgetFont=font;
fontHeight=TTF_FontHeight(font)+1;
}
bool GUITextArea::handleEvents(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;
//Update the vertical scrollbar.
b=b||scrollBar->handleEvents(x,y,enabled,visible,b);
if(!editable)
currentLine=scrollBar->value;
//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 && editable){
//Get the keycode.
int key=(int)event.key.keysym.unicode;
//Check if the key is supported.
if(key>=32&&key<=126){
//Add the key to the string.
string* str=&lines.at(currentLine);
str->insert((size_t)value,1,char(key));
value++;
//Update cache.
SDL_Surface** c=&linesCache.at(currentLine);
if(*c) SDL_FreeSurface(*c);
SDL_Color black={0,0,0,0};
*c=TTF_RenderUTF8_Blended(widgetFont,str->c_str(),black);
//Update view if needed.
adjustView();
//If there is an event callback then call it.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
}else if(event.key.keysym.sym==SDLK_BACKSPACE){
//Set the key values correct.
this->key=SDLK_BACKSPACE;
keyHoldTime=0;
keyTime=5;
//Delete one character direct to prevent a lag.
backspaceChar();
}else if(event.key.keysym.sym==SDLK_DELETE){
//Set the key values correct.
this->key=SDLK_DELETE;
keyHoldTime=0;
keyTime=5;
//Delete one character direct to prevent a lag.
deleteChar();
}else if(event.key.keysym.sym==SDLK_RETURN){
//Split the current line and update.
string str2=lines.at(currentLine).substr(value);
lines.at(currentLine)=lines.at(currentLine).substr(0,value);
SDL_Surface** c=&linesCache.at(currentLine);
if(*c) SDL_FreeSurface(*c);
SDL_Color black={0,0,0,0};
*c=TTF_RenderUTF8_Blended(widgetFont,lines.at(currentLine).c_str(),black);
//Add the rest in a new line.
currentLine++;
value=0;
lines.insert(lines.begin()+currentLine,str2);
SDL_Surface* c2;
c2=TTF_RenderUTF8_Blended(widgetFont,str2.c_str(),black);
linesCache.insert(linesCache.begin()+currentLine,c2);
//Adjust view.
adjustView();
//If there is an event callback then call it.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
}else if(event.key.keysym.sym==SDLK_TAB){
//Add a tabulator or here just 2 spaces to the string.
string* str=&lines.at(currentLine);
str->insert((size_t)value,2,char(' '));
value+=2;
//Update cache.
SDL_Surface** c=&linesCache.at(currentLine);
if(*c) SDL_FreeSurface(*c);
SDL_Color black={0,0,0,0};
*c=TTF_RenderUTF8_Blended(widgetFont,str->c_str(),black);
//Adjust view.
adjustView();
}else if(event.key.keysym.sym==SDLK_RIGHT){
//Set the key values correct.
this->key=SDLK_RIGHT;
keyHoldTime=0;
keyTime=5;
//Move the carrot once to prevent a lag.
moveCarrotRight();
}else if(event.key.keysym.sym==SDLK_LEFT){
//Set the key values correct.
this->key=SDLK_LEFT;
keyHoldTime=0;
keyTime=5;
//Move the carrot once to prevent a lag.
moveCarrotLeft();
}else if(event.key.keysym.sym==SDLK_DOWN){
//Set the key values correct.
this->key=SDLK_DOWN;
keyHoldTime=0;
keyTime=5;
//Move the carrot once to prevent a lag.
moveCarrotDown();
}else if(event.key.keysym.sym==SDLK_UP){
//Set the key values correct.
this->key=SDLK_UP;
keyHoldTime=0;
keyTime=5;
//Move the carrot once to prevent a lag.
moveCarrotUp();
}
//The event has been processed.
b=true;
}else if(state==2 && event.type==SDL_KEYUP && !b){
//Check if released key is the same as the holded key.
if(event.key.keysym.sym==key){
//It is so stop the key.
key=-1;
}
}
//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 GUIObject.
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;
}
//Check for mouse wheel scrolling.
//Scroll horizontally if mouse is over the horizontal scrollbar.
//Otherwise scroll vertically.
if(j>=y+height-16&&scrollBarH->visible){
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_WHEELDOWN){
scrollBarH->value+=20;
if(scrollBarH->value>scrollBarH->maxValue)
scrollBarH->value=scrollBarH->maxValue;
}else if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_WHEELUP){
scrollBarH->value-=20;
if(scrollBarH->value<0)
scrollBarH->value=0;
}
}else{
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_WHEELDOWN){
scrollBar->value++;
if(scrollBar->value>scrollBar->maxValue)
scrollBar->value=scrollBar->maxValue;
}else if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_WHEELUP){
scrollBar->value--;
if(scrollBar->value<0)
scrollBar->value=0;
}
}
//When mouse is not over the scrollbar.
if(i<x+width-16&&j<(scrollBarH->visible?y+height-16:y+height)&&editable){
//Update the cursor type.
currentCursor=CURSOR_CARROT;
//Check for a mouse button press.
if(k&SDL_BUTTON(1)){
//We have focus.
state=2;
//Move carrot to the place clicked.
currentLine=clamp((int)floor(float(j-y)/float(fontHeight))+scrollBar->value,0,lines.size()-1);
string* str=&lines.at(currentLine);
value=str->length();
int clickX=i-x+scrollBarH->value;
int xPos=0;
for(unsigned int i=0;i<str->length();i++){
int advance;
TTF_GlyphMetrics(widgetFont,str->at(i),NULL,NULL,NULL,NULL,&advance);
xPos+=advance;
if(clickX<xPos-advance/2){
value=i;
break;
}
}
}
}
}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;
}
}
}
//Process child controls event except for the scrollbar.
//That's why i starts at one.
for(unsigned int i=1;i<childControls.size();i++){
bool b1=childControls[i]->handleEvents(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 GUITextArea::deleteChar(){
//Remove a character after the carrot.
if(value<(int)lines.at(currentLine).length()){
//Normal delete inside of a line.
//Update string.
string* str=&lines.at(currentLine);
str->erase((size_t)value,1);
//Update cache.
SDL_Surface** c=&linesCache.at(currentLine);
if(*c) SDL_FreeSurface(*c);
SDL_Color black={0,0,0,0};
*c=TTF_RenderUTF8_Blended(widgetFont,str->c_str(),black);
}else{
//Make sure there's a line after currentLine.
if(currentLine<(int)lines.size()-1){
//Append next line.
string* str=&lines.at(currentLine);
str->append(lines.at(currentLine+1));
//Remove the unused line.
SDL_Surface** c=&linesCache.at(currentLine+1);
if(*c) SDL_FreeSurface(*c);
lines.erase(lines.begin()+currentLine+1);
linesCache.erase(linesCache.begin()+currentLine+1);
//Update cache.
c=&linesCache.at(currentLine);
if(*c) SDL_FreeSurface(*c);
SDL_Color black={0,0,0,0};
*c=TTF_RenderUTF8_Blended(widgetFont,str->c_str(),black);
}
}
//Adjust view.
adjustView();
//If there is an event callback.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
}
void GUITextArea::backspaceChar(){
//Remove a character before the carrot.
value--;
if(value<0){
//Remove a line but append it's content to the previous.
//However we can't do this to the first line.
if(currentLine>0){
//Remove line from display but store the string.
string str=lines.at(currentLine);
lines.erase(lines.begin()+currentLine);
SDL_Surface** c=&linesCache.at(currentLine);
if(*c) SDL_FreeSurface(*c);
linesCache.erase(linesCache.begin()+currentLine);
//Append that string to the previous line.
currentLine--;
string* str2=&lines.at(currentLine);
value=str2->length();
str2->append(str);
//Update cache.
c=&linesCache.at(currentLine);
if(*c) SDL_FreeSurface(*c);
SDL_Color black={0,0,0,0};
*c=TTF_RenderUTF8_Blended(widgetFont,str2->c_str(),black);
}else{
//Don't let the value become negative.
value=0;
}
}else{
//Normal delete inside of a line.
//Update string.
string* str=&lines.at(currentLine);
str->erase((size_t)value,1);
//Update cache.
SDL_Surface** c=&linesCache.at(currentLine);
if(*c) SDL_FreeSurface(*c);
SDL_Color black={0,0,0,0};
*c=TTF_RenderUTF8_Blended(widgetFont,str->c_str(),black);
}
//Adjust view.
adjustView();
//If there is an event callback.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
}
void GUITextArea::moveCarrotRight(){
//Move carrot.
value++;
//Check if over the current line.
if(value>(int)lines.at(currentLine).length()){
//Check if the last line.
if(currentLine==lines.size()-1){
value=lines.at(currentLine).length();
}else{
//Can move to the next line.
currentLine++;
value=0;
}
}
//Adjust view.
adjustView();
}
void GUITextArea::moveCarrotLeft(){
//Move carrot.
value--;
//Check if below the current line.
if(value<0){
//Check if the first line.
if(currentLine==0){
value=0;
}else{
//Can move to the previous line.
currentLine--;
value=lines.at(currentLine).length();
}
}
//Adjust view.
adjustView();
}
void GUITextArea::moveCarrotUp(){
if(currentLine==0){
value=0;
}else{
//Calculate carrot x position.
int carrotX=0;
for(int n=0;n<value;n++){
int advance;
TTF_GlyphMetrics(widgetFont,lines.at(currentLine).at(n),NULL,NULL,NULL,NULL,&advance);
carrotX+=advance;
}
//Find out closest match.
currentLine--;
string* str=&lines.at(currentLine);
value=str->length();
int xPos=0;
for(unsigned int i=0;i<str->length();i++){
int advance;
TTF_GlyphMetrics(widgetFont,str->at(i),NULL,NULL,NULL,NULL,&advance);
xPos+=advance;
if(carrotX<xPos-advance/2){
value=i;
break;
}
}
}
//Adjust view.
adjustView();
}
void GUITextArea::moveCarrotDown(){
if(currentLine==lines.size()-1){
value=lines.at(currentLine).length();
}else{
//Calculate carrot x position.
int carrotX=0;
for(int n=0;n<value;n++){
int advance;
TTF_GlyphMetrics(widgetFont,lines.at(currentLine).at(n),NULL,NULL,NULL,NULL,&advance);
carrotX+=advance;
}
//Find out closest match.
currentLine++;
string* str=&lines.at(currentLine);
value=str->length();
int xPos=0;
for(unsigned int i=0;i<str->length();i++){
int advance;
TTF_GlyphMetrics(widgetFont,str->at(i),NULL,NULL,NULL,NULL,&advance);
xPos+=advance;
if(carrotX<xPos-advance/2){
value=i;
break;
}
}
}
//Adjust view.
adjustView();
}
void GUITextArea::adjustView(){
//Adjust view to current line.
if(fontHeight*(currentLine-scrollBar->value)+4>height-4)
scrollBar->value=currentLine-3;
else if(currentLine-scrollBar->value<0)
scrollBar->value=currentLine;
//Find out the lenght of the longest line.
int maxWidth=0;
for(vector<SDL_Surface*>::iterator it=linesCache.begin();it!=linesCache.end();++it){
if((*it)&&(*it)->w>width-16&&(*it)->w>maxWidth)
maxWidth=(*it)->w;
}
//We need the horizontal scrollbar if any line is too long.
if(maxWidth>0){
scrollBar->height=height-16;
scrollBarH->visible=true;
scrollBarH->maxValue=maxWidth-width+24;
}else{
scrollBar->height=height;
scrollBarH->visible=false;
scrollBarH->value=0;
scrollBarH->maxValue=0;
}
//Adjust the horizontal view.
int carrotX=0;
for(int n=0;n<value;n++){
int advance;
TTF_GlyphMetrics(widgetFont,lines.at(currentLine).at(n),NULL,NULL,NULL,NULL,&advance);
carrotX+=advance;
}
if(carrotX>width-24)
scrollBarH->value=scrollBarH->maxValue;
else
scrollBarH->value=0;
//Update vertical scrollbar.
int rh=height-(scrollBarH->visible?16:0);
int m=lines.size(),n=(int)floor((float)rh/(float)fontHeight);
if(m>n){
scrollBar->maxValue=m-n;
scrollBar->smallChange=1;
scrollBar->largeChange=n;
}else{
scrollBar->value=0;
scrollBar->maxValue=0;
}
}
void GUITextArea::render(int x,int y,bool draw){
//FIXME: Logic in the render method since that is update constant.
if(key!=-1){
//Increase the key time.
keyHoldTime++;
//Make sure the deletionTime isn't to short.
if(keyHoldTime>=keyTime){
keyHoldTime=0;
keyTime--;
if(keyTime<1)
keyTime=1;
//Now check the which key it was.
switch(key){
case SDLK_BACKSPACE:
backspaceChar();
break;
case SDLK_DELETE:
deleteChar();
break;
case SDLK_LEFT:
moveCarrotLeft();
break;
case SDLK_RIGHT:
moveCarrotRight();
break;
case SDLK_UP:
moveCarrotUp();
break;
case SDLK_DOWN:
moveCarrotDown();
break;
}
}
}
//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 box.
Uint32 color=0xFFFFFFFF;
drawGUIBox(x,y,width,height,screen,color);
//Draw text.
int lineY=0;
for(std::vector<SDL_Surface*>::iterator it=linesCache.begin()+scrollBar->value;it!=linesCache.end();++it){
if(*it){
if(lineY<height){
SDL_Rect r={scrollBarH->value,0,width-17,(*it)->h};
int over=-height+lineY+fontHeight;
if(over>0) r.h-=over;
applySurface(x+1,y+1+lineY,*it,screen,&r);
}else{
break;
}
}
lineY+=fontHeight;
}
//Only draw the carrot when focus.
if(state==2&&editable){
SDL_Rect r;
r.x=x-scrollBarH->value;
r.y=y+4+fontHeight*(currentLine-scrollBar->value);
r.w=2;
r.h=fontHeight-4;
//Make sure that the carrot is inside the textbox.
if((r.y<y+height-4)&&(r.y>y)){
//Calculate position for the carrot.
for(int n=0;n<value;n++){
int advance;
TTF_GlyphMetrics(widgetFont,lines.at(currentLine).at(n),NULL,NULL,NULL,NULL,&advance);
r.x+=advance;
}
//Draw the carrot.
if(r.x>x-1&&r.x<x+width-16)
SDL_FillRect(screen,&r,0);
}
}
//We now need to draw all the children of the GUIObject.
for(unsigned int i=0;i<childControls.size();i++){
childControls[i]->render(x,y,draw);
}
}
void GUITextArea::setString(std::string input){
//Clear previous content if any.
//Delete every line.
lines.clear();
//Free cached images.
for(unsigned int i=0;i<linesCache.size();i++){
SDL_FreeSurface(linesCache[i]);
}
linesCache.clear();
size_t linePos=0,lineLen=0;
SDL_Color black={0,0,0,0};
SDL_Surface* bm=NULL;
//Loop through the input string.
for(size_t i=0;i<input.length();++i)
{
//Check when we come in end of a line.
if(input.at(i)=='\n'){
//Check if the line is empty.
if(lineLen==0){
lines.push_back("");
linesCache.push_back(NULL);
}else{
//Read the whole line.
string line=input.substr(linePos,lineLen);
lines.push_back(line);
//Render and cache text.
bm=TTF_RenderUTF8_Blended(widgetFont,line.c_str(),black);
linesCache.push_back(bm);
}
//Skip '\n' in end of the line.
linePos=i+1;
lineLen=0;
}else{
lineLen++;
}
}
//The string might not end with a newline.
//That's why we're going to add end rest of the string as one line.
string line=input.substr(linePos);
lines.push_back(line);
bm=TTF_RenderUTF8_Blended(widgetFont,line.c_str(),black);
linesCache.push_back(bm);
//Adjust view.
adjustView();
}
void GUITextArea::setStringArray(std::vector<std::string> input){
//Free cached images.
for(unsigned int i=0;i<linesCache.size();i++){
SDL_FreeSurface(linesCache[i]);
}
linesCache.clear();
//Copy values.
lines=input;
//Draw new strings.
SDL_Color black={0,0,0,0};
for(vector<string>::iterator it=lines.begin();it!=lines.end();++it){
SDL_Surface* bm=TTF_RenderUTF8_Blended(widgetFont,(*it).c_str(),black);
linesCache.push_back(bm);
}
//Adjust view.
adjustView();
}
string GUITextArea::getString(){
string tmp;
for(vector<string>::iterator it=lines.begin();it!=lines.end();++it){
//Append a newline only if not the first line.
if(it!=lines.begin())
tmp.append(1,'\n');
//Append the line.
tmp.append(*it);
}
return tmp;
}
void GUITextArea::resize(){
scrollBar->left=width-16;
scrollBar->height=height;
if(scrollBarH->visible)
scrollBar->height-=16;
scrollBarH->top=height-16;
scrollBarH->width=width-16;
adjustView();
}
diff --git a/src/GUITextArea.h b/src/GUITextArea.h
index 19af7c5..2395743 100644
--- a/src/GUITextArea.h
+++ b/src/GUITextArea.h
@@ -1,114 +1,122 @@
/*
* 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 GUITEXTAREA_H
#define GUITEXTAREA_H
#ifdef __APPLE__
#include <SDL_ttf/SDL_ttf.h>
#else
#include <SDL/SDL_ttf.h>
#endif
#include "GUIObject.h"
#include "GUIScrollBar.h"
//GUIObject based widget for multiline text input.
//It extends GUIObject because it's a special GUIObject.
class GUITextArea:public GUIObject{
private:
//Method that will remove the last character of the text.
void backspaceChar();
void deleteChar();
//Methods to move the carrot by one character/line.
void moveCarrotLeft();
void moveCarrotRight();
void moveCarrotUp();
void moveCarrotDown();
//Method to adjust view so carrot stays visible.
void adjustView();
//Pointer to the font used in the widget.
TTF_Font* widgetFont;
//Widget's text.
//One line per vector element.
std::vector<std::string> lines;
//Cache for rendered lines.
//Will be updated alongside with variable text.
std::vector<SDL_Surface*> linesCache;
//Variable for carrot position.
//NOTE: We will use variable "value" from GUIObject for position within the current line.
int currentLine;
//Height of the font.
int fontHeight;
//Scrollbar widget.
GUIScrollBar* scrollBar;
GUIScrollBar* scrollBarH;
+
+ //Integer containing the key that is holded.
+ int key;
+
+ //Integer containing the time the key is pressed.
+ int keyHoldTime;
+ //The time it takes to invoke the key action again.
+ int keyTime;
public:
//Constructor.
//left: The relative x location of the GUITextArea.
//top: The relative y location of the GUITextArea.
//witdh: The width of the GUITextArea.
//height: The height of the GUITextArea.
//enabled: Boolean if the GUITextArea is enabled or not.
//visible: Boolean if the GUITextArea is visisble or not.
GUITextArea(int left=0,int top=0,int width=0,int height=0,bool enabled=true,bool visible=true);
//Destructor
~GUITextArea();
//Method used to change the font.
//font: Pointer to the font
void setFont(TTF_Font* font);
//Method used to reposition scrollbars after a resize.
void resize();
//Method used to get widget's text in a single string.
std::string getString();
//Method used to set widget's text.
void setString(std::string input);
void setStringArray(std::vector<std::string> input);
//Bool if user can edit text in the widget.
bool editable;
//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(int x=0,int y=0,bool enabled=true,bool visible=true,bool processed=false);
//Method that will render the GUITextArea.
//x: The x location to draw the GUITextArea. (x+left)
//y: The y location to draw the GUITextArea. (y+top)
virtual void render(int x=0,int y=0,bool draw=true);
};
#endif
diff --git a/src/GUIWindow.cpp b/src/GUIWindow.cpp
index bab85b8..080a02d 100644
--- a/src/GUIWindow.cpp
+++ b/src/GUIWindow.cpp
@@ -1,297 +1,297 @@
/*
* 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 "GUIWindow.h"
using namespace std;
GUIWindow::GUIWindow(int left,int top,int width,int height,bool enabled,bool visible,const char* caption):
- GUIObject(left,top,width,height,0,caption,-1,enabled,visible){
+ GUIObject(left,top,width,height,caption,-1,enabled,visible){
//Set some default values.
dragging=false;
resizing=false;
minWidth=minHeight=0;
maxWidth=maxHeight=0;
}
bool GUIWindow::handleEvents(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){
//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 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 && checkCollision(mouse,titlebar)){
//Mouse pressed inside the window,so assume dragging
dragging=true;
}
//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):checkCollision(mouse,edge);
edge.x=x+width-5;
edge.w=5;
edge.h=height;
bool rightEdge=resizing?(resizeDirection==GUIResizeRight || resizeDirection==GUIResizeTopRight || resizeDirection==GUIResizeBottomRight):checkCollision(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):checkCollision(mouse,edge);
edge.y=y;
edge.w=5;
edge.h=height;
bool leftEdge=resizing?(resizeDirection==GUIResizeLeft || resizeDirection==GUIResizeTopLeft || resizeDirection==GUIResizeBottomLeft):checkCollision(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(checkCollision(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(unsigned int i=0;i<childControls.size();i++){
bool b1=childControls[i]->handleEvents(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 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;
}
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;
}
//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(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,screen,color);
//Draw the titlebar.
color=0x00000033;
drawGUIBox(x,y,width,48,screen,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
SDL_Rect r={left+width-36,top+12,24,24};
if(checkCollision(mouse,r)){
drawGUIBox(r.x,r.y,r.w,r.h,screen,0x999999FFU);
}
SDL_Rect r1={112,0,16,16};
applySurface(left+width-32,top+16,bmGUI,screen,&r1);
}
//Draw the caption.
{
SDL_Rect captionRect={6,8,width-16,32};
//The color black.
SDL_Color black={0,0,0,0};
SDL_Surface* bm=TTF_RenderUTF8_Blended(fontGUI,caption.c_str(),black);
applySurface(x+captionRect.x+(captionRect.w-bm->w)/2,
y+captionRect.y+(captionRect.h-bm->h)/2,bm,screen,NULL);
SDL_FreeSurface(bm);
}
//We now need to draw all the children of the GUIObject.
for(unsigned int i=0;i<childControls.size();i++){
childControls[i]->render(x,y,draw);
}
}
void GUIWindow::GUIEventCallback_OnEvent(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(name,this,eventType);
}
}
diff --git a/src/Game.cpp b/src/Game.cpp
index f601552..d896775 100644
--- a/src/Game.cpp
+++ b/src/Game.cpp
@@ -1,1666 +1,1666 @@
/*
* Copyright (C) 2011-2013 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 "GameState.h"
#include "Globals.h"
#include "Functions.h"
#include "FileManager.h"
#include "GameObjects.h"
#include "ThemeManager.h"
#include "Objects.h"
#include "Game.h"
#include "TreeStorageNode.h"
#include "POASerializer.h"
#include "InputManager.h"
#include "StatisticsManager.h"
#include <fstream>
#include <iostream>
#include <sstream>
#include <vector>
#include <map>
#include <algorithm>
#include <locale>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "libs/tinyformat/tinyformat.h"
using namespace std;
const char* Game::blockName[TYPE_MAX]={"Block","PlayerStart","ShadowStart",
"Exit","ShadowBlock","Spikes",
"Checkpoint","Swap","Fragile",
"MovingBlock","MovingShadowBlock","MovingSpikes",
"Teleporter","Button","Switch",
"ConveyorBelt","ShadowConveyorBelt","NotificationBlock", "Collectable", "Pushable"
};
map<string,int> Game::blockNameMap;
map<int,string> Game::gameObjectEventTypeMap;
map<string,int> Game::gameObjectEventNameMap;
map<int,string> Game::levelEventTypeMap;
map<string,int> Game::levelEventNameMap;
string Game::recordFile;
Game::Game():isReset(false)
,currentLevelNode(NULL)
,customTheme(NULL)
,background(NULL)
,won(false)
,interlevel(false)
,gameTipIndex(0)
,time(0),timeSaved(0)
,recordings(0),recordingsSaved(0)
,cameraMode(CAMERA_PLAYER),cameraModeSaved(CAMERA_PLAYER)
,player(this),shadow(this),objLastCheckPoint(NULL),
medalX(0),currentCollectables(0),totalCollectables(0),currentCollectablesSaved(0){
saveStateNextTime=false;
loadStateNextTime=false;
recentSwap=recentSwapSaved=-10000;
recentLoad=recentSave=0;
//Reserve the memory for the GameObject tips.
memset(bmTips,0,sizeof(bmTips));
action=loadImage(getDataPath()+"gfx/actions.png");
medals=loadImage(getDataPath()+"gfx/medals.png");
//Get the collectable image from the theme.
//NOTE: Isn't there a better way to retrieve the image?
objThemes.getBlock(TYPE_COLLECTABLE)->createInstance(&collectable);
//Hide the cursor if not in the leveleditor.
if(stateID!=STATE_LEVEL_EDITOR)
SDL_ShowCursor(SDL_DISABLE);
}
Game::~Game(){
//Simply call our destroy method.
destroy();
//Before we leave make sure the cursor is visible.
SDL_ShowCursor(SDL_ENABLE);
}
void Game::destroy(){
//Loop through the levelObjects and delete them.
for(unsigned int i=0;i<levelObjects.size();i++)
delete levelObjects[i];
//Done now clear the levelObjects vector.
levelObjects.clear();
//Loop through the backgroundLayers and delete them.
std::map<std::string,std::vector<Scenery*> >::iterator it;
for(it=backgroundLayers.begin();it!=backgroundLayers.end();++it){
for(unsigned int i=0;i<it->second.size();i++)
delete it->second[i];
}
backgroundLayers.clear();
//Clear the name and the editor data.
levelName.clear();
levelFile.clear();
editorData.clear();
//Loop through the tips.
for(int i=0;i<TYPE_MAX;i++){
//If it exist free the SDL_Surface.
if(bmTips[i])
SDL_FreeSurface(bmTips[i]);
}
memset(bmTips,0,sizeof(bmTips));
//Remove everything from the themeManager.
background=NULL;
if(customTheme)
objThemes.removeTheme();
customTheme=NULL;
//If there's a (partial) theme bundled with the levelpack remove that as well.
if(levels->customTheme)
objThemes.removeTheme();
//delete current level (if any)
if(currentLevelNode){
delete currentLevelNode;
currentLevelNode=NULL;
}
//Reset the time.
time=timeSaved=0;
recordings=recordingsSaved=0;
recentSwap=recentSwapSaved=-10000;
}
void Game::loadLevelFromNode(TreeStorageNode* obj,const string& fileName){
//Make sure there's nothing left from any previous levels.
//Not needed since loadLevelFromNode is only called from the changeState method, meaning it's a new instance of Game.
//destroy();
//set current level to loaded one.
currentLevelNode=obj;
//Temp var used for block locations.
SDL_Rect box;
//Set the level dimensions to the default, it will probably be changed by the editorData,
//but 800x600 is a fallback.
LEVEL_WIDTH=800;
LEVEL_HEIGHT=600;
currentCollectables=0;
totalCollectables=0;
currentCollectablesSaved=0;
//Load the additional data.
for(map<string,vector<string> >::iterator i=obj->attributes.begin();i!=obj->attributes.end();++i){
if(i->first=="size"){
//We found the size attribute.
if(i->second.size()>=2){
//Set the dimensions of the level.
LEVEL_WIDTH=atoi(i->second[0].c_str());
LEVEL_HEIGHT=atoi(i->second[1].c_str());
}
}else if(i->second.size()>0){
//Any other data will be put into the editorData.
editorData[i->first]=i->second[0];
}
}
//Get the theme.
{
//If a theme is configured then load it.
string theme=processFileName(getSettings()->getValue("theme"));
//Check if level themes are enabled.
if(getSettings()->getBoolValue("leveltheme")) {
//Check for the theme to use.
string &s=editorData["theme"];
if(!s.empty()){
customTheme=objThemes.appendThemeFromFile(processFileName(s)+"/theme.mnmstheme");
}
//Also check for bundled (partial) themes.
if(levels->customTheme){
if(objThemes.appendThemeFromFile(levels->levelpackPath+"/theme/theme.mnmstheme")==NULL){
//The theme failed to load so set the customTheme boolean to false.
levels->customTheme=false;
}
}
}
//Set the Appearance of the player and the shadow.
objThemes.getCharacter(false)->createInstance(&player.appearance);
objThemes.getCharacter(true)->createInstance(&shadow.appearance);
}
for(unsigned int i=0;i<obj->subNodes.size();i++){
TreeStorageNode* obj1=obj->subNodes[i];
if(obj1==NULL) continue;
if(obj1->name=="tile"){
Block* block=new Block(this);
if(!block->loadFromNode(obj1)){
delete block;
continue;
}
//If the type is collectable, increase the number of totalCollectables
if(block->type==TYPE_COLLECTABLE)
totalCollectables++;
//Add the block to the levelObjects vector.
levelObjects.push_back(block);
}else if(obj1->name=="backgroundlayer" && obj1->value.size()==1){
//Loop through the sub nodes.
for(unsigned int j=0;j<obj1->subNodes.size();j++){
TreeStorageNode* obj2=obj1->subNodes[j];
if(obj2==NULL) continue;
if(obj2->name=="object"){
//Load the scenery from node.
Scenery* scenery=new Scenery(this);
if(!scenery->loadFromNode(obj2)){
delete scenery;
continue;
}
backgroundLayers[obj1->value[0]].push_back(scenery);
}
}
}else if(obj1->name=="script" && !obj1->value.empty()){
map<string,int>::iterator it=Game::levelEventNameMap.find(obj1->value[0]);
if(it!=Game::levelEventNameMap.end()){
int eventType=it->second;
const std::string& script=obj1->attributes["script"][0];
if(!script.empty()) scripts[eventType]=script;
}
}
}
//Close exits if there are collectables
if(totalCollectables>0){
for(unsigned int i=0;i<levelObjects.size();i++){
if(levelObjects[i]->type==TYPE_EXIT){
levelObjects[i]->onEvent(GameObjectEvent_OnSwitchOff);
}
}
}
//Set the levelName to the name of the current level.
levelName=editorData["name"];
levelFile=fileName;
//Some extra stuff only needed when not in the levelEditor.
if(stateID!=STATE_LEVEL_EDITOR){
//We create a text with the text "Level <levelno> <levelName>".
//It will be shown in the left bottom corner of the screen.
string s;
if(levels->getLevelCount()>1){
s=tfm::format(_("Level %d %s"),levels->getCurrentLevel()+1,_CC(levels->getDictionaryManager(),editorData["name"]));
}
SDL_Color fg={0,0,0,0};
bmTips[0]=TTF_RenderUTF8_Blended(fontText,s.c_str(),fg);
}
//Get the background
background=objThemes.getBackground(false);
if(background)
background->resetAnimation(true);
//Reset the script environment.
getScriptExecutor()->reset();
//Send GameObjectEvent_OnCreate event to the script
for(unsigned int i=0;i<levelObjects.size();i++){
levelObjects[i]->onEvent(GameObjectEvent_OnCreate);
}
//Finally call the level's onCreate event.
executeScript(LevelEvent_OnCreate);
}
void Game::loadLevel(string fileName){
//Create a TreeStorageNode that will hold the loaded data.
TreeStorageNode *obj=new TreeStorageNode();
{
POASerializer objSerializer;
string s=fileName;
//Parse the file.
if(!objSerializer.loadNodeFromFile(s.c_str(),obj,true)){
cout<<"Can't load level file "<<s<<endl;
delete obj;
return;
}
}
//Now call another function.
loadLevelFromNode(obj,fileName);
}
void Game::saveRecord(const char* fileName){
//check if current level is NULL (which should be impossible)
if(currentLevelNode==NULL) return;
TreeStorageNode obj;
POASerializer objSerializer;
//put current level to the node.
currentLevelNode->name="map";
obj.subNodes.push_back(currentLevelNode);
//serialize the game record using RLE compression.
#define PUSH_BACK \
if(j>0){ \
if(j>1){ \
sprintf(c,"%d*%d",last,j); \
}else{ \
sprintf(c,"%d",last); \
} \
v.push_back(c); \
}
vector<string> &v=obj.attributes["record"];
vector<int> *record=player.getRecord();
char c[64];
int i,j=0,last;
for(i=0;i<(int)record->size();i++){
int currentKey=(*record)[i];
if(j==0 || currentKey!=last){
PUSH_BACK;
last=currentKey;
j=1;
}else{
j++;
}
}
PUSH_BACK;
#undef PUSH_BACK
#ifdef RECORD_FILE_DEBUG
//add record file debug data.
{
obj.attributes["recordKeyPressLog"].push_back(player.keyPressLog());
vector<SDL_Rect> &playerPosition=player.playerPosition();
string s;
char c[32];
sprintf(c,"%d\n",int(playerPosition.size()));
s=c;
for(unsigned int i=0;i<playerPosition.size();i++){
SDL_Rect& r=playerPosition[i];
sprintf(c,"%d %d\n",r.x,r.y);
s+=c;
}
obj.attributes["recordPlayerPosition"].push_back(s);
}
#endif
//save it
objSerializer.saveNodeToFile(fileName,&obj,true,true);
//remove current level from node to prevent delete it.
obj.subNodes.clear();
}
void Game::loadRecord(const char* fileName){
//Create a TreeStorageNode that will hold the loaded data.
TreeStorageNode obj;
{
POASerializer objSerializer;
string s=fileName;
//Parse the file.
if(!objSerializer.loadNodeFromFile(s.c_str(),&obj,true)){
cout<<"Can't load record file "<<s<<endl;
return;
}
}
//find the node named 'map'.
bool loaded=false;
for(unsigned int i=0;i<obj.subNodes.size();i++){
if(obj.subNodes[i]->name=="map"){
//load the level. (fileName=???)
loadLevelFromNode(obj.subNodes[i],"???");
//remove this node to prevent delete it.
obj.subNodes[i]=NULL;
//over
loaded=true;
break;
}
}
if(!loaded){
cout<<"ERROR: Can't find subnode named 'map' from record file"<<endl;
return;
}
//load the record.
{
vector<int> *record=player.getRecord();
record->clear();
vector<string> &v=obj.attributes["record"];
for(unsigned int i=0;i<v.size();i++){
string &s=v[i];
string::size_type pos=s.find_first_of('*');
if(pos==string::npos){
//1 item only.
int i=atoi(s.c_str());
record->push_back(i);
}else{
//contains many items.
int i=atoi(s.substr(0,pos).c_str());
int j=atoi(s.substr(pos+1).c_str());
for(;j>0;j--){
record->push_back(i);
}
}
}
}
#ifdef RECORD_FILE_DEBUG
//load the debug data
{
vector<string> &v=obj.attributes["recordPlayerPosition"];
vector<SDL_Rect> &playerPosition=player.playerPosition();
playerPosition.clear();
if(!v.empty()){
if(!v[0].empty()){
stringstream st(v[0]);
int m;
st>>m;
for(int i=0;i<m;i++){
SDL_Rect r;
st>>r.x>>r.y;
r.w=0;
r.h=0;
playerPosition.push_back(r);
}
}
}
}
#endif
//play the record.
//TODO: tell the level manager don't save the level progress.
player.playRecord();
shadow.playRecord(); //???
}
/////////////EVENT///////////////
void Game::handleEvents(){
//First of all let the player handle input.
player.handleInput(&shadow);
//Check for an SDL_QUIT event.
if(event.type==SDL_QUIT){
//We need to quit so enter STATE_EXIT.
setNextState(STATE_EXIT);
}
//Check for the escape key.
if(inputMgr.isKeyUpEvent(INPUTMGR_ESCAPE)){
//Escape means we go one level up, to the level select state.
setNextState(STATE_LEVEL_SELECT);
//Save the progress.
levels->saveLevelProgress();
//And change the music back to the menu music.
getMusicManager()->playMusic("menu");
}
//Check if 'r' is pressed.
if(inputMgr.isKeyDownEvent(INPUTMGR_RESTART)){
//Only set isReset true if this isn't a replay.
if(!(player.isPlayFromRecord() && !interlevel))
isReset=true;
}
//Check for the next level buttons when in the interlevel popup.
if(inputMgr.isKeyDownEvent(INPUTMGR_SPACE) || (event.type==SDL_KEYDOWN && (event.key.keysym.sym==SDLK_RETURN || event.key.keysym.sym==SDLK_RCTRL))){
if(interlevel){
//The interlevel popup is shown so we need to delete it.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Now goto the next level.
gotoNextLevel();
}
}
//Check if tab is pressed.
if(inputMgr.isKeyDownEvent(INPUTMGR_TAB)){
//Switch the camera mode.
switch(cameraMode){
case CAMERA_PLAYER:
cameraMode=CAMERA_SHADOW;
break;
case CAMERA_SHADOW:
case CAMERA_CUSTOM:
cameraMode=CAMERA_PLAYER;
break;
}
}
}
/////////////////LOGIC///////////////////
void Game::logic(){
//Check if we should save/load state.
if(saveStateNextTime){
saveState();
}else if(loadStateNextTime){
loadState();
}
saveStateNextTime=false;
loadStateNextTime=false;
//Add one tick to the time.
time++;
//First prepare each gameObject for the new frame.
//This includes resetting dx/dy and xVel/yVel.
for(unsigned int o=0;o<levelObjects.size();o++)
levelObjects[o]->prepareFrame();
//Process any event in the queue.
for(unsigned int idx=0;idx<eventQueue.size();idx++){
//Get the event from the queue.
typeGameObjectEvent &e=eventQueue[idx];
//Check if the it has an id attached to it.
if(e.target){
//NOTE: Should we check if the target still exists???
e.target->onEvent(e.eventType);
}else if(e.flags|1){
//Loop through the levelObjects and give them the event if they have the right id.
for(unsigned int i=0;i<levelObjects.size();i++){
if(e.objectType<0 || levelObjects[i]->type==e.objectType){
if(levelObjects[i]->id==e.id){
levelObjects[i]->onEvent(e.eventType);
}
}
}
}else{
//Loop through the levelObjects and give them the event.
for(unsigned int i=0;i<levelObjects.size();i++){
if(e.objectType<0 || levelObjects[i]->type==e.objectType){
levelObjects[i]->onEvent(e.eventType);
}
}
}
}
//Done processing the events so clear the queue.
eventQueue.clear();
//Loop through the gameobjects to update them.
for(unsigned int i=0;i<levelObjects.size();i++){
//Send GameObjectEvent_OnEnterFrame event to the script
levelObjects[i]->onEvent(GameObjectEvent_OnEnterFrame);
}
for(unsigned int i=0;i<levelObjects.size();i++){
//Let the gameobject handle movement.
levelObjects[i]->move();
}
//Also update the scenery.
{
std::map<std::string,std::vector<Scenery*> >::iterator it;
for(it=backgroundLayers.begin();it!=backgroundLayers.end();++it){
for(unsigned int i=0;i<it->second.size();i++)
it->second[i]->move();
}
}
//Let the player store his move, if recording.
player.shadowSetState();
//Let the player give his recording to the shadow, if configured.
player.shadowGiveState(&shadow);
//Let him move.
player.move(levelObjects);
//Now let the shadow decide his move, if he's playing a recording.
shadow.moveLogic();
//Let the shadow move.
shadow.move(levelObjects);
//Check collision and stuff for the shadow and player.
player.otherCheck(&shadow);
//Update the camera.
switch(cameraMode){
case CAMERA_PLAYER:
player.setMyCamera();
break;
case CAMERA_SHADOW:
shadow.setMyCamera();
break;
case CAMERA_CUSTOM:
//NOTE: The target is (should be) screen size independent so calculate the real target x and y here.
int targetX=cameraTarget.x-(SCREEN_WIDTH/2);
int targetY=cameraTarget.y-(SCREEN_HEIGHT/2);
//Move the camera to the cameraTarget.
if(camera.x>targetX){
camera.x-=(camera.x-targetX)>>4;
//Make sure we don't go too far.
if(camera.x<targetX)
camera.x=targetX;
}else if(camera.x<targetX){
camera.x+=(targetX-camera.x)>>4;
//Make sure we don't go too far.
if(camera.x>targetX)
camera.x=targetX;
}
if(camera.y>targetY){
camera.y-=(camera.y-targetY)>>4;
//Make sure we don't go too far.
if(camera.y<targetY)
camera.y=targetY;
}else if(camera.y<targetY){
camera.y+=(targetY-camera.y)>>4;
//Make sure we don't go too far.
if(camera.y>targetY)
camera.y=targetY;
}
break;
}
//Check if we won.
if(won){
//Check if it's playing from record
if(player.isPlayFromRecord() && !interlevel){
recordingEnded();
}else{
//the string to store auto-save record path.
string bestTimeFilePath,bestRecordingFilePath;
//and if we can't get test path.
bool filePathError=false;
//Get current level
LevelPack::Level *level=levels->getLevel();
//Now check if we should update statistics
{
//Get previous and current medal
int oldMedal=level->won?1:0,newMedal=1;
int bestTime=level->time;
int targetTime=level->targetTime;
int bestRecordings=level->recordings;
int targetRecordings=level->targetRecordings;
if(oldMedal){
if(targetTime<0){
oldMedal=3;
}else{
if(targetTime<0 || bestTime<=targetTime)
oldMedal++;
if(targetRecordings<0 || bestRecordings<=targetRecordings)
oldMedal++;
}
}else{
bestTime=time;
bestRecordings=recordings;
}
if(bestTime==-1 || bestTime>time) bestTime=time;
if(bestRecordings==-1 || bestRecordings>recordings) bestRecordings=recordings;
if(targetTime<0){
newMedal=3;
}else{
if(targetTime<0 || bestTime<=targetTime)
newMedal++;
if(targetRecordings<0 || bestRecordings<=targetRecordings)
newMedal++;
}
//Check if we need to update statistics
if(newMedal>oldMedal){
switch(oldMedal){
case 0:
statsMgr.completedLevels++;
break;
case 2:
statsMgr.silverLevels--;
break;
}
switch(newMedal){
case 2:
statsMgr.silverLevels++;
break;
case 3:
statsMgr.goldLevels++;
break;
}
}
}
//Set the current level won.
level->won=true;
if(level->time==-1 || level->time>time){
level->time=time;
//save the best-time game record.
if(bestTimeFilePath.empty()){
getCurrentLevelAutoSaveRecordPath(bestTimeFilePath,bestRecordingFilePath,true);
}
if(bestTimeFilePath.empty()){
cout<<"ERROR: Couldn't get auto-save record file path"<<endl;
filePathError=true;
}else{
saveRecord(bestTimeFilePath.c_str());
}
}
if(level->recordings==-1 || level->recordings>recordings){
level->recordings=recordings;
//save the best-recordings game record.
if(bestRecordingFilePath.empty() && !filePathError){
getCurrentLevelAutoSaveRecordPath(bestTimeFilePath,bestRecordingFilePath,true);
}
if(bestRecordingFilePath.empty()){
cout<<"ERROR: Couldn't get auto-save record file path"<<endl;
filePathError=true;
}else{
saveRecord(bestRecordingFilePath.c_str());
}
}
//Set the next level unlocked if it exists.
if(levels->getCurrentLevel()+1<levels->getLevelCount()){
levels->setLocked(levels->getCurrentLevel()+1);
}
//And save the progress.
levels->saveLevelProgress();
//Now go to the interlevel screen.
replayPlay();
//Update achievements
if(levels->levelpackName=="tutorial") statsMgr.updateTutorialAchievements();
statsMgr.updateLevelAchievements();
//NOTE: We set isReset false to prevent the user from getting a best time of 0.00s and 0 recordings.
}
}
won=false;
//Check if we should reset.
if(isReset)
//NOTE: In case of the interlevel popup the save data needs to be deleted so the restart behaviour is the same for key and button restart.
reset(interlevel);
isReset=false;
}
/////////////////RENDER//////////////////
void Game::render(){
//First of all render the background.
{
//Get a pointer to the background.
ThemeBackground* bg=background;
//Check if the background is null, but there are themes.
if(bg==NULL && objThemes.themeCount()>0){
//Get the background from the first theme in the stack.
bg=objThemes[0]->getBackground(false);
}
//Check if the background isn't null.
if(bg){
//It isn't so draw it.
bg->draw(screen);
//And if it's the loaded background then also update the animation.
//FIXME: Updating the animation in the render method?
if(bg==background)
bg->updateAnimation();
}else{
//There's no background so fill the screen with white.
SDL_Rect r={0,0,SCREEN_WIDTH,SCREEN_HEIGHT};
SDL_FillRect(screen,&r,-1);
}
}
//Now draw the backgroundLayers.
std::map<std::string,std::vector<Scenery*> >::iterator it;
for(it=backgroundLayers.begin();it!=backgroundLayers.end();++it){
for(unsigned int i=0;i<it->second.size();i++)
it->second[i]->show();
}
//Now we draw the levelObjects.
for(unsigned int o=0; o<levelObjects.size(); o++){
levelObjects[o]->show();
}
//Followed by the player and the shadow.
//NOTE: We draw the shadow second because he needs to be behind the player.
shadow.show();
player.show();
//Show the levelName if it isn't the level editor.
if(stateID!=STATE_LEVEL_EDITOR && bmTips[0]!=NULL && !interlevel){
drawGUIBox(-2,SCREEN_HEIGHT-bmTips[0]->h-4,bmTips[0]->w+8,bmTips[0]->h+6,screen,0xFFFFFFFF);
applySurface(2,SCREEN_HEIGHT-bmTips[0]->h,bmTips[0],screen,NULL);
}
//Check if there's a tooltip.
//NOTE: gameTipIndex 0 is used for the levelName, 1 for shadow death, 2 for restart text, 3 for restart+checkpoint.
if(gameTipIndex>3 && gameTipIndex<TYPE_MAX){
//Check if there's a tooltip for the type.
if(bmTips[gameTipIndex]==NULL){
//There isn't thus make it.
string s;
string keyCode=_(inputMgr.getKeyCodeName(inputMgr.getKeyCode(INPUTMGR_ACTION,false)));
transform(keyCode.begin(),keyCode.end(),keyCode.begin(),::toupper);
switch(gameTipIndex){
case TYPE_CHECKPOINT:
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with current action key
s=tfm::format(_("Press %s key to save the game."),keyCode);
break;
case TYPE_SWAP:
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with current action key
s=tfm::format(_("Press %s key to swap the position of player and shadow."),keyCode);
break;
case TYPE_SWITCH:
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with current action key
s=tfm::format(_("Press %s key to activate the switch."),keyCode);
break;
case TYPE_PORTAL:
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with current action key
s=tfm::format(_("Press %s key to teleport."),keyCode);
break;
}
//If we have a string then it's a supported GameObject type.
if(!s.empty()){
SDL_Color fg={0,0,0,0};
bmTips[gameTipIndex]=TTF_RenderUTF8_Blended(fontText,s.c_str(),fg);
}
}
//We already have a gameTip for this type so draw it.
if(bmTips[gameTipIndex]!=NULL){
drawGUIBox(-2,-2,bmTips[gameTipIndex]->w+8,bmTips[gameTipIndex]->h+6,screen,0xFFFFFFFF);
applySurface(2,2,bmTips[gameTipIndex],screen,NULL);
}
}
//Set the gameTip to 0.
gameTipIndex=0;
//Pointer to the sdl surface that will contain a message, if any.
SDL_Surface* bm=NULL;
//Check if the player is dead, meaning we draw a message.
if(player.dead){
//Get user configured restart key
string keyCodeRestart=inputMgr.getKeyCodeName(inputMgr.getKeyCode(INPUTMGR_RESTART,false));
transform(keyCodeRestart.begin(),keyCodeRestart.end(),keyCodeRestart.begin(),::toupper);
//The player is dead, check if there's a state that can be loaded.
if(player.canLoadState()){
//Now check if the tip is already made, if not make it.
if(bmTips[3]==NULL){
//Get user defined key for loading checkpoint
string keyCodeLoad=inputMgr.getKeyCodeName(inputMgr.getKeyCode(INPUTMGR_LOAD,false));
transform(keyCodeLoad.begin(),keyCodeLoad.end(),keyCodeLoad.begin(),::toupper);
//Draw string
SDL_Color fg={0,0,0,0},bg={255,255,255,0};
bmTips[3]=TTF_RenderUTF8_Blended(fontText,
/// TRANSLATORS: Please do not remove %s from your translation:
/// - first %s means currently configured key to restart game
/// - Second %s means configured key to load from last save
tfm::format(_("Press %s to restart current level or press %s to load the game."),
keyCodeRestart,keyCodeLoad).c_str(),
fg);
}
bm=bmTips[3];
}else{
//Now check if the tip is already made, if not make it.
if(bmTips[2]==NULL){
SDL_Color fg={0,0,0,0};
bmTips[2]=TTF_RenderUTF8_Blended(fontText,
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with currently configured key to restart game
tfm::format(_("Press %s to restart current level."),keyCodeRestart).c_str(),
fg);
}
bm=bmTips[2];
}
}
//Check if the shadow has died (and there's no other notification).
//NOTE: We use the shadow's jumptime as countdown, this variable isn't used when the shadow is dead.
if(shadow.dead && bm==NULL && shadow.jumpTime>0){
//Now check if the tip is already made, if not make it.
if(bmTips[1]==NULL){
SDL_Color fg={0,0,0,0},bg={255,255,255,0};
bmTips[1]=TTF_RenderUTF8_Blended(fontText,
_("Your shadow has died."),
fg);
}
bm=bmTips[1];
//NOTE: Logic in the render loop, we substract the shadow's jumptime by one.
shadow.jumpTime--;
//return view to player and keep it there
cameraMode=CAMERA_PLAYER;
}
//Draw the tip.
if(bm!=NULL){
int x=(SCREEN_WIDTH-bm->w)/2;
int y=32;
drawGUIBox(x-8,y-8,bm->w+16,bm->h+14,screen,0xFFFFFFFF);
applySurface(x,y,bm,screen,NULL);
}
//Show the number of collectables the user has collected if there are collectables in the level.
//We hide this when interlevel.
if(currentCollectables<=totalCollectables && totalCollectables!=0 && !interlevel && time>0){
//Temp stringstream just to addup all the text nicely
stringstream temp;
temp << currentCollectables << "/" << totalCollectables;
SDL_Rect r;
SDL_Surface* bm=TTF_RenderText_Blended(fontText,temp.str().c_str(),themeTextColorDialog);
//Align the text properly
r.x=SCREEN_WIDTH-50-bm->w+22;
r.y=SCREEN_HEIGHT-bm->h;
//Draw background
drawGUIBox(SCREEN_WIDTH-bm->w-34,SCREEN_HEIGHT-bm->h-4,bm->w+34+2,bm->h+4+2,screen,0xFFFFFFFF);
//Draw the collectable icon
collectable.draw(screen,SCREEN_WIDTH-50+12,SCREEN_HEIGHT-50+10,NULL);
//Draw text
SDL_BlitSurface(bm,NULL,screen,&r);
SDL_FreeSurface(bm);
}
//show time and records used in level editor.
if(stateID==STATE_LEVEL_EDITOR && time>0){
SDL_Color fg={0,0,0,0},bg={255,255,255,0};
SDL_Surface *bm;
int y=SCREEN_HEIGHT;
bm=TTF_RenderUTF8_Shaded(fontText,tfm::format(_("%d recordings"),recordings).c_str(),fg,bg);
SDL_SetAlpha(bm,SDL_SRCALPHA,160);
y-=bm->h;
applySurface(0,y,bm,screen,NULL);
SDL_FreeSurface(bm);
char c[32];
sprintf(c,"%-.2fs",time/40.0f);
bm=TTF_RenderUTF8_Shaded(fontText,c,fg,bg);
SDL_SetAlpha(bm,SDL_SRCALPHA,160);
y-=bm->h;
applySurface(0,y,bm,screen,NULL);
SDL_FreeSurface(bm);
}
//Draw the current action in the upper right corner.
if(player.record){
applySurface(SCREEN_WIDTH-50,0,action,screen,NULL);
}else if(shadow.state!=0){
SDL_Rect r={50,0,50,50};
applySurface(SCREEN_WIDTH-50,0,action,screen,&r);
}
//if the game is play from record then draw something indicates it
if(player.isPlayFromRecord()){
//Dim the screen if interlevel is true.
if(interlevel){
SDL_BlitSurface(screen,NULL,tempSurface,NULL);
SDL_FillRect(screen,NULL,0);
SDL_SetAlpha(tempSurface, SDL_SRCALPHA,191);
SDL_BlitSurface(tempSurface,NULL,screen,NULL);
//Check if the GUI isn't null.
if(GUIObjectRoot){
//==Create first box==
//Create the title
SDL_Rect r;
/// TRANSLATORS: This is caption for finished level
SDL_Surface* bm=TTF_RenderUTF8_Blended(fontGUI,_("You've finished:"),themeTextColorDialog);
//Recreate the level string.
string s;
if (levels->getLevelCount()>0){
/// TRANSLATORS: Please do not remove %s or %d from your translation:
/// - %d means the level number in a levelpack
/// - %s means the name of current level
s=tfm::format(_("Level %d %s"),levels->getCurrentLevel()+1,_CC(levels->getDictionaryManager(),levelName));
}
SDL_Surface* bm2=TTF_RenderUTF8_Blended(fontText,s.c_str(),themeTextColorDialog);
//Now draw the first gui box so that it's bigger than longer text.
int width;
if(bm->w>bm2->w)
width=bm->w+32;
else
width=bm2->w+32;
drawGUIBox((SCREEN_WIDTH-width)/2,4,width,68,screen,0xFFFFFFBF);
// Now draw title.
r.x=(SCREEN_WIDTH-bm->w)/2;
r.y=8-GUI_FONT_RAISE;
SDL_BlitSurface(bm,NULL,screen,&r);
// And then level name.
r.x=(SCREEN_WIDTH-bm2->w)/2;
r.y=44;
SDL_BlitSurface(bm2,NULL,screen,&r);
//Free drawed texts
SDL_FreeSurface(bm);
SDL_FreeSurface(bm2);
//==Create second box==
//Now draw the second gui box.
drawGUIBox(GUIObjectRoot->left,GUIObjectRoot->top,GUIObjectRoot->width,GUIObjectRoot->height,screen,0xFFFFFFBF);
//Draw the medal.
int medal=GUIObjectRoot->value;
r.x=(medal-1)*30;
r.y=0;
r.w=30;
r.h=30;
applySurface(GUIObjectRoot->left+16,GUIObjectRoot->top+92,medals,screen,&r);
applySurface(GUIObjectRoot->left+medalX,GUIObjectRoot->top+92,medals,screen,&r);
}
}else if((time & 0x10)==0x10){
SDL_Rect r={50,0,50,50};
applySurface(0,0,action,screen,&r);
applySurface(0,SCREEN_HEIGHT-50,action,screen,&r);
applySurface(SCREEN_WIDTH-50,SCREEN_HEIGHT-50,action,screen,&r);
}
}else if(player.objNotificationBlock){
//If the player is in front of a notification block show the message.
//And it isn't a replay.
std::string &untranslated_message=player.objNotificationBlock->message;
std::string message=_CC(levels->getDictionaryManager(),untranslated_message);
std::vector<char> string_data(message.begin(), message.end());
string_data.push_back('\0');
int maxWidth = 0;
int y = 20;
vector<SDL_Surface*> lines;
//Now process the prompt.
{
//Pointer to the string.
char* lps=&string_data[0];
//Pointer to a character.
char* lp=NULL;
//We keep looping forever.
//The only way out is with the break statement.
for(;;){
//As long as it's still the same sentence we continue.
//It will stop when there's a newline or end of line.
for(lp=lps;*lp!='\n'&&*lp!='\r'&&*lp!=0;lp++);
//Store the character we stopped on. (End or newline)
char c=*lp;
//Set the character in the string to 0, making lps a string containing one sentence.
*lp=0;
//Integer used to center the sentence horizontally.
int x;
TTF_SizeText(fontText,lps,&x,NULL);
//Find out largest width
if(x>maxWidth)
maxWidth=x;
x=(SCREEN_WIDTH-x)/2;
lines.push_back(TTF_RenderUTF8_Blended(fontText,lps,themeTextColorDialog));
//Increase y with 25, about the height of the text.
y+=25;
//Check the stored character if it was a stop.
if(c==0){
//It was so break out of the for loop.
lps=lp;
break;
}
//It wasn't meaning more will follow.
//We set lps to point after the "newline" forming a new string.
lps=lp+1;
}
}
maxWidth+=SCREEN_WIDTH*0.15;
drawGUIBox((SCREEN_WIDTH-maxWidth)/2,SCREEN_HEIGHT-y-25,maxWidth,y+20,screen,0xFFFFFFBF);
while(!lines.empty()){
SDL_Surface* bm=lines[0];
if(bm!=NULL){
applySurface(100+((SCREEN_WIDTH-200-bm->w)/2),SCREEN_HEIGHT-y,bm,screen,NULL);
SDL_FreeSurface(bm);
}
y-=25;
lines.erase(lines.begin());
}
}
}
void Game::resize(){
//Check if the interlevel popup is shown.
if(interlevel && GUIObjectRoot){
GUIObjectRoot->left=(SCREEN_WIDTH-GUIObjectRoot->width)/2;
}
}
void Game::replayPlay(){
//Reset the number of collectables
currentCollectables=currentCollectablesSaved=0;
//We show the interlevel popup so interlevel must be true.
interlevel=true;
//Make the cursor visible when the interlevel popup is up.
SDL_ShowCursor(SDL_ENABLE);
//Create the gui if it isn't already done.
if(!GUIObjectRoot){
- GUIObjectRoot=new GUIObject(0,SCREEN_HEIGHT-140,570,135,GUIObjectNone);
+ GUIObjectRoot=new GUIObject(0,SCREEN_HEIGHT-140,570,135);
//NOTE: We put the medal in the value of the GUIObjectRoot.
//Make child widgets change color properly according to theme.
GUIObjectRoot->inDialog=true;
//The different values.
int bestTime=levels->getLevel()->time;
int targetTime=levels->getLevel()->targetTime;
int bestRecordings=levels->getLevel()->recordings;
int targetRecordings=levels->getLevel()->targetRecordings;
int medal=1;
if(targetTime<0){
medal=3;
}else{
if(targetTime<0 || bestTime<=targetTime)
medal++;
if(targetRecordings<0 || bestRecordings<=targetRecordings)
medal++;
}
//Add it to the GUIObjectRoot.
GUIObjectRoot->value=medal;
int maxWidth=0;
int x=20;
//Is there a target time for this level?
int timeY=0;
bool isTargetTime=true;
if(targetTime<=0){
isTargetTime=false;
timeY=12;
}
//Create the labels with the time and best time.
/// TRANSLATORS: Please do not remove %-.2f from your translation:
/// - %-.2f means time in seconds
/// - s is shortened form of a second. Try to keep it so.
- GUIObject* obj=new GUIObject(x,10+timeY,-1,36,GUIObjectLabel,tfm::format(_("Time: %-.2fs"),time/40.0f).c_str());
+ GUIObject* obj=new GUILabel(x,10+timeY,-1,36,tfm::format(_("Time: %-.2fs"),time/40.0f).c_str());
GUIObjectRoot->addChild(obj);
obj->render(0,0,false);
maxWidth=obj->width;
/// TRANSLATORS: Please do not remove %-.2f from your translation:
/// - %-.2f means time in seconds
/// - s is shortened form of a second. Try to keep it so.
- obj=new GUIObject(x,34+timeY,-1,36,GUIObjectLabel,tfm::format(_("Best time: %-.2fs"),bestTime/40.0f).c_str());
+ obj=new GUILabel(x,34+timeY,-1,36,tfm::format(_("Best time: %-.2fs"),bestTime/40.0f).c_str());
GUIObjectRoot->addChild(obj);
obj->render(0,0,false);
if(obj->width>maxWidth)
maxWidth=obj->width;
/// TRANSLATORS: Please do not remove %-.2f from your translation:
/// - %-.2f means time in seconds
/// - s is shortened form of a second. Try to keep it so.
if(isTargetTime){
- obj=new GUIObject(x,58,-1,36,GUIObjectLabel,tfm::format(_("Target time: %-.2fs"),targetTime/40.0f).c_str());
+ obj=new GUILabel(x,58,-1,36,tfm::format(_("Target time: %-.2fs"),targetTime/40.0f).c_str());
GUIObjectRoot->addChild(obj);
obj->render(0,0,false);
if(obj->width>maxWidth)
maxWidth=obj->width;
}
x+=maxWidth+20;
//Is there target recordings for this level?
int recsY=0;
bool isTargetRecs=true;
if(targetRecordings<0){
isTargetRecs=false;
recsY=12;
}
//Now the ones for the recordings.
/// TRANSLATORS: Please do not remove %d from your translation:
/// - %d means the number of recordings user has made
- obj=new GUIObject(x,10+recsY,-1,36,GUIObjectLabel,tfm::format(_("Recordings: %d"),recordings).c_str());
+ obj=new GUILabel(x,10+recsY,-1,36,tfm::format(_("Recordings: %d"),recordings).c_str());
GUIObjectRoot->addChild(obj);
obj->render(0,0,false);
maxWidth=obj->width;
/// TRANSLATORS: Please do not remove %d from your translation:
/// - %d means the number of recordings user has made
- obj=new GUIObject(x,34+recsY,-1,36,GUIObjectLabel,tfm::format(_("Best recordings: %d"),bestRecordings).c_str());
+ obj=new GUILabel(x,34+recsY,-1,36,tfm::format(_("Best recordings: %d"),bestRecordings).c_str());
GUIObjectRoot->addChild(obj);
obj->render(0,0,false);
if(obj->width>maxWidth)
maxWidth=obj->width;
/// TRANSLATORS: Please do not remove %d from your translation:
/// - %d means the number of recordings user has made
if(isTargetRecs){
- obj=new GUIObject(x,58,-1,36,GUIObjectLabel,tfm::format(_("Target recordings: %d"),targetRecordings).c_str());
+ obj=new GUILabel(x,58,-1,36,tfm::format(_("Target recordings: %d"),targetRecordings).c_str());
GUIObjectRoot->addChild(obj);
obj->render(0,0,false);
if(obj->width>maxWidth)
maxWidth=obj->width;
}
x+=maxWidth;
//The medal that is earned.
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with name of a prize medal (gold, silver or bronze)
string s1=tfm::format(_("You earned the %s medal"),(medal>1)?(medal==3)?_("GOLD"):_("SILVER"):_("BRONZE"));
- obj=new GUIObject(50,92,-1,36,GUIObjectLabel,s1.c_str(),0,true,true,GUIGravityCenter);
+ obj=new GUILabel(50,92,-1,36,s1.c_str(),0,true,true,GUIGravityCenter);
GUIObjectRoot->addChild(obj);
obj->render(0,0,false);
if(obj->left+obj->width>x){
x=obj->left+obj->width+30;
}else{
obj->left=20+(x-20-obj->width)/2;
}
medalX=x-24;
//Create the three buttons, Menu, Restart, Next.
/// TRANSLATORS: used as return to the level selector menu
- GUIObject* b1=new GUIObject(x,10,-1,36,GUIObjectButton,_("Menu"),0,true,true,GUIGravityCenter);
+ GUIObject* b1=new GUIButton(x,10,-1,36,_("Menu"),0,true,true,GUIGravityCenter);
b1->name="cmdMenu";
b1->eventCallback=this;
GUIObjectRoot->addChild(b1);
b1->render(0,0,true);
/// TRANSLATORS: used as restart level
- GUIObject* b2=new GUIObject(x,50,-1,36,GUIObjectButton,_("Restart"),0,true,true,GUIGravityCenter);
+ GUIObject* b2=new GUIButton(x,50,-1,36,_("Restart"),0,true,true,GUIGravityCenter);
b2->name="cmdRestart";
b2->eventCallback=this;
GUIObjectRoot->addChild(b2);
b2->render(0,0,true);
/// TRANSLATORS: used as next level
- GUIObject* b3=new GUIObject(x,90,-1,36,GUIObjectButton,_("Next"),0,true,true,GUIGravityCenter);
+ GUIObject* b3=new GUIButton(x,90,-1,36,_("Next"),0,true,true,GUIGravityCenter);
b3->name="cmdNext";
b3->eventCallback=this;
GUIObjectRoot->addChild(b3);
b3->render(0,0,true);
maxWidth=b1->width;
if(b2->width>maxWidth)
maxWidth=b2->width;
if(b3->width>maxWidth)
maxWidth=b3->width;
b1->left=b2->left=b3->left=x+maxWidth/2;
x+=maxWidth;
GUIObjectRoot->width=x;
GUIObjectRoot->left=(SCREEN_WIDTH-GUIObjectRoot->width)/2;
}
//We only need to reset a few things so we don't call reset().
for(unsigned int i=0;i<levelObjects.size();i++){
levelObjects[i]->reset(true);
}
//Also reset the background animation, if any.
if(background)
background->resetAnimation(true);
//The script environment.
getScriptExecutor()->reset();
//And call the onCreate scripts.
for(unsigned int i=0;i<levelObjects.size();i++){
levelObjects[i]->onEvent(GameObjectEvent_OnCreate);
}
//Close exit(s) if there are any collectables
if(totalCollectables>0){
for(unsigned int i=0;i<levelObjects.size();i++){
if(levelObjects[i]->type==TYPE_EXIT){
levelObjects[i]->onEvent(GameObjectEvent_OnSwitchOff);
}
}
}
//Execute the onCreate event, if any.
//NOTE: Do we need an onReset event???
//executeScript(LevelEvent_OnReset);
executeScript(LevelEvent_OnCreate);
//Make a copy of the playerButtons.
vector<int> recordCopy=player.recordButton;
player.reset(true);
shadow.reset(true);
player.recordButton=recordCopy;
//Now play the recording.
player.playRecord();
}
void Game::recordingEnded(){
//Check if it's a normal replay, if so just stop.
if(!interlevel){
//Show the cursor so that the user can press the ok button.
SDL_ShowCursor(SDL_ENABLE);
//Now show the message box.
msgBox(_("Game replay is done."),MsgBoxOKOnly,_("Game Replay"));
//Go to the level select menu.
setNextState(STATE_LEVEL_SELECT);
//And change the music back to the menu music.
getMusicManager()->playMusic("menu");
}else{
//Instead of directly replaying we set won true to let the Game handle the replaying at the end of the update cycle.
won=true;
}
}
bool Game::canSaveState(){
return (player.canSaveState() && shadow.canSaveState());
}
bool Game::saveState(){
//Check if the player and shadow can save the current state.
if(canSaveState()){
//Let the player and the shadow save their state.
player.saveState();
shadow.saveState();
//Save the stats.
timeSaved=time;
recordingsSaved=recordings;
recentSwapSaved=recentSwap;
//Save the camera mode and target.
cameraModeSaved=cameraMode;
cameraTargetSaved=cameraTarget;
//Save the current collectables
currentCollectablesSaved=currentCollectables;
//Save other state, for example moving blocks.
for(unsigned int i=0;i<levelObjects.size();i++){
levelObjects[i]->saveState();
}
//Also save the background animation, if any.
if(background)
background->saveAnimation();
if(!player.isPlayFromRecord() && !interlevel){
//Update achievements
Uint32 t=SDL_GetTicks()+5000; //Add a bias to prevent bugs
if(recentSave+1000>t){
statsMgr.newAchievement("panicSave");
}
recentSave=t;
//Update statistics.
statsMgr.saveTimes++;
//Update achievements
switch(statsMgr.saveTimes){
case 1000:
statsMgr.newAchievement("save1k");
break;
}
}
//Execute the onSave event.
executeScript(LevelEvent_OnSave);
//Return true.
return true;
}
//We can't save the state so return false.
return false;
}
bool Game::loadState(){
//Check if there's a state that can be loaded.
if(player.canLoadState() && shadow.canLoadState()){
//Let the player and the shadow load their state.
player.loadState();
shadow.loadState();
//Load the stats.
time=timeSaved;
recordings=recordingsSaved;
recentSwap=recentSwapSaved;
//Load the camera mode and target.
cameraMode=cameraModeSaved;
cameraTarget=cameraTargetSaved;
//Load the current collactbles
currentCollectables=currentCollectablesSaved;
//Load other state, for example moving blocks.
for(unsigned int i=0;i<levelObjects.size();i++){
levelObjects[i]->loadState();
}
//Also load the background animation, if any.
if(background)
background->loadAnimation();
if(!player.isPlayFromRecord() && !interlevel){
//Update achievements.
Uint32 t=SDL_GetTicks()+5000; //Add a bias to prevent bugs
if(recentLoad+1000>t){
statsMgr.newAchievement("panicLoad");
}
recentLoad=t;
//Update statistics.
statsMgr.loadTimes++;
//Update achievements
switch(statsMgr.loadTimes){
case 1000:
statsMgr.newAchievement("load1k");
break;
}
}
//Execute the onLoad event, if any.
executeScript(LevelEvent_OnLoad);
//Return true.
return true;
}
//We can't load the state so return false.
return false;
}
void Game::reset(bool save){
//We need to reset the game so we also reset the player and the shadow.
player.reset(save);
shadow.reset(save);
saveStateNextTime=false;
loadStateNextTime=false;
//Reset the stats.
time=0;
recordings=0;
recentSwap=-10000;
if(save) recentSwapSaved=-10000;
//Reset the camera.
cameraMode=CAMERA_PLAYER;
if(save) cameraModeSaved=CAMERA_PLAYER;
cameraTarget.x=cameraTarget.y=cameraTarget.w=cameraTarget.h=0;
if(save) cameraTargetSaved.x=cameraTargetSaved.y=cameraTargetSaved.w=cameraTargetSaved.h=0;
//Reset the number of collectables
currentCollectables=0;
if(save)
currentCollectablesSaved=0;
//There is no last checkpoint so set it to NULL.
if(save)
objLastCheckPoint=NULL;
//Clear the event queue, since all the events are from before the reset.
eventQueue.clear();
//Reset other state, for example moving blocks.
for(unsigned int i=0;i<levelObjects.size();i++){
levelObjects[i]->reset(save);
}
//Also reset the background animation, if any.
if(background)
background->resetAnimation(save);
//Reset the script environment
//NOTE: The scriptExecutor will only be reset between levels.
//getScriptExecutor()->reset();
//Send GameObjectEvent_OnCreate event to the script
for(unsigned int i=0;i<levelObjects.size();i++){
levelObjects[i]->onEvent(GameObjectEvent_OnCreate);
}
//Close exit(s) if there are any collectables
if(totalCollectables>0){
for(unsigned int i=0;i<levelObjects.size();i++){
if(levelObjects[i]->type==TYPE_EXIT){
levelObjects[i]->onEvent(GameObjectEvent_OnSwitchOff);
}
}
}
//Execute the onCreate event, if any.
//NOTE: Do we need an onReset event???
//executeScript(LevelEvent_OnReset);
executeScript(LevelEvent_OnCreate);
//Check if interlevel is true, if so we might need to delete the gui.
if(interlevel){
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
//Also set interlevel to false.
interlevel=false;
//Hide the cursor (if not the leveleditor).
if(stateID!=STATE_LEVEL_EDITOR)
SDL_ShowCursor(SDL_DISABLE);
}
void Game::executeScript(int eventType){
map<int,string>::iterator it;
//Check if there's a script for the given event.
it=scripts.find(eventType);
if(it!=scripts.end()){
//There is one so execute it.
getScriptExecutor()->executeScript(it->second);
}
}
void Game::broadcastObjectEvent(int eventType,int objectType,const char* id,GameObject* target){
//Create a typeGameObjectEvent that can be put into the queue.
typeGameObjectEvent e;
//Set the event type.
e.eventType=eventType;
//Set the object type.
e.objectType=objectType;
//By default flags=0.
e.flags=0;
//Unless there's an id.
if(id){
//Set flags to 0x1 and set the id.
e.flags|=1;
e.id=id;
}
//Or there's a target given.
if(target)
e.target=target;
else
e.target=NULL;
//Add the event to the queue.
eventQueue.push_back(e);
}
void Game::getCurrentLevelAutoSaveRecordPath(std::string &bestTimeFilePath,std::string &bestRecordingFilePath,bool createPath){
levels->getLevelAutoSaveRecordPath(-1,bestTimeFilePath,bestRecordingFilePath,createPath);
}
void Game::gotoNextLevel(){
//Goto the next level.
levels->nextLevel();
//Check if the level exists.
if(levels->getCurrentLevel()<levels->getLevelCount()){
setNextState(STATE_GAME);
//Don't forget the music.
getMusicManager()->pickMusic();
}else{
if(!levels->congratulationText.empty()){
msgBox(_CC(levels->getDictionaryManager(),levels->congratulationText),MsgBoxOKOnly,_("Congratulations"));
}else{
msgBox(_("You have finished the levelpack!"),MsgBoxOKOnly,_("Congratulations"));
}
//Now go back to the levelselect screen.
setNextState(STATE_LEVEL_SELECT);
//And set the music back to menu.
getMusicManager()->playMusic("menu");
}
}
void Game::GUIEventCallback_OnEvent(string name,GUIObject* obj,int eventType){
if(name=="cmdMenu"){
setNextState(STATE_LEVEL_SELECT);
//And change the music back to the menu music.
getMusicManager()->playMusic("menu");
}else if(name=="cmdRestart"){
//Clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//And reset the game.
//new: we don't need to clear the save game because
//in level replay the game won't be saved
//TODO: it doesn't work; better leave for next release
reset(/*false*/ true);
}else if(name=="cmdNext"){
//No matter what, clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//And goto the next level.
gotoNextLevel();
}
}
diff --git a/src/InputManager.cpp b/src/InputManager.cpp
index 8ea40b8..3317ace 100644
--- a/src/InputManager.cpp
+++ b/src/InputManager.cpp
@@ -1,453 +1,453 @@
/*
* 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 "InputManager.h"
#include "Globals.h"
#include "Settings.h"
#include "Functions.h"
#include "GUIObject.h"
#include "GUIListBox.h"
#include "GUIOverlay.h"
#include <stdlib.h>
#include <stdio.h>
#include <string>
using namespace std;
InputManager inputMgr;
//the order must be the same as InputManagerKeys
static const char* keySettingNames[INPUTMGR_MAX]={
"key_up","key_down","key_left","key_right","key_jump","key_action","key_space","key_cancelRecording",
"key_escape","key_restart","key_tab","key_save","key_load","key_swap",
"key_teleport","key_suicide","key_shift","key_next","key_previous","key_select"
};
//the order must be the same as InputManagerKeys
static const char* keySettingDescription[INPUTMGR_MAX]={
__("Up (in menu)"),__("Down (in menu)"),__("Left"),__("Right"),__("Jump"),__("Action"),__("Space (Record)"),__("Cancel recording"),
__("Escape"),__("Restart"),__("Tab (View shadow/Level prop.)"),__("Save game (in editor)"),__("Load game"),__("Swap (in editor)"),
__("Teleport (in editor)"),__("Suicide (in editor)"),__("Shift (in editor)"),__("Next block type (in Editor)"),
__("Previous block type (in editor)"), __("Select (in menu)")
};
//A class that handles the gui events of the inputDialog.
class InputDialogHandler:public GUIEventCallback{
private:
//the list box which contains keys.
GUIListBox* listBox;
//the parent object.
InputManager* parent;
//update specified key config item
void updateConfigItem(int index){
//Get the description of the key.
std::string s=_(keySettingDescription[index]);
s+=": ";
//Get the key code name.
int keyCode=parent->getKeyCode((InputManagerKeys)index,false);
s+=_(InputManager::getKeyCodeName(keyCode));
//Add the alternative key if there is one.
int keyCodeAlt=parent->getKeyCode((InputManagerKeys)index,true);
if(keyCodeAlt!=0){
s+=" ";
s+=_("OR");
s+=" ";
s+=_(InputManager::getKeyCodeName(keyCodeAlt));
}
//Update item.
listBox->updateItem(index,s);
}
public:
//Constructor.
InputDialogHandler(GUIListBox* listBox,InputManager* parent):listBox(listBox),parent(parent){
//load the available keys to the list box.
for(int i=0;i<INPUTMGR_MAX;i++){
//Get the description of the key.
std::string s=_(keySettingDescription[i]);
s+=": ";
//Get key code name.
int keyCode=parent->getKeyCode((InputManagerKeys)i,false);
s+=_(InputManager::getKeyCodeName(keyCode));
//Add the alternative key if there is one.
int keyCodeAlt=parent->getKeyCode((InputManagerKeys)i,true);
if(keyCodeAlt!=0){
s+=" ";
s+=_("OR");
s+=" ";
s+=_(InputManager::getKeyCodeName(keyCodeAlt));
}
//Add item.
listBox->addItem(s);
}
}
//When a key is pressed call this to set the key to currently-selected item.
void onKeyDown(int keyCode){
//Check if an item is selected.
int index=listBox->value;
if(index<0 || index>=INPUTMGR_MAX) return;
//Update the key.
//SDLK_BACKSPACE will erase the key.
if(keyCode==SDLK_BACKSPACE){
parent->setKeyCode((InputManagerKeys)index,0,true);
parent->setKeyCode((InputManagerKeys)index,0,false);
updateConfigItem(index);
}else{
//Update the main key if there isn't one. Otherwise update the alternative key if there isn't one.
int key=parent->getKeyCode((InputManagerKeys)index,false);
int altKey=parent->getKeyCode((InputManagerKeys)index,true);
if(key==0){
parent->setKeyCode((InputManagerKeys)index,keyCode,false);
}else if((altKey==0)&&(keyCode!=key)){
parent->setKeyCode((InputManagerKeys)index,keyCode,true);
}
updateConfigItem(index);
}
}
void GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType){
//Do nothing...
}
};
//Event handler.
static InputDialogHandler* handler;
//A GUIObject that is used to create events for key presses.
class GUIKeyListener:public GUIObject{
//Leave empty.
~GUIKeyListener(){}
bool handleEvents(int x,int y,bool enabled,bool visible,bool processed){
if(enabled && handler){
if(event.type==SDL_KEYDOWN){
handler->onKeyDown(event.key.keysym.sym);
}
//Joystick
else if(event.type==SDL_JOYAXISMOTION){
if(event.jaxis.value>3200){
handler->onKeyDown(0x00010001 | (int(event.jaxis.axis)<<8));
}else if(event.jaxis.value<-3200){
handler->onKeyDown(0x000100FF | (int(event.jaxis.axis)<<8));
}
}
else if(event.type==SDL_JOYBUTTONDOWN){
handler->onKeyDown(0x00020000 | (int(event.jbutton.button)<<8));
}
else if(event.type==SDL_JOYHATMOTION){
if(event.jhat.value & SDL_HAT_LEFT){
handler->onKeyDown(0x00030000 | (int(event.jhat.hat)<<8) | SDL_HAT_LEFT);
}else if(event.jhat.value & SDL_HAT_RIGHT){
handler->onKeyDown(0x00030000 | (int(event.jhat.hat)<<8) | SDL_HAT_RIGHT);
}else if(event.jhat.value & SDL_HAT_UP){
handler->onKeyDown(0x00030000 | (int(event.jhat.hat)<<8) | SDL_HAT_UP);
}else if(event.jhat.value & SDL_HAT_DOWN){
handler->onKeyDown(0x00030000 | (int(event.jhat.hat)<<8) | SDL_HAT_DOWN);
}
}
}
return false;
}
//Nothing to do.
void render(){}
};
int InputManager::getKeyCode(InputManagerKeys key,bool isAlternativeKey){
if(isAlternativeKey) return alternativeKeys[key];
else return keys[key];
}
void InputManager::setKeyCode(InputManagerKeys key,int keyCode,bool isAlternativeKey){
if(isAlternativeKey) alternativeKeys[key]=keyCode;
else keys[key]=keyCode;
}
void InputManager::loadConfig(){
for(int i=0;i<INPUTMGR_MAX;i++){
string s=keySettingNames[i];
keys[i]=atoi(getSettings()->getValue(s).c_str());
s+="2";
alternativeKeys[i]=atoi(getSettings()->getValue(s).c_str());
//Move the alternative key to main key if the main key is empty.
if(keys[i]==0&&alternativeKeys[i]!=0){
keys[i]=alternativeKeys[i];
alternativeKeys[i]=0;
}
//Remove duplicate.
if(keys[i]==alternativeKeys[i])
alternativeKeys[i]=0;
}
}
void InputManager::saveConfig(){
int i;
char c[32];
for(i=0;i<INPUTMGR_MAX;i++){
string s=keySettingNames[i];
//Remove duplicate.
if(keys[i]==alternativeKeys[i])
alternativeKeys[i]=0;
sprintf(c,"%d",keys[i]);
getSettings()->setValue(s,c);
s+="2";
sprintf(c,"%d",alternativeKeys[i]);
getSettings()->setValue(s,c);
}
}
GUIObject* InputManager::showConfig(int height){
//Create the new GUI.
- GUIObject* root=new GUIObject(0,0,SCREEN_WIDTH,height,GUIObjectNone);
+ GUIObject* root=new GUIObject(0,0,SCREEN_WIDTH,height);
//Instruction label.
- GUIObject* obj=new GUIObject(0,6,root->width,36,GUIObjectLabel,_("Select an item and press a key to change it."),0,true,true,GUIGravityCenter);
+ GUIObject* obj=new GUILabel(0,6,root->width,36,_("Select an item and press a key to change it."),0,true,true,GUIGravityCenter);
root->addChild(obj);
- obj=new GUIObject(0,30,root->width,36,GUIObjectLabel,_("Press backspace to clear the selected item."),0,true,true,GUIGravityCenter);
+ obj=new GUILabel(0,30,root->width,36,_("Press backspace to clear the selected item."),0,true,true,GUIGravityCenter);
root->addChild(obj);
//The listbox for keys.
GUIListBox *listBox=new GUIListBox(SCREEN_WIDTH*0.15,72,SCREEN_WIDTH*0.7,height-72-8);
root->addChild(listBox);
//Create the event handler.
if(handler)
delete handler;
handler=new InputDialogHandler(listBox,this);
obj=new GUIKeyListener();
root->addChild(obj);
//Return final widget.
return root;
}
//Get key name from key code.
std::string InputManager::getKeyCodeName(int keyCode){
char c[64];
if(keyCode>0 && keyCode <0x1000){
//Keyboard.
char* s=SDL_GetKeyName((SDLKey)keyCode);
if(s!=NULL){
return s;
}else{
sprintf(c,"(Key %d)",keyCode);
return c;
}
}else if(keyCode>0x1000){
//Joystick. First set it to invalid value.
sprintf(c,"(Joystick 0x%08X)",keyCode);
//Check the input type.
switch((keyCode & 0x00FF0000)>>16){
case 1:
//Axis.
switch(keyCode & 0xFF){
case 1:
sprintf(c,"Joystick axis %d +",(keyCode & 0x0000FF00)>>8);
break;
case 0xFF:
sprintf(c,"Joystick axis %d -",(keyCode & 0x0000FF00)>>8);
break;
}
break;
case 2:
//Button.
sprintf(c,"Joystick button %d",(keyCode & 0x0000FF00)>>8);
break;
case 3:
//Hat.
switch(keyCode & 0xFF){
case SDL_HAT_LEFT:
sprintf(c,"Joystick hat %d left",(keyCode & 0x0000FF00)>>8);
break;
case SDL_HAT_RIGHT:
sprintf(c,"Joystick hat %d right",(keyCode & 0x0000FF00)>>8);
break;
case SDL_HAT_UP:
sprintf(c,"Joystick hat %d up",(keyCode & 0x0000FF00)>>8);
break;
case SDL_HAT_DOWN:
sprintf(c,"Joystick hat %d down",(keyCode & 0x0000FF00)>>8);
break;
}
break;
}
return c;
}else{
//Disabled or unknown.
return "-";
}
}
InputManager::InputManager(){
//Clear the arrays.
for(int i=0;i<INPUTMGR_MAX;i++){
keys[i]=alternativeKeys[i]=keyFlags[i]=0;
}
}
InputManager::~InputManager(){
closeAllJoysticks();
}
int InputManager::getKeyState(int keyCode,int oldState,bool hasEvent){
int state=0;
if(keyCode>0 && keyCode<0x1000){
//Keyboard.
if(hasEvent){
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==keyCode){
state|=0x2;
}
if(event.type==SDL_KEYUP && event.key.keysym.sym==keyCode){
state|=0x4;
}
}
if(keyCode<SDLK_LAST && SDL_GetKeyState(NULL)[keyCode]){
state|=0x1;
}
}else if(keyCode>0x1000){
//Joystick.
int index=(keyCode & 0x0000FF00)>>8;
int value=keyCode & 0xFF;
int i,v;
switch((keyCode & 0x00FF0000)>>16){
case 1:
//Axis.
if(hasEvent){
if(event.type==SDL_JOYAXISMOTION && event.jaxis.axis==index){
if((value==1 && event.jaxis.value>3200) || (value==0xFF && event.jaxis.value<-3200)){
if((oldState & 0x1)==0) state|=0x2;
}else{
if(oldState & 0x1) state|=0x4;
}
}
}
for(i=0;i<(int)joysticks.size();i++){
v=SDL_JoystickGetAxis(joysticks[i],index);
if((value==1 && v>3200) || (value==0xFF && v<-3200)){
state|=0x1;
break;
}
}
break;
case 2:
//Button.
if(hasEvent){
if(event.type==SDL_JOYBUTTONDOWN && event.jbutton.button==index){
state|=0x2;
}
if(event.type==SDL_JOYBUTTONUP && event.jbutton.button==index){
state|=0x4;
}
}
for(i=0;i<(int)joysticks.size();i++){
v=SDL_JoystickGetButton(joysticks[i],index);
if(v){
state|=0x1;
break;
}
}
break;
case 3:
//Hat.
if(hasEvent){
if(event.type==SDL_JOYHATMOTION && event.jhat.hat==index){
if(event.jhat.value & value){
if((oldState & 0x1)==0) state|=0x2;
}else{
if(oldState & 0x1) state|=0x4;
}
}
}
for(i=0;i<(int)joysticks.size();i++){
v=SDL_JoystickGetHat(joysticks[i],index);
if(v & value){
state|=0x1;
break;
}
}
break;
}
}
return state;
}
//Update the key state, according to current SDL event, etc.
void InputManager::updateState(bool hasEvent){
for(int i=0;i<INPUTMGR_MAX;i++){
keyFlags[i]=getKeyState(keys[i],keyFlags[i],hasEvent)|getKeyState(alternativeKeys[i],keyFlags[i],hasEvent);
}
}
//Check if there is KeyDown event.
bool InputManager::isKeyDownEvent(InputManagerKeys key){
return keyFlags[key]&0x2;
}
//Check if there is KeyUp event.
bool InputManager::isKeyUpEvent(InputManagerKeys key){
return keyFlags[key]&0x4;
}
//Check if specified key is down.
bool InputManager::isKeyDown(InputManagerKeys key){
return keyFlags[key]&0x1;
}
//Open all joysticks.
void InputManager::openAllJoysitcks(){
int i,m;
//First close previous joysticks.
closeAllJoysticks();
//Open all joysticks.
m=SDL_NumJoysticks();
for(i=0;i<m;i++){
SDL_Joystick *j=SDL_JoystickOpen(i);
if(j==NULL){
printf("ERROR: Couldn't open Joystick %d\n",i);
}else{
joysticks.push_back(j);
}
}
}
//Close all joysticks.
void InputManager::closeAllJoysticks(){
for(int i=0;i<(int)joysticks.size();i++){
SDL_JoystickClose(joysticks[i]);
}
joysticks.clear();
}
diff --git a/src/LevelEditSelect.cpp b/src/LevelEditSelect.cpp
index ad71ce5..315e10d 100644
--- a/src/LevelEditSelect.cpp
+++ b/src/LevelEditSelect.cpp
@@ -1,749 +1,750 @@
/*
* 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 "LevelEditSelect.h"
#include "GameState.h"
#include "Functions.h"
#include "FileManager.h"
#include "Globals.h"
#include "Objects.h"
#include "GUIObject.h"
#include "GUIListBox.h"
#include "GUIScrollBar.h"
#include "InputManager.h"
#include "StatisticsManager.h"
#include "Game.h"
#include "GUIOverlay.h"
#ifdef __APPLE__
#include <SDL_ttf/SDL_ttf.h>
#else
#include <SDL/SDL_ttf.h>
#endif
#include <algorithm>
#include <string>
#include <iostream>
#include "libs/tinyformat/tinyformat.h"
using namespace std;
LevelEditSelect::LevelEditSelect():LevelSelect(_("Map Editor"),LevelPackManager::CUSTOM_PACKS){
//Create the gui.
createGUI(true);
//Set the levelEditGUIObjectRoot.
levelEditGUIObjectRoot=GUIObjectRoot;
//show level list
changePack();
refresh();
}
LevelEditSelect::~LevelEditSelect(){
selectedNumber=NULL;
}
void LevelEditSelect::createGUI(bool initial){
if(initial){
//The levelpack name text field.
- levelpackName=new GUIObject(280,104,240,32,GUIObjectTextBox);
+ levelpackName=new GUITextBox(280,104,240,32);
levelpackName->eventCallback=this;
levelpackName->visible=false;
GUIObjectRoot->addChild(levelpackName);
}
if(!initial){
//Remove the previous buttons.
+ //TODO: better way to do this?
for(int i=0;i<(int)GUIObjectRoot->childControls.size();i++){
- if(GUIObjectRoot->childControls[i]->type==GUIObjectButton && GUIObjectRoot->childControls[i]->caption!=_("Back")){
+ if(GUIObjectRoot->childControls[i]->caption!=_("Back")){
delete GUIObjectRoot->childControls[i];
GUIObjectRoot->childControls.erase(GUIObjectRoot->childControls.begin()+i);
i--;
}
}
}
//Create the six buttons at the bottom of the screen.
- GUIObject* obj=new GUIObject(SCREEN_WIDTH*0.02,SCREEN_HEIGHT-120,-1,32,GUIObjectButton,_("New Levelpack"));
+ GUIButton* obj=new GUIButton(SCREEN_WIDTH*0.02,SCREEN_HEIGHT-120,-1,32,_("New Levelpack"));
obj->name="cmdNewLvlpack";
obj->eventCallback=this;
GUIObjectRoot->addChild(obj);
- propertiesPack=new GUIObject(SCREEN_WIDTH*0.5,SCREEN_HEIGHT-120,-1,32,GUIObjectButton,_("Pack Properties"),0,true,true,GUIGravityCenter);
+ propertiesPack=new GUIButton(SCREEN_WIDTH*0.5,SCREEN_HEIGHT-120,-1,32,_("Pack Properties"),0,true,true,GUIGravityCenter);
propertiesPack->name="cmdLvlpackProp";
propertiesPack->eventCallback=this;
GUIObjectRoot->addChild(propertiesPack);
- removePack=new GUIObject(SCREEN_WIDTH*0.98,SCREEN_HEIGHT-120,-1,32,GUIObjectButton,_("Remove Pack"),0,true,true,GUIGravityRight);
+ removePack=new GUIButton(SCREEN_WIDTH*0.98,SCREEN_HEIGHT-120,-1,32,_("Remove Pack"),0,true,true,GUIGravityRight);
removePack->name="cmdRmLvlpack";
removePack->eventCallback=this;
GUIObjectRoot->addChild(removePack);
- move=new GUIObject(SCREEN_WIDTH*0.02,SCREEN_HEIGHT-60,-1,32,GUIObjectButton,_("Move Map"));
+ move=new GUIButton(SCREEN_WIDTH*0.02,SCREEN_HEIGHT-60,-1,32,_("Move Map"));
move->name="cmdMoveMap";
move->eventCallback=this;
//NOTE: Set enabled equal to the inverse of initial.
//When resizing the window initial will be false and therefor the move button can stay enabled.
move->enabled=false;
GUIObjectRoot->addChild(move);
- remove=new GUIObject(SCREEN_WIDTH*0.5,SCREEN_HEIGHT-60,-1,32,GUIObjectButton,_("Remove Map"),0,false,true,GUIGravityCenter);
+ remove=new GUIButton(SCREEN_WIDTH*0.5,SCREEN_HEIGHT-60,-1,32,_("Remove Map"),0,false,true,GUIGravityCenter);
remove->name="cmdRmMap";
remove->eventCallback=this;
GUIObjectRoot->addChild(remove);
- edit=new GUIObject(SCREEN_WIDTH*0.98,SCREEN_HEIGHT-60,-1,32,GUIObjectButton,_("Edit Map"),0,false,true,GUIGravityRight);
+ edit=new GUIButton(SCREEN_WIDTH*0.98,SCREEN_HEIGHT-60,-1,32,_("Edit Map"),0,false,true,GUIGravityRight);
edit->name="cmdEdit";
edit->eventCallback=this;
GUIObjectRoot->addChild(edit);
//Now update widgets and then check if they overlap
GUIObjectRoot->render(0,0,false);
if(propertiesPack->left-propertiesPack->gravityX < obj->left+obj->width ||
propertiesPack->left-propertiesPack->gravityX+propertiesPack->width > removePack->left-removePack->gravityX){
obj->smallFont=true;
obj->width=-1;
propertiesPack->smallFont=true;
propertiesPack->width=-1;
removePack->smallFont=true;
removePack->width=-1;
move->smallFont=true;
move->width=-1;
remove->smallFont=true;
remove->width=-1;
edit->smallFont=true;
edit->width=-1;
}
//Check again
GUIObjectRoot->render(0,0,false);
if(propertiesPack->left-propertiesPack->gravityX < obj->left+obj->width ||
propertiesPack->left-propertiesPack->gravityX+propertiesPack->width > removePack->left-removePack->gravityX){
obj->left = SCREEN_WIDTH*0.02;
obj->top = SCREEN_HEIGHT-140;
obj->smallFont=false;
obj->width=-1;
obj->gravity = GUIGravityLeft;
propertiesPack->left = SCREEN_WIDTH*0.02;
propertiesPack->top = SCREEN_HEIGHT-100;
propertiesPack->smallFont=false;
propertiesPack->width=-1;
propertiesPack->gravity = GUIGravityLeft;
removePack->left = SCREEN_WIDTH*0.02;
removePack->top = SCREEN_HEIGHT-60;
removePack->smallFont=false;
removePack->width=-1;
removePack->gravity = GUIGravityLeft;
move->left = SCREEN_WIDTH*0.98;
move->top = SCREEN_HEIGHT-140;
move->smallFont=false;
move->width=-1;
move->gravity = GUIGravityRight;
remove->left = SCREEN_WIDTH*0.98;
remove->top = SCREEN_HEIGHT-100;
remove->smallFont=false;
remove->width=-1;
remove->gravity = GUIGravityRight;
edit->left = SCREEN_WIDTH*0.98;
edit->top = SCREEN_HEIGHT-60;
edit->smallFont=false;
edit->width=-1;
edit->gravity = GUIGravityRight;
}
}
void LevelEditSelect::changePack(){
packName=levelpacks->item[levelpacks->value].second;
if(packName=="Custom Levels"){
//Disable some levelpack buttons.
propertiesPack->enabled=false;
removePack->enabled=false;
}else{
//Enable some levelpack buttons.
propertiesPack->enabled=true;
removePack->enabled=true;
}
//Set last levelpack.
getSettings()->setValue("lastlevelpack",levelpacks->getName());
//Now let levels point to the right pack.
levels=getLevelPackManager()->getLevelPack(levelpacks->getName());
}
void LevelEditSelect::packProperties(bool newPack){
//Open a message popup.
- GUIObject* root=new GUIObject((SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-320)/2,600,320,GUIObjectFrame,_("Properties"));
+ GUIObject* root=new GUIFrame((SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-320)/2,600,320,_("Properties"));
GUIObject* obj;
- obj=new GUIObject(40,50,240,36,GUIObjectLabel,_("Name:"));
+ obj=new GUILabel(40,50,240,36,_("Name:"));
root->addChild(obj);
- obj=new GUIObject(60,80,480,36,GUIObjectTextBox,packName.c_str());
+ obj=new GUITextBox(60,80,480,36,packName.c_str());
if(newPack)
obj->caption="";
obj->name="LvlpackName";
root->addChild(obj);
- obj=new GUIObject(40,120,240,36,GUIObjectLabel,_("Description:"));
+ obj=new GUILabel(40,120,240,36,_("Description:"));
root->addChild(obj);
- obj=new GUIObject(60,150,480,36,GUIObjectTextBox,levels->levelpackDescription.c_str());
+ obj=new GUITextBox(60,150,480,36,levels->levelpackDescription.c_str());
if(newPack)
obj->caption="";
obj->name="LvlpackDescription";
root->addChild(obj);
- obj=new GUIObject(40,190,240,36,GUIObjectLabel,_("Congratulation text:"));
+ obj=new GUILabel(40,190,240,36,_("Congratulation text:"));
root->addChild(obj);
- obj=new GUIObject(60,220,480,36,GUIObjectTextBox,levels->congratulationText.c_str());
+ obj=new GUITextBox(60,220,480,36,levels->congratulationText.c_str());
if(newPack)
obj->caption="";
obj->name="LvlpackCongratulation";
root->addChild(obj);
- obj=new GUIObject(root->width*0.3,320-44,-1,36,GUIObjectButton,_("OK"),0,true,true,GUIGravityCenter);
+ obj=new GUIButton(root->width*0.3,320-44,-1,36,_("OK"),0,true,true,GUIGravityCenter);
obj->name="cfgOK";
obj->eventCallback=this;
root->addChild(obj);
- obj=new GUIObject(root->width*0.7,320-44,-1,36,GUIObjectButton,_("Cancel"),0,true,true,GUIGravityCenter);
+ obj=new GUIButton(root->width*0.7,320-44,-1,36,_("Cancel"),0,true,true,GUIGravityCenter);
obj->name="cfgCancel";
obj->eventCallback=this;
root->addChild(obj);
//Create the gui overlay.
//NOTE: We don't need to store a pointer since it will auto cleanup itself.
new GUIOverlay(root);
if(newPack){
packName.clear();
}
}
void LevelEditSelect::addLevel(){
//Open a message popup.
- GUIObject* root=new GUIObject((SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-200)/2,600,200,GUIObjectFrame,_("Add level"));
+ GUIObject* root=new GUIFrame((SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-200)/2,600,200,_("Add level"));
GUIObject* obj;
- obj=new GUIObject(40,80,240,36,GUIObjectLabel,_("File name:"));
+ obj=new GUILabel(40,80,240,36,_("File name:"));
root->addChild(obj);
char s[64];
sprintf(s,"map%02d.map",levels->getLevelCount()+1);
- obj=new GUIObject(300,80,240,36,GUIObjectTextBox,s);
+ obj=new GUITextBox(300,80,240,36,s);
obj->name="LvlFile";
root->addChild(obj);
- obj=new GUIObject(root->width*0.3,200-44,-1,36,GUIObjectButton,_("OK"),0,true,true,GUIGravityCenter);
+ obj=new GUIButton(root->width*0.3,200-44,-1,36,_("OK"),0,true,true,GUIGravityCenter);
obj->name="cfgAddOK";
obj->eventCallback=this;
root->addChild(obj);
- obj=new GUIObject(root->width*0.7,200-44,-1,36,GUIObjectButton,_("Cancel"),0,true,true,GUIGravityCenter);
+ obj=new GUIButton(root->width*0.7,200-44,-1,36,_("Cancel"),0,true,true,GUIGravityCenter);
obj->name="cfgAddCancel";
obj->eventCallback=this;
root->addChild(obj);
//Dim the screen using the tempSurface.\
//NOTE: We don't need to store a pointer since it will auto cleanup itself.
new GUIOverlay(root);
}
void LevelEditSelect::moveLevel(){
//Open a message popup.
- GUIObject* root=new GUIObject((SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-200)/2,600,200,GUIObjectFrame,_("Move level"));
+ GUIObject* root=new GUIFrame((SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-200)/2,600,200,_("Move level"));
GUIObject* obj;
- obj=new GUIObject(40,60,240,36,GUIObjectLabel,_("Level: "));
+ obj=new GUILabel(40,60,240,36,_("Level: "));
root->addChild(obj);
- obj=new GUIObject(300,60,240,36,GUIObjectTextBox,"1");
+ obj=new GUITextBox(300,60,240,36,"1");
obj->name="MoveLevel";
root->addChild(obj);
obj=new GUISingleLineListBox(root->width*0.5,110,240,36,true,true,GUIGravityCenter);
obj->name="lstPlacement";
vector<string> v;
v.push_back(_("Before"));
v.push_back(_("After"));
v.push_back(_("Swap"));
(dynamic_cast<GUISingleLineListBox*>(obj))->addItems(v);
obj->value=0;
root->addChild(obj);
- obj=new GUIObject(root->width*0.3,200-44,-1,36,GUIObjectButton,_("OK"),0,true,true,GUIGravityCenter);
+ obj=new GUIButton(root->width*0.3,200-44,-1,36,_("OK"),0,true,true,GUIGravityCenter);
obj->name="cfgMoveOK";
obj->eventCallback=this;
root->addChild(obj);
- obj=new GUIObject(root->width*0.7,200-44,-1,36,GUIObjectButton,_("Cancel"),0,true,true,GUIGravityCenter);
+ obj=new GUIButton(root->width*0.7,200-44,-1,36,_("Cancel"),0,true,true,GUIGravityCenter);
obj->name="cfgMoveCancel";
obj->eventCallback=this;
root->addChild(obj);
//Create the gui overlay.
//NOTE: We don't need to store a pointer since it will auto cleanup itself.
new GUIOverlay(root);
}
void LevelEditSelect::refresh(bool change){
int m=levels->getLevelCount();
if(change){
numbers.clear();
//clear the selected level
if(selectedNumber!=NULL){
selectedNumber=NULL;
}
//Disable the level specific buttons.
move->enabled=false;
remove->enabled=false;
edit->enabled=false;
for(int n=0;n<=m;n++){
numbers.push_back(Number());
}
}
for(int n=0;n<m;n++){
SDL_Rect box={(n%LEVELS_PER_ROW)*64+80,(n/LEVELS_PER_ROW)*64+184,0,0};
numbers[n].init(n,box);
}
SDL_Rect box={(m%LEVELS_PER_ROW)*64+80,(m/LEVELS_PER_ROW)*64+184,0,0};
numbers[m].init("+",box);
m++; //including the "+" button
if(m>LEVELS_DISPLAYED_IN_SCREEN){
levelScrollBar->maxValue=(m-LEVELS_DISPLAYED_IN_SCREEN+LEVELS_PER_ROW-1)/LEVELS_PER_ROW;
levelScrollBar->visible=true;
}else{
levelScrollBar->maxValue=0;
levelScrollBar->visible=false;
}
if(!levels->levelpackDescription.empty())
levelpackDescription->caption=_CC(levels->getDictionaryManager(),levels->levelpackDescription);
else
levelpackDescription->caption="";
}
void LevelEditSelect::selectNumber(unsigned int number,bool selected){
if(selected){
levels->setCurrentLevel(number);
setNextState(STATE_LEVEL_EDITOR);
//Pick music from the current music list.
getMusicManager()->pickMusic();
}else{
if(number==numbers.size()-1){
addLevel();
}else if(number<numbers.size()){
selectedNumber=&numbers[number];
//Enable the level specific buttons.
//NOTE: We check if 'remove levelpack' is enabled, if not then it's the Levels levelpack.
if(removePack->enabled)
move->enabled=true;
remove->enabled=true;
edit->enabled=true;
}
}
}
void LevelEditSelect::render(){
//Let the levelselect render.
LevelSelect::render();
}
void LevelEditSelect::resize(){
//Let the levelselect resize.
LevelSelect::resize();
//Create the GUI.
createGUI(false);
//NOTE: This is a workaround for buttons failing when resizing.
if(packName=="Custom Levels"){
removePack->enabled=false;
propertiesPack->enabled=false;
}
if(selectedNumber)
selectNumber(selectedNumber->getNumber(),false);
}
void LevelEditSelect::renderTooltip(unsigned int number,int dy){
SDL_Color fg={0,0,0};
SDL_Surface* name;
if(number==(unsigned)levels->getLevelCount()){
//Render the name of the level.
name=TTF_RenderUTF8_Blended(fontText,_("Add level"),fg);
}else{
//Render the name of the level.
name=TTF_RenderUTF8_Blended(fontText,_CC(levels->getDictionaryManager(),levels->getLevelName(number)),fg);
}
//Check if name isn't null.
if(name==NULL)
return;
//Now draw a square the size of the three texts combined.
SDL_Rect r=numbers[number].box;
r.y-=dy*64;
r.w=name->w;
r.h=name->h;
//Make sure the tooltip doesn't go outside the window.
if(r.y>SCREEN_HEIGHT-200){
r.y-=name->h+4;
}else{
r.y+=numbers[number].box.h+2;
}
if(r.x+r.w>SCREEN_WIDTH-50)
r.x=SCREEN_WIDTH-50-r.w;
//Draw a rectange
Uint32 color=0xFFFFFFFF;
drawGUIBox(r.x-5,r.y-5,r.w+10,r.h+10,screen,color);
//Calc the position to draw.
SDL_Rect r2=r;
//Now we render the name if the surface isn't null.
if(name!=NULL){
//Draw the name.
SDL_BlitSurface(name,NULL,screen,&r2);
}
//And free the surfaces.
SDL_FreeSurface(name);
}
void LevelEditSelect::GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType){
//NOTE: We check for the levelpack change to enable/disable some levelpack buttons.
if(name=="cmdLvlPack"){
//We call changepack and return to prevent the LevelSelect to undo what we did.
changePack();
refresh();
return;
}
//Let the level select handle his GUI events.
LevelSelect::GUIEventCallback_OnEvent(name,obj,eventType);
//Check for the edit button.
if(name=="cmdNewLvlpack"){
//Create a new pack.
packProperties(true);
}else if(name=="cmdLvlpackProp"){
//Show the pack properties.
packProperties(false);
}else if(name=="cmdRmLvlpack"){
//Show an "are you sure" message.
if(msgBox(_("Are you sure?"),MsgBoxYesNo,_("Remove prompt"))==MsgBoxYes){
//Remove the directory.
if(!removeDirectory(levels->levelpackPath.c_str())){
cerr<<"ERROR: Unable to remove levelpack directory "<<levels->levelpackPath<<endl;
}
//Remove it from the vector (levelpack list).
vector<pair<string,string> >::iterator it;
for(it=levelpacks->item.begin();it!=levelpacks->item.end();++it){
if(it->second==packName)
levelpacks->item.erase(it);
}
//Remove it from the levelpackManager.
getLevelPackManager()->removeLevelPack(levels->levelpackPath);
//And call changePack.
levelpacks->value=levelpacks->item.size()-1;
changePack();
refresh();
}
}else if(name=="cmdMoveMap"){
if(selectedNumber!=NULL){
moveLevel();
}
}else if(name=="cmdRmMap"){
if(selectedNumber!=NULL){
if(packName!="Custom Levels"){
if(!removeFile((levels->levelpackPath+"/"+levels->getLevel(selectedNumber->getNumber())->file).c_str())){
cerr<<"ERROR: Unable to remove level "<<(levels->levelpackPath+"/"+levels->getLevel(selectedNumber->getNumber())->file).c_str()<<endl;
}
levels->removeLevel(selectedNumber->getNumber());
levels->saveLevels(levels->levelpackPath+"/levels.lst");
}else{
//This is the levels levelpack so we just remove the file.
if(!removeFile(levels->getLevel(selectedNumber->getNumber())->file.c_str())){
cerr<<"ERROR: Unable to remove level "<<levels->getLevel(selectedNumber->getNumber())->file<<endl;
}
levels->removeLevel(selectedNumber->getNumber());
}
//And refresh the selection screen.
refresh();
}
}else if(name=="cmdEdit"){
if(selectedNumber!=NULL){
levels->setCurrentLevel(selectedNumber->getNumber());
setNextState(STATE_LEVEL_EDITOR);
//Pick music from the current music list.
getMusicManager()->pickMusic();
}
}
//Check for levelpack properties events.
if(name=="cfgOK"){
//Now loop throught the children of the GUIObjectRoot in search of the fields.
for(unsigned int i=0;i<GUIObjectRoot->childControls.size();i++){
if(GUIObjectRoot->childControls[i]->name=="LvlpackName"){
//Check if the name changed.
if(packName!=GUIObjectRoot->childControls[i]->caption){
//Delete the old one.
if(!packName.empty()){
if(!renameDirectory((getUserPath(USER_DATA)+"custom/levelpacks/"+packName).c_str(),(getUserPath(USER_DATA)+"custom/levelpacks/"+GUIObjectRoot->childControls[i]->caption).c_str())){
cerr<<"ERROR: Unable to move levelpack directory "<<(getUserPath(USER_DATA)+"custom/levelpacks/"+packName)<<" to "<<(getUserPath(USER_DATA)+"custom/levelpacks/"+GUIObjectRoot->childControls[i]->caption)<<endl;
}
//Remove the old one from the levelpack manager.
getLevelPackManager()->removeLevelPack(levelpacks->getName());
//And the levelpack list.
vector<pair<string,string> >::iterator it1;
for(it1=levelpacks->item.begin();it1!=levelpacks->item.end();++it1){
if(it1!=levelpacks->item.end()){
levelpacks->item.erase(it1);
break;
}
}
}else{
//It's a new levelpack so we need to change the levels array.
LevelPack* pack=new LevelPack;
levels=pack;
//Now create the dirs.
if(!createDirectory((getUserPath(USER_DATA)+"custom/levelpacks/"+GUIObjectRoot->childControls[i]->caption).c_str())){
cerr<<"ERROR: Unable to create levelpack directory "<<(getUserPath(USER_DATA)+"custom/levelpacks/"+GUIObjectRoot->childControls[i]->caption)<<endl;
}
if(!createFile((getUserPath(USER_DATA)+"custom/levelpacks/"+GUIObjectRoot->childControls[i]->caption+"/levels.lst").c_str())){
cerr<<"ERROR: Unable to create levelpack file "<<(getUserPath(USER_DATA)+"custom/levelpacks/"+GUIObjectRoot->childControls[i]->caption+"/levels.lst")<<endl;
}
}
//And set the new name.
packName=GUIObjectRoot->childControls[i]->caption;
levels->levelpackName=packName;
levels->levelpackPath=(getUserPath(USER_DATA)+"custom/levelpacks/"+packName+"/");
//Also add the levelpack location
getLevelPackManager()->addLevelPack(levels);
levelpacks->addItem(levels->levelpackPath,GUIObjectRoot->childControls[i]->caption);
levelpacks->value=levelpacks->item.size()-1;
//And call changePack.
changePack();
}
}
if(GUIObjectRoot->childControls[i]->name=="LvlpackDescription"){
levels->levelpackDescription=GUIObjectRoot->childControls[i]->caption;
}
if(GUIObjectRoot->childControls[i]->name=="LvlpackCongratulation"){
levels->congratulationText=GUIObjectRoot->childControls[i]->caption;
}
}
//Refresh the leveleditselect to show the correct information.
refresh();
//Save the configuration.
levels->saveLevels(getUserPath(USER_DATA)+"custom/levelpacks/"+packName+"/levels.lst");
getSettings()->setValue("lastlevelpack",levels->levelpackPath);
//Clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}else if(name=="cfgCancel"){
//Check if packName is empty, if so it was a new levelpack and we need to revert to an existing one.
if(packName.empty()){
packName=levelpacks->item[levelpacks->value].second;
changePack();
}
//Clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
//Check for add level events.
if(name=="cfgAddOK"){
//Check if the file name isn't null.
//Now loop throught the children of the GUIObjectRoot in search of the fields.
for(unsigned int i=0;i<GUIObjectRoot->childControls.size();i++){
if(GUIObjectRoot->childControls[i]->name=="LvlFile"){
if(GUIObjectRoot->childControls[i]->caption.empty()){
msgBox(_("No file name given for the new level."),MsgBoxOKOnly,_("Missing file name"));
return;
}else{
string tmp_caption = GUIObjectRoot->childControls[i]->caption;
//Replace all spaces with a underline.
size_t j;
for(;(j=tmp_caption.find(" "))!=string::npos;){
tmp_caption.replace(j,1,"_");
}
//If there isn't ".map" extension add it.
size_t found=tmp_caption.find_first_of(".");
if(found!=string::npos)
tmp_caption.replace(tmp_caption.begin()+found+1,tmp_caption.end(),"map");
else if (tmp_caption.substr(found+1)!="map")
tmp_caption.append(".map");
/* Create path and file in it */
string path=(levels->levelpackPath+"/"+tmp_caption);
if(packName=="Custom Levels"){
path=(getUserPath(USER_DATA)+"/custom/levels/"+tmp_caption);
}
//First check if the file doesn't exist already.
FILE* f;
f=fopen(path.c_str(),"rb");
//Check if it exists.
if(f){
//Close the file.
fclose(f);
//Let the currentState render once to prevent multiple GUI overlapping and prevent the screen from going black.
currentState->render();
levelEditGUIObjectRoot->render();
//Notify the user.
msgBox(string("The file "+tmp_caption+" already exists."),MsgBoxOKOnly,"Error");
return;
}
if(!createFile(path.c_str())){
cerr<<"ERROR: Unable to create level file "<<path<<endl;
}else{
//Update statistics.
statsMgr.newAchievement("create1");
if((++statsMgr.createdLevels)>=50) statsMgr.newAchievement("create50");
}
levels->addLevel(path);
//NOTE: Also add the level to the levels levelpack in case of custom levels.
if(packName=="Custom Levels"){
LevelPack* levelsPack=getLevelPackManager()->getLevelPack("Levels");
if(levelsPack){
levelsPack->addLevel(path);
levelsPack->setLocked(levelsPack->getLevelCount()-1);
}else{
cerr<<_("ERROR: Unable to add level to Levels levelpack")<<endl;
}
}
if(packName!="Custom Levels")
levels->saveLevels(getUserPath(USER_DATA)+"custom/levelpacks/"+packName+"/levels.lst");
refresh();
//Clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
return;
}
}
}
}
}else if(name=="cfgAddCancel"){
//Clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
//Check for move level events.
if(name=="cfgMoveOK"){
//Check if the entered level number is valid.
//Now loop throught the children of the GUIObjectRoot in search of the fields.
int level=0;
int placement=0;
for(unsigned int i=0;i<GUIObjectRoot->childControls.size();i++){
if(GUIObjectRoot->childControls[i]->name=="MoveLevel"){
level=atoi(GUIObjectRoot->childControls[i]->caption.c_str());
if(level<=0 || level>levels->getLevelCount()){
msgBox(_("The entered level number isn't valid!"),MsgBoxOKOnly,_("Illegal number"));
return;
}
}
if(GUIObjectRoot->childControls[i]->name=="lstPlacement"){
placement=GUIObjectRoot->childControls[i]->value;
}
}
//Now we execute the swap/move.
//Check for the place before.
if(placement==0){
//We place the selected level before the entered level.
levels->moveLevel(selectedNumber->getNumber(),level-1);
}else if(placement==1){
//We place the selected level after the entered level.
if(level<selectedNumber->getNumber())
levels->moveLevel(selectedNumber->getNumber(),level);
else
levels->moveLevel(selectedNumber->getNumber(),level+1);
}else if(placement==2){
//We swap the selected level with the entered level.
levels->swapLevel(selectedNumber->getNumber(),level-1);
}
//And save the change.
if(packName!="Custom Levels")
levels->saveLevels(getUserPath(USER_DATA)+"custom/levelpacks/"+packName+"/levels.lst");
refresh();
//Clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}else if(name=="cfgMoveCancel"){
//Clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
}
diff --git a/src/LevelEditSelect.h b/src/LevelEditSelect.h
index 304bb07..8b112fc 100644
--- a/src/LevelEditSelect.h
+++ b/src/LevelEditSelect.h
@@ -1,102 +1,102 @@
/*
* 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/>.
*/
#ifndef LEVELEDITSELECT_H
#define LEVELEDITSELECT_H
#include "LevelSelect.h"
#include "GameState.h"
#include "GameObjects.h"
#include "Player.h"
#include "GUIObject.h"
#ifdef __APPLE__
#include <SDL_mixer/SDL_mixer.h>
#include <SDL_ttf/SDL_ttf.h>
#else
#include <SDL/SDL_mixer.h>
#include <SDL/SDL_ttf.h>
#endif
#include <vector>
#include <string>
//This is the LevelEditSelect state, here you can select levelpacks and levels.
class LevelEditSelect :public LevelSelect{
private:
//Pointer to the GUIObjectRoot of the levelselect main gui.
GUIObject* levelEditGUIObjectRoot;
//Pointer to the new levelpack textfield.
GUIObject* levelpackName;
//Pointer to the remove levelpack button.
- GUIObject* propertiesPack;
+ GUIButton* propertiesPack;
//Pointer to the remove levelpack button.
- GUIObject* removePack;
+ GUIButton* removePack;
//Pointer to the move map button.
- GUIObject* move;
+ GUIButton* move;
//Pointer to the remove map button.
- GUIObject* remove;
+ GUIButton* remove;
//Pointer to the edit map button.
- GUIObject* edit;
+ GUIButton* edit;
//String that contains the name of the current levelpack.
std::string packName;
//Method that will create the GUI elements.
//initial: Boolean if it is the first time the gui is created.
void createGUI(bool initial);
//Method that should be called when changing the current levelpack in an abnormal way.
void changePack();
//This method will show a popup with levelpack specific settings.
//newPack: Boolean if it's a new levelpack.
void packProperties(bool newPack);
//This method will show an add level dialog.
void addLevel();
//This method will show an move level dialog.
void moveLevel();
public:
//Constructor.
LevelEditSelect();
//Destructor.
~LevelEditSelect();
//Inherited from LevelSelect.
//change: Boolean if the levelpack changed, if not we only have to rearrange the numbers.
void refresh(bool change=true);
void selectNumber(unsigned int number,bool selected);
//Inherited from GameState.
void render();
//Inherited from GameState.
void resize();
//Inherited from LevelSelect.
void renderTooltip(unsigned int number,int dy);
//GUI events will be handled here.
void GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType);
};
#endif
diff --git a/src/LevelEditor.cpp b/src/LevelEditor.cpp
index 4681f8d..91ef3dc 100644
--- a/src/LevelEditor.cpp
+++ b/src/LevelEditor.cpp
@@ -1,3308 +1,3308 @@
/*
* 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 "GameState.h"
#include "Globals.h"
#include "Functions.h"
#include "FileManager.h"
#include "GameObjects.h"
#include "ThemeManager.h"
#include "Objects.h"
#include "LevelPack.h"
#include "LevelEditor.h"
#include "TreeStorageNode.h"
#include "POASerializer.h"
#include "GUIListBox.h"
#include "GUITextArea.h"
#include "GUIWindow.h"
#include "GUISpinBox.h"
#include "InputManager.h"
#include "StatisticsManager.h"
#include <fstream>
#include <iostream>
#include <vector>
#include <algorithm>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sstream>
#ifdef WIN32
#include <windows.h>
#include <shlobj.h>
#else
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>
#endif
#include "libs/tinyformat/tinyformat.h"
using namespace std;
static int levelTime,levelRecordings;
//Array containing translateble block names
static const char* blockNames[TYPE_MAX]={
__("Block"),__("Player Start"),__("Shadow Start"),
__("Exit"),__("Shadow Block"),__("Spikes"),
__("Checkpoint"),__("Swap"),__("Fragile"),
__("Moving Block"),__("Moving Shadow Block"),__("Moving Spikes"),
__("Teleporter"),__("Button"),__("Switch"),
__("Conveyor Belt"),__("Shadow Conveyor Belt"),__("Notification Block"),__("Collectable"),__("Pushable")
};
//Array indicates if block is linkable
static const bool isLinkable[TYPE_MAX]={
false,false,false,
false,false,false,
false,false,false,
true,true,true,
true,true,true,
false,false,false,false
};
/////////////////LevelEditorActionsPopup/////////////////
class LevelEditorActionsPopup:private GUIEventCallback{
private:
//The parent object.
LevelEditor* parent;
//The position and size of window.
SDL_Rect rect;
//Array containing the actions in this popup.
GUIListBox* actions;
//GUI image.
SDL_Surface* bmGUI;
//Pointer to the object the actions apply to.
GameObject* target;
//The behaviour names.
vector<string> behaviour;
//The fragile block states.
vector<string> states;
public:
SDL_Rect getRect(){
return rect;
}
void dismiss(){
//Remove the actionsPopup from the parent.
if(parent!=NULL && parent->actionsPopup==this){
parent->actionsPopup=NULL;
}
//And delete ourself.
delete this;
}
SDL_Surface* createItem(const char* caption,int icon){
//FIXME: Add some sort of caching?
SDL_Color fg={0,0,0};
SDL_Surface* tip=TTF_RenderUTF8_Blended(fontText,caption,fg);
SDL_SetAlpha(tip,0,0xFF);
//Create the surface, we add 16px to the width for an icon,
//plus 8px for the border to make it looks better.
SDL_Surface* item=SDL_CreateRGBSurface(SDL_SWSURFACE,tip->w+16+8,24,32,RMASK,GMASK,BMASK,AMASK);
SDL_Rect itemRect={0,0,item->w,item->h};
SDL_FillRect(item,&itemRect,0x00FFFFFF);
itemRect.y=3;
itemRect.h=16;
SDL_FillRect(item,&itemRect,0xFFFFFFFF);
//Draw the text on the item surface.
applySurface(16+8,0,tip,item,NULL);
//Check if we should draw an icon.
if(icon>0){
//Draw the check (or not).
SDL_Rect r={0,0,16,16};
r.x=((icon-1)%8)*16;
r.y=((icon-1)/8)*16;
applySurface(4,3,bmGUI,item,&r);
}
//Free the tip surface.
SDL_FreeSurface(tip);
//Update the height.
rect.h+=24;
//Check if we should update the width., 8px extra on the width is for four pixels spacing on either side.
if(item->w+8>rect.w)
rect.w=item->w+8;
return item;
}
void updateItem(int index,const char* action,const char* caption,int icon=0){
SDL_Surface* item=createItem(caption,icon);
actions->updateItem(index,action,item);
}
void addItem(const char* action,const char* caption,int icon=0){
SDL_Surface* item=createItem(caption,icon);
actions->addItem(action,item);
}
LevelEditorActionsPopup(LevelEditor* parent, GameObject* target, int x=0, int y=0){
this->parent=parent;
this->target=target;
//NOTE: The size gets set in the addItem method, height is already four to prevent a scrollbar.
rect.w=0;
rect.h=4;
//Load the gui images.
bmGUI=loadImage(getDataPath()+"gfx/gui.png");
//Create the behaviour vector.
behaviour.push_back(_("On"));
behaviour.push_back(_("Off"));
behaviour.push_back(_("Toggle"));
//Create the states list.
states.push_back(_("Complete"));
states.push_back(_("One step"));
states.push_back(_("Two steps"));
states.push_back(_("Gone"));
//TODO: The width should be based on the longest option.
//Create default actions.
//NOTE: Width and height are determined later on when the options are rendered.
actions=new GUIListBox(0,0,0,0);
actions->eventCallback=this;
//Check if it's a block or not.
if(target!=NULL)
addBlockItems();
else
addLevelItems();
//Now set the size of the GUIListBox.
actions->width=rect.w;
actions->height=rect.h;
if(x>SCREEN_WIDTH-rect.w) x=SCREEN_WIDTH-rect.w;
else if(x<0) x=0;
if(y>SCREEN_HEIGHT-rect.h) y=SCREEN_HEIGHT-rect.h;
else if(y<0) y=0;
rect.x=x;
rect.y=y;
}
void addBlockItems(){
//Get the type of the target.
int type=target->type;
//Check if the block is selected or not.
std::vector<GameObject*>::iterator it;
it=find(parent->selection.begin(),parent->selection.end(),target);
if(it!=parent->selection.end())
addItem("Deselect",_("Deselect"));
else
addItem("Select",_("Select"));
addItem("Delete",_("Delete"),8);
//Determine what to do depending on the type.
if(isLinkable[type]){
//Check if it's a moving block type or trigger.
if(type==TYPE_BUTTON || type==TYPE_SWITCH || type==TYPE_PORTAL){
addItem("Link",_("Link"),8*3);
addItem("Remove Links",_("Remove Links"));
//Check if it's a portal, which contains a automatic option, and triggers a behaviour one.
if(type==TYPE_PORTAL){
addItem("Automatic",_("Automatic"),(target->getEditorProperty("automatic")=="1")?2:1);
}else{
//Get the current behaviour.
int currentBehaviour=2;
if(target->getEditorProperty("behaviour")=="on"){
currentBehaviour=0;
}else if(target->getEditorProperty("behaviour")=="off"){
currentBehaviour=1;
}
addItem("Behaviour",behaviour[currentBehaviour].c_str());
}
}else{
addItem("Path",_("Path"));
addItem("Remove Path",_("Remove Path"));
//FIXME: We use hardcoded indeces, if the order changes we have a problem.
addItem("Enabled",_("Enabled"),(target->getEditorProperty("disabled")=="0")?2:1);
addItem("Looping",_("Looping"),(target->getEditorProperty("loop")=="1")?2:1);
}
}
//Check for a conveyor belt.
if(type==TYPE_CONVEYOR_BELT || type==TYPE_SHADOW_CONVEYOR_BELT){
addItem("Enabled",_("Enabled"),(target->getEditorProperty("disabled")=="0")?2:1);
addItem("Speed",_("Speed"));
}
//Check if it's a fragile block.
if(type==TYPE_FRAGILE){
//Get the current state.
int currentState=atoi(target->getEditorProperty("state").c_str());
addItem("State",states[currentState].c_str());
}
//Check if it's a notification block.
if(type==TYPE_NOTIFICATION_BLOCK)
addItem("Message",_("Message"));
//Finally add scripting to the bottom.
addItem("Scripting",_("Scripting"),8*2+1);
}
void addLevelItems(){
addItem("LevelSettings",_("Settings"),8*2);
addItem("LevelScripting",_("Scripting"),8*2+1);
}
~LevelEditorActionsPopup(){
if(actions)
delete actions;
}
void render(){
//Draw the actions.
actions->render(rect.x,rect.y);
//get mouse position
int x,y;
SDL_GetMouseState(&x,&y);
SDL_Rect mouse={x,y,0,0};
}
void handleEvents(){
//Check if a mouse is pressed outside the popup.
int x,y;
SDL_GetMouseState(&x,&y);
SDL_Rect mouse={x,y,0,0};
if(event.type==SDL_MOUSEBUTTONDOWN && !checkCollision(mouse,rect)){
dismiss();
return;
}
//Let the listbox handle its events.
actions->handleEvents(rect.x,rect.y);
}
void GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType){
//NOTE: There should only be one GUIObject, so we know what event is fired.
//Get the selected entry.
std::string action=actions->item[actions->value];
if(action=="Select"){
//Add the target to the selection.
parent->selection.push_back(target);
dismiss();
return;
}else if(action=="Deselect"){
//Check if the block is in the selection.
std::vector<GameObject*>::iterator it;
it=find(parent->selection.begin(),parent->selection.end(),target);
if(it!=parent->selection.end()){
//Remove the object from the selection.
parent->selection.erase(it);
}
dismiss();
return;
}else if(action=="Delete"){
parent->removeObject(target);
dismiss();
return;
}else if(action=="Link"){
parent->linking=true;
parent->linkingTrigger=dynamic_cast<Block*>(target);
parent->tool=LevelEditor::SELECT;
dismiss();
return;
}else if(action=="Remove Links"){
//Remove all the
std::map<Block*,vector<GameObject*> >::iterator it;
it=parent->triggers.find(dynamic_cast<Block*>(target));
if(it!=parent->triggers.end()){
//Remove the targets.
(*it).second.clear();
}
//In case of a portal remove its destination field.
if(target->type==TYPE_PORTAL){
target->setEditorProperty("destination","");
}else{
//We give the trigger a new id to prevent activating unlinked targets.
char s[64];
sprintf(s,"%u",parent->currentId);
parent->currentId++;
target->setEditorProperty("id",s);
}
dismiss();
return;
}else if(action=="Path"){
parent->moving=true;
parent->movingBlock=dynamic_cast<Block*>(target);
parent->tool=LevelEditor::SELECT;
dismiss();
return;
}else if(action=="Remove Path"){
//Set the number of moving positions to zero.
target->setEditorProperty("MovingPosCount","0");
std::map<Block*,vector<MovingPosition> >::iterator it;
it=parent->movingBlocks.find(dynamic_cast<Block*>(target));
if(it!=parent->movingBlocks.end()){
(*it).second.clear();
}
dismiss();
return;
}else if(action=="Message"){
//Create the GUI.
GUIWindow* root=new GUIWindow((SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-250)/2,600,250,true,true,_("Notification block"));
root->name="notificationBlockWindow";
root->eventCallback=parent;
GUIObject* obj;
- obj=new GUIObject(40,50,240,36,GUIObjectLabel,_("Enter message here:"));
+ obj=new GUILabel(40,50,240,36,_("Enter message here:"));
root->addChild(obj);
GUITextArea* textarea=new GUITextArea(50,90,500,100);
//Set the name of the text area, which is used to identify the object later on.
textarea->name="message";
string tmp=target->getEditorProperty("message");
//Change \n with the characters '\n'.
while(tmp.find("\\n")!=string::npos){
tmp=tmp.replace(tmp.find("\\n"),2,"\n");
}
textarea->setString(tmp);
root->addChild(textarea);
- obj=new GUIObject(root->width*0.3,250-44,-1,36,GUIObjectButton,_("OK"),0,true,true,GUIGravityCenter);
+ obj=new GUIButton(root->width*0.3,250-44,-1,36,_("OK"),0,true,true,GUIGravityCenter);
obj->name="cfgNotificationBlockOK";
obj->eventCallback=root;
root->addChild(obj);
- obj=new GUIObject(root->width*0.7,250-44,-1,36,GUIObjectButton,_("Cancel"),0,true,true,GUIGravityCenter);
+ obj=new GUIButton(root->width*0.7,250-44,-1,36,_("Cancel"),0,true,true,GUIGravityCenter);
obj->name="cfgCancel";
obj->eventCallback=root;
root->addChild(obj);
//Add the window to the GUIObjectRoot and the objectWindows map.
GUIObjectRoot->addChild(root);
parent->objectWindows[root]=target;
//And dismiss this popup.
dismiss();
return;
}else if(action=="Enabled"){
//Get the previous state.
bool enabled=(target->getEditorProperty("disabled")=="0");
//Switch the state.
enabled=!enabled;
//NOTE: In case of enabled it is inverted since the property is actually disabled,
target->setEditorProperty("disabled",enabled?"0":"1");
updateItem(actions->value,"Enabled",_("Enabled"),enabled?2:1);
actions->value=-1;
return;
}else if(action=="Looping"){
//Get the previous state.
bool loop=(target->getEditorProperty("loop")=="1");
//Switch the state.
loop=!loop;
target->setEditorProperty("loop",loop?"1":"0");
updateItem(actions->value,"Looping",_("Looping"),loop?2:1);
actions->value=-1;
return;
}else if(action=="Automatic"){
//Get the previous state.
bool automatic=(target->getEditorProperty("automatic")=="1");
//Switch the state.
automatic=!automatic;
target->setEditorProperty("automatic",automatic?"1":"0");
updateItem(actions->value,"Automatic",_("Automatic"),automatic?2:1);
actions->value=-1;
return;
}else if(action=="Behaviour"){
//Get the current behaviour.
int currentBehaviour=2;
string behave=target->getEditorProperty("behaviour");
if(behave=="on"){
currentBehaviour=0;
}else if(behave=="off"){
currentBehaviour=1;
}
//Increase the behaviour.
currentBehaviour++;
if(currentBehaviour>2)
currentBehaviour=0;
//Update the data of the block.
target->setEditorProperty("behaviour",behaviour[currentBehaviour]);
//And update the item.
updateItem(actions->value,"Behaviour",behaviour[currentBehaviour].c_str());
actions->value=-1;
return;
}else if(action=="State"){
//Get the current state.
int currentState=atoi(target->getEditorProperty("state").c_str());
//Increase the state.
currentState++;
if(currentState>3)
currentState=0;
//Update the data of the block.
char s[64];
sprintf(s,"%d",currentState);
target->setEditorProperty("state",s);
//And update the item.
updateItem(actions->value,"State",states[currentState].c_str());
actions->value=-1;
return;
}else if(action=="Speed"){
//Create the GUI.
GUIWindow* root=new GUIWindow((SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-250)/2,600,250,true,true,_("Conveyor belt speed"));
root->name="conveyorBlockWindow";
root->eventCallback=parent;
GUIObject* obj;
- obj=new GUIObject(40,100,240,36,GUIObjectLabel,_("Enter speed here:"));
+ obj=new GUILabel(40,100,240,36,_("Enter speed here:"));
root->addChild(obj);
GUISpinBox* obj2=new GUISpinBox(240,100,320,36);
//Set the name of the text area, which is used to identify the object later on.
obj2->name="speed";
obj2->caption=target->getEditorProperty("speed");
obj2->update();
root->addChild(obj2);
- obj=new GUIObject(root->width*0.3,250-44,-1,36,GUIObjectButton,_("OK"),0,true,true,GUIGravityCenter);
+ obj=new GUIButton(root->width*0.3,250-44,-1,36,_("OK"),0,true,true,GUIGravityCenter);
obj->name="cfgConveyorBlockOK";
obj->eventCallback=root;
root->addChild(obj);
- obj=new GUIObject(root->width*0.7,250-44,-1,36,GUIObjectButton,_("Cancel"),0,true,true,GUIGravityCenter);
+ obj=new GUIButton(root->width*0.7,250-44,-1,36,_("Cancel"),0,true,true,GUIGravityCenter);
obj->name="cfgCancel";
obj->eventCallback=root;
root->addChild(obj);
//Add the window to the GUIObjectRoot and the objectWindows map.
GUIObjectRoot->addChild(root);
parent->objectWindows[root]=target;
//And dismiss this popup.
dismiss();
return;
}else if(action=="Scripting"){
//Create the GUI.
GUIWindow* root=new GUIWindow((SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-500)/2,600,500,true,true,_("Scripting"));
root->name="scriptingWindow";
root->eventCallback=parent;
GUIObject* obj;
- obj=new GUIObject(50,60,240,36,GUIObjectLabel,_("Id:"));
+ obj=new GUILabel(50,60,240,36,_("Id:"));
root->addChild(obj);
- obj=new GUIObject(100,60,240,36,GUIObjectTextBox,dynamic_cast<Block*>(target)->id.c_str());
+ obj=new GUITextBox(100,60,240,36,dynamic_cast<Block*>(target)->id.c_str());
obj->name="id";
root->addChild(obj);
GUISingleLineListBox* list=new GUISingleLineListBox(50,100,500,36);
std::map<std::string,int>::iterator it;
for(it=Game::gameObjectEventNameMap.begin();it!=Game::gameObjectEventNameMap.end();++it)
list->addItem(it->first);
list->name="cfgScriptingEventType";
list->value=0;
list->eventCallback=root;
root->addChild(list);
//Add a text area for each event type.
Block* block=dynamic_cast<Block*>(target);
for(unsigned int i=0;i<list->item.size();i++){
GUITextArea* text=new GUITextArea(50,140,500,300);
text->name=list->item[i].first;
text->setFont(fontMono);
//Only set the first one visible and enabled.
text->visible=(i==0);
text->enabled=(i==0);
map<int,string>::iterator it=block->scripts.find(Game::gameObjectEventNameMap[list->item[i].first]);
if(it!=block->scripts.end())
text->setString(it->second);
root->addChild(text);
}
- obj=new GUIObject(root->width*0.3,500-44,-1,36,GUIObjectButton,_("OK"),0,true,true,GUIGravityCenter);
+ obj=new GUIButton(root->width*0.3,500-44,-1,36,_("OK"),0,true,true,GUIGravityCenter);
obj->name="cfgScriptingOK";
obj->eventCallback=root;
root->addChild(obj);
- obj=new GUIObject(root->width*0.7,500-44,-1,36,GUIObjectButton,_("Cancel"),0,true,true,GUIGravityCenter);
+ obj=new GUIButton(root->width*0.7,500-44,-1,36,_("Cancel"),0,true,true,GUIGravityCenter);
obj->name="cfgCancel";
obj->eventCallback=root;
root->addChild(obj);
//Add the window to the GUIObjectRoot and the objectWindows map.
GUIObjectRoot->addChild(root);
parent->objectWindows[root]=target;
//And dismiss this popup.
dismiss();
return;
}else if(action=="LevelSettings"){
//Open the levelSettings window.
parent->levelSettings();
//And dismiss this popup.
dismiss();
return;
}else if(action=="LevelScripting"){
//Create the GUI.
GUIWindow* root=new GUIWindow((SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-500)/2,600,500,true,true,_("Level Scripting"));
root->name="levelScriptingWindow";
root->eventCallback=parent;
GUIObject* obj;
GUISingleLineListBox* list=new GUISingleLineListBox(50,60,500,36);
std::map<std::string,int>::iterator it;
for(it=Game::levelEventNameMap.begin();it!=Game::levelEventNameMap.end();++it)
list->addItem(it->first);
list->name="cfgLevelScriptingEventType";
list->value=0;
list->eventCallback=root;
root->addChild(list);
//Add a text area for each event type.
for(unsigned int i=0;i<list->item.size();i++){
GUITextArea* text=new GUITextArea(50,100,500,340);
text->name=list->item[i].first;
text->setFont(fontMono);
//Only set the first one visible and enabled.
text->visible=(i==0);
text->enabled=(i==0);
map<int,string>::iterator it=parent->scripts.find(Game::levelEventNameMap[list->item[i].first]);
if(it!=parent->scripts.end())
text->setString(it->second);
root->addChild(text);
}
- obj=new GUIObject(root->width*0.3,500-44,-1,36,GUIObjectButton,_("OK"),0,true,true,GUIGravityCenter);
+ obj=new GUIButton(root->width*0.3,500-44,-1,36,_("OK"),0,true,true,GUIGravityCenter);
obj->name="cfgLevelScriptingOK";
obj->eventCallback=root;
root->addChild(obj);
- obj=new GUIObject(root->width*0.7,500-44,-1,36,GUIObjectButton,_("Cancel"),0,true,true,GUIGravityCenter);
+ obj=new GUIButton(root->width*0.7,500-44,-1,36,_("Cancel"),0,true,true,GUIGravityCenter);
obj->name="cfgCancel";
obj->eventCallback=root;
root->addChild(obj);
//Add the window to the GUIObjectRoot and the objectWindows map.
GUIObjectRoot->addChild(root);
parent->objectWindows[root]=target;
//And dismiss this popup.
dismiss();
return;
}
}
};
/////////////////LevelEditorSelectionPopup/////////////////
class LevelEditorSelectionPopup{
private:
//The parent object
LevelEditor* parent;
//The position of window
SDL_Rect rect;
//GUI image
SDL_Surface* bmGUI;
//The selection
std::vector<GameObject*> selection;
//The scrollbar
GUIScrollBar* scrollBar;
//Highlighted object
GameObject* highlightedObj;
//Highlighted button index. 0=none 1=select/deselect 2=delete 3=configure
int highlightedBtn;
public:
int startRow,showedRow;
//If selection is dirty
bool dirty;
public:
SDL_Rect getRect(){
return rect;
}
int width(){
return rect.w;
}
int height(){
return rect.h;
}
void updateScrollBar(){
int m=selection.size()-showedRow;
if(m>0){
if(startRow<0) startRow=0;
else if(startRow>m) startRow=m;
if(scrollBar==NULL){
scrollBar=new GUIScrollBar(0,0,16,rect.h-16,ScrollBarVertical,startRow,0,m,1,showedRow);
}
scrollBar->visible=true;
scrollBar->maxValue=m;
scrollBar->value=startRow;
}else{
startRow=0;
if(scrollBar){
scrollBar->visible=false;
scrollBar->value=0;
}
}
}
void updateSelection(){
if(parent!=NULL){
std::vector<Block*>& v=parent->levelObjects;
for(int i=selection.size()-1;i>=0;i--){
if(find(v.begin(),v.end(),selection[i])==v.end()){
selection.erase(selection.begin()+i);
}
}
updateScrollBar();
}
}
void dismiss(){
if(parent!=NULL && parent->selectionPopup==this){
parent->selectionPopup=NULL;
}
delete this;
}
LevelEditorSelectionPopup(LevelEditor* parent, std::vector<GameObject*>& selection, int x=0, int y=0){
this->parent=parent;
this->selection=selection;
dirty=false;
scrollBar=NULL;
highlightedObj=NULL;
highlightedBtn=0;
//calc window size
startRow=0;
showedRow=selection.size();
int m=SCREEN_HEIGHT/64-1;
if(showedRow>m) showedRow=m;
rect.w=320;
rect.h=showedRow*64+16;
if(x>SCREEN_WIDTH-rect.w) x=SCREEN_WIDTH-rect.w;
else if(x<0) x=0;
if(y>SCREEN_HEIGHT-rect.h) y=SCREEN_HEIGHT-rect.h;
else if(y<0) y=0;
rect.x=x;
rect.y=y;
updateScrollBar();
//Load the gui images.
bmGUI=loadImage(getDataPath()+"gfx/gui.png");
}
~LevelEditorSelectionPopup(){
if(scrollBar)
delete scrollBar;
}
void move(int x,int y){
if(x>SCREEN_WIDTH-rect.w) x=SCREEN_WIDTH-rect.w;
else if(x<0) x=0;
if(y>SCREEN_HEIGHT-rect.h) y=SCREEN_HEIGHT-rect.h;
else if(y<0) y=0;
rect.x=x;
rect.y=y;
}
void render(){
//Check dirty
if(dirty){
updateSelection();
if(selection.empty()){
dismiss();
return;
}
dirty=false;
}
//background
drawGUIBox(rect.x,rect.y,rect.w,rect.h,screen,0xFFFFFFFFU);
//get mouse position
int x,y;
SDL_GetMouseState(&x,&y);
SDL_Rect mouse={x,y,0,0};
//the tool tip of item
SDL_Rect tooltipRect;
string tooltip;
if(scrollBar && scrollBar->visible){
startRow=scrollBar->value;
}
highlightedObj=NULL;
highlightedBtn=0;
//draw avaliable item
for(int i=0;i<showedRow;i++){
int j=startRow+i;
if(j>=(int)selection.size()) break;
SDL_Rect r={rect.x+8,rect.y+i*64+8,rect.w-16,64};
if(scrollBar && scrollBar->visible) r.w-=24;
//check highlight
if(checkCollision(mouse,r)){
highlightedObj=selection[j];
SDL_FillRect(screen,&r,0xCCCCCC);
}
int type=selection[j]->type;
//draw tile picture
ThemeBlock* obj=objThemes.getBlock(type);
if(obj){
obj->editorPicture.draw(screen,r.x+7,r.y+7);
}
//draw name
SDL_Color fg={0,0,0};
SDL_Surface* txt=TTF_RenderUTF8_Blended(fontText,_(blockNames[type]),fg);
if(txt!=NULL){
SDL_Rect r2={r.x+64,r.y+(64-txt->h)/2,0,0};
SDL_BlitSurface(txt,NULL,screen,&r2);
SDL_FreeSurface(txt);
}
if(parent!=NULL){
//draw selected
{
std::vector<GameObject*> &v=parent->selection;
bool isSelected=find(v.begin(),v.end(),selection[j])!=v.end();
SDL_Rect r1={isSelected?16:0,0,16,16};
SDL_Rect r2={r.x+r.w-72,r.y+20,24,24};
if(checkCollision(mouse,r2)){
drawGUIBox(r2.x,r2.y,r2.w,r2.h,screen,0x999999FFU);
tooltipRect=r2;
tooltip=_("Select");
highlightedBtn=1;
}
r2.x+=4;
r2.y+=4;
SDL_BlitSurface(bmGUI,&r1,screen,&r2);
}
//draw delete
{
SDL_Rect r1={112,0,16,16};
SDL_Rect r2={r.x+r.w-48,r.y+20,24,24};
if(checkCollision(mouse,r2)){
drawGUIBox(r2.x,r2.y,r2.w,r2.h,screen,0x999999FFU);
tooltipRect=r2;
tooltip=_("Delete");
highlightedBtn=2;
}
r2.x+=4;
r2.y+=4;
SDL_BlitSurface(bmGUI,&r1,screen,&r2);
}
//draw configure
{
SDL_Rect r1={112,16,16,16};
SDL_Rect r2={r.x+r.w-24,r.y+20,24,24};
if(checkCollision(mouse,r2)){
drawGUIBox(r2.x,r2.y,r2.w,r2.h,screen,0x999999FFU);
tooltipRect=r2;
tooltip=_("Configure");
highlightedBtn=3;
}
r2.x+=4;
r2.y+=4;
SDL_BlitSurface(bmGUI,&r1,screen,&r2);
}
}
}
//draw scrollbar
if(scrollBar && scrollBar->visible){
scrollBar->render(rect.x+rect.w-24,rect.y+8);
}
//draw tooltip
if(!tooltip.empty()){
//The back and foreground colors.
SDL_Color fg={0,0,0};
//Tool specific text.
SDL_Surface* tip=TTF_RenderUTF8_Blended(fontText,tooltip.c_str(),fg);
//Draw only if there's a tooltip available
if(tip!=NULL){
tooltipRect.y-=4;
tooltipRect.h+=8;
if(tooltipRect.y+tooltipRect.h+tip->h>SCREEN_HEIGHT-20)
tooltipRect.y-=tip->h;
else
tooltipRect.y+=tooltipRect.h;
if(tooltipRect.x+tip->w>SCREEN_WIDTH-20)
tooltipRect.x=SCREEN_WIDTH-20-tip->w;
//Draw borders around text
Uint32 color=0xFFFFFF00|230;
drawGUIBox(tooltipRect.x-2,tooltipRect.y-2,tip->w+4,tip->h+4,screen,color);
//Draw tooltip's text
SDL_BlitSurface(tip,NULL,screen,&tooltipRect);
SDL_FreeSurface(tip);
}
}
}
void handleEvents(){
//Check dirty
if(dirty){
updateSelection();
if(selection.empty()){
dismiss();
return;
}
dirty=false;
}
//Check scrollbar event
if(scrollBar && scrollBar->visible){
if(scrollBar->handleEvents(rect.x+rect.w-24,rect.y+8)) return;
}
if(event.type==SDL_MOUSEBUTTONDOWN){
//check mousewheel
if(event.button.button==SDL_BUTTON_WHEELUP){
startRow-=2;
updateScrollBar();
return;
}else if(event.button.button==SDL_BUTTON_WHEELDOWN){
startRow+=2;
updateScrollBar();
return;
}else if(event.button.button==SDL_BUTTON_LEFT){
SDL_Rect mouse={event.button.x,event.button.y,0,0};
//Check if close it
if(!checkCollision(mouse,rect)){
dismiss();
return;
}
//Check if item is clicked
if(highlightedObj!=NULL && highlightedBtn>0 && parent!=NULL){
std::vector<Block*>& v=parent->levelObjects;
if(find(v.begin(),v.end(),highlightedObj)!=v.end()){
switch(highlightedBtn){
case 1:
{
std::vector<GameObject*>& v2=parent->selection;
std::vector<GameObject*>::iterator it=find(v2.begin(),v2.end(),highlightedObj);
if(it==v2.end()){
v2.push_back(highlightedObj);
}else{
v2.erase(it);
}
}
break;
case 2:
parent->removeObject(highlightedObj);
break;
case 3:
if(parent->actionsPopup)
delete parent->actionsPopup;
parent->actionsPopup=new LevelEditorActionsPopup(parent,highlightedObj,mouse.x,mouse.y);
break;
}
}
}
}
}
}
};
/////////////////MovingPosition////////////////////////////
MovingPosition::MovingPosition(int x,int y,int time){
this->x=x;
this->y=y;
this->time=time;
}
MovingPosition::~MovingPosition(){}
void MovingPosition::updatePosition(int x,int y){
this->x=x;
this->y=y;
}
/////////////////LEVEL EDITOR//////////////////////////////
LevelEditor::LevelEditor():Game(){
//Get the target time and recordings.
levelTime=levels->getLevel()->targetTime;
levelRecordings=levels->getLevel()->targetRecordings;
//This will set some default settings.
reset();
//Create the GUI root.
GUIObjectRoot=new GUIObject(0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
//Load the toolbar.
toolbar=loadImage(getDataPath()+"gfx/menu/toolbar.png");
SDL_Rect tmp={(SCREEN_WIDTH-410)/2,SCREEN_HEIGHT-50,410,50};
toolbarRect=tmp;
selectionPopup=NULL;
actionsPopup=NULL;
movingSpeedWidth=-1;
//Load the selectionMark.
selectionMark=loadImage(getDataPath()+"gfx/menu/selection.png");
//Load the movingMark.
movingMark=loadImage(getDataPath()+"gfx/menu/moving.png");
//Create the semi transparent surface.
placement=SDL_CreateRGBSurface(SDL_SWSURFACE|SDL_SRCALPHA,SCREEN_WIDTH,SCREEN_HEIGHT,32,RMASK,GMASK,BMASK,0);
SDL_SetColorKey(placement,SDL_SRCCOLORKEY|SDL_RLEACCEL,SDL_MapRGB(placement->format,255,0,255));
SDL_SetAlpha(placement,SDL_SRCALPHA,125);
//Count the level editing time.
statsMgr.startLevelEdit();
}
LevelEditor::~LevelEditor(){
//Loop through the levelObjects and delete them.
for(unsigned int i=0;i<levelObjects.size();i++)
delete levelObjects[i];
levelObjects.clear();
selection.clear();
//Free the placement surface.
SDL_FreeSurface(placement);
//Delete the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Delete the popup
if(selectionPopup){
delete selectionPopup;
selectionPopup=NULL;
}
//Delete the popup
if(actionsPopup){
delete actionsPopup;
actionsPopup=NULL;
}
//Reset the camera.
camera.x=0;
camera.y=0;
//Count the level editing time.
statsMgr.endLevelEdit();
}
void LevelEditor::reset(){
//Set some default values.
playMode=false;
tool=ADD;
currentType=0;
pressedShift=false;
pressedLeftMouse=false;
dragging=false;
selectionDrag=false;
dragCenter=NULL;
cameraXvel=0;
cameraYvel=0;
linking=false;
linkingTrigger=NULL;
currentId=0;
movingBlock=NULL;
moving=false;
movingSpeed=10;
tooltip=-1;
//Set the player and shadow to their starting position.
player.setLocation(player.fx,player.fy);
shadow.setLocation(shadow.fx,shadow.fy);
selection.clear();
clipboard.clear();
triggers.clear();
movingBlocks.clear();
//Delete any gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Clear the GUIWindow object map.
objectWindows.clear();
}
void LevelEditor::loadLevelFromNode(TreeStorageNode* obj, const std::string& fileName){
//call the method of base class.
Game::loadLevelFromNode(obj,fileName);
//now do our own stuff.
string s=editorData["time"];
if(s.empty() || !(s[0]>='0' && s[0]<='9')){
levelTime=-1;
}else{
levelTime=atoi(s.c_str());
}
s=editorData["recordings"];
if(s.empty() || !(s[0]>='0' && s[0]<='9')){
levelRecordings=-1;
}else{
levelRecordings=atoi(s.c_str());
}
//NOTE: We set the camera here since we know the dimensions of the level.
if(LEVEL_WIDTH<SCREEN_WIDTH)
camera.x=-(SCREEN_WIDTH-LEVEL_WIDTH)/2;
else
camera.x=0;
if(LEVEL_HEIGHT<SCREEN_HEIGHT)
camera.y=-(SCREEN_HEIGHT-LEVEL_HEIGHT)/2;
else
camera.y=0;
//The level is loaded, so call postLoad.
postLoad();
}
void LevelEditor::saveLevel(string fileName){
//Create the output stream and check if it starts.
std::ofstream save(fileName.c_str());
if(!save) return;
//The dimensions of the level.
int maxX=0;
int maxY=0;
//The storageNode to put the level data in before writing it away.
TreeStorageNode node;
char s[64];
//The name of the level.
if(!levelName.empty()){
node.attributes["name"].push_back(levelName);
//Update the level name in the levelpack.
levels->getLevel()->name=levelName;
}
//The leveltheme.
if(!levelTheme.empty())
node.attributes["theme"].push_back(levelTheme);
//target time and recordings.
{
char c[32];
if(levelTime>=0){
sprintf(c,"%d",levelTime);
node.attributes["time"].push_back(c);
//Update the target time the levelpack.
levels->getLevel()->targetTime=levelTime;
}
if(levelRecordings>=0){
sprintf(c,"%d",levelRecordings);
node.attributes["recordings"].push_back(c);
//Update the target recordings the levelpack.
levels->getLevel()->targetRecordings=levelRecordings;
}
}
//The width of the level.
maxX=LEVEL_WIDTH;
sprintf(s,"%d",maxX);
node.attributes["size"].push_back(s);
//The height of the level.
maxY=LEVEL_HEIGHT;
sprintf(s,"%d",maxY);
node.attributes["size"].push_back(s);
//Loop through the gameObjects and save them.
for(int o=0;o<(signed)levelObjects.size();o++){
int objectType=levelObjects[o]->type;
//Check if it's a legal gameObject type.
if(objectType>=0 && objectType<TYPE_MAX){
TreeStorageNode* obj1=new TreeStorageNode;
node.subNodes.push_back(obj1);
//It's a tile so name the node tile.
obj1->name="tile";
//Write away the type of the gameObject.
obj1->value.push_back(blockName[objectType]);
//Get the box for the location of the gameObject.
SDL_Rect box=levelObjects[o]->getBox(BoxType_Base);
//Put the location in the storageNode.
sprintf(s,"%d",box.x);
obj1->value.push_back(s);
sprintf(s,"%d",box.y);
obj1->value.push_back(s);
//Loop through the editor data and save it also.
vector<pair<string,string> > obj;
levelObjects[o]->getEditorData(obj);
for(unsigned int i=0;i<obj.size();i++){
if((!obj[i].first.empty()) && (!obj[i].second.empty())){
obj1->attributes[obj[i].first].push_back(obj[i].second);
}
}
//Loop through the scripts and add them to the storage node of the game object.
map<int,string>::iterator it;
Block* object=(dynamic_cast<Block*>(levelObjects[o]));
for(it=object->scripts.begin();it!=object->scripts.end();++it){
//Make sure the script isn't an empty string.
if(it->second.empty())
continue;
TreeStorageNode* script=new TreeStorageNode;
obj1->subNodes.push_back(script);
script->name="script";
script->value.push_back(gameObjectEventTypeMap[it->first]);
script->attributes["script"].push_back(it->second);
}
}
}
//Loop through the level scripts and save them.
map<int,string>::iterator it;
for(it=scripts.begin();it!=scripts.end();++it){
//Make sure the script isn't an empty string.
if(it->second.empty())
continue;
TreeStorageNode* script=new TreeStorageNode;
node.subNodes.push_back(script);
script->name="script";
script->value.push_back(levelEventTypeMap[it->first]);
script->attributes["script"].push_back(it->second);
}
//Create a POASerializer and write away the level node.
POASerializer objSerializer;
objSerializer.writeNode(&node,save,true,true);
}
///////////////EVENT///////////////////
void LevelEditor::handleEvents(){
//Check if we need to quit, if so we enter the exit state.
if(event.type==SDL_QUIT){
setNextState(STATE_EXIT);
}
//If playing/testing we should the game handle the events.
if(playMode){
Game::handleEvents();
//Also check if we should exit the playMode.
if(inputMgr.isKeyDownEvent(INPUTMGR_ESCAPE)){
//Reset the game and disable playMode.
Game::reset(true);
playMode=false;
GUIObjectRoot->visible=true;
camera.x=cameraSave.x;
camera.y=cameraSave.y;
//NOTE: To prevent the mouse to still "be pressed" we set it to false.
pressedLeftMouse=false;
}
}else{
//Also check if we should exit the editor.
if(inputMgr.isKeyDownEvent(INPUTMGR_ESCAPE)){
//Before we quit ask a make sure question.
if(msgBox(_("Are you sure you want to quit?"),MsgBoxYesNo,_("Quit prompt"))==MsgBoxYes){
//We exit the level editor.
setNextState(STATE_LEVEL_EDIT_SELECT);
//Play the menu music again.
getMusicManager()->playMusic("menu");
//No need for handling other events, so return.
return;
}
}
//Check if we should redirect the event to the actions popup
if(actionsPopup!=NULL){
actionsPopup->handleEvents();
return;
}
//Check if we should redirect the event to selection popup
if(selectionPopup!=NULL){
if(event.type==SDL_MOUSEBUTTONDOWN
|| event.type==SDL_MOUSEBUTTONUP
|| event.type==SDL_MOUSEMOTION)
{
selectionPopup->handleEvents();
return;
}
}
//TODO: Don't handle any Events when GUIWindows process them.
{
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
SDL_Rect mouse={x,y,0,0};
for(unsigned int i=0;i<GUIObjectRoot->childControls.size();i++){
SDL_Rect box={0,0,0,0};
box.x=GUIObjectRoot->childControls[i]->left;
box.y=GUIObjectRoot->childControls[i]->top;
box.w=GUIObjectRoot->childControls[i]->width;
box.h=GUIObjectRoot->childControls[i]->height;
if(checkCollision(mouse,box))
return;
}
}
//Check if toolbar is clicked.
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_LEFT && tooltip>=0){
int t=tooltip;
if(t<NUMBER_TOOLS){
tool=(Tools)t;
}else{
//The selected button isn't a tool.
//Now check which button it is.
if(t==NUMBER_TOOLS){
playMode=true;
GUIObjectRoot->visible=false;
cameraSave.x=camera.x;
cameraSave.y=camera.y;
//Also stop linking or moving.
if(linking){
linking=false;
linkingTrigger=NULL;
}
if(moving){
//Write the path to the moving block.
std::map<std::string,std::string> editorData;
char s[64], s0[64];
sprintf(s,"%d",int(movingBlocks[movingBlock].size()));
editorData["MovingPosCount"]=s;
//Loop through the positions.
for(unsigned int o=0;o<movingBlocks[movingBlock].size();o++){
sprintf(s0+1,"%u",o);
sprintf(s,"%d",movingBlocks[movingBlock][o].x);
s0[0]='x';
editorData[s0]=s;
sprintf(s,"%d",movingBlocks[movingBlock][o].y);
s0[0]='y';
editorData[s0]=s;
sprintf(s,"%d",movingBlocks[movingBlock][o].time);
s0[0]='t';
editorData[s0]=s;
}
movingBlock->setEditorData(editorData);
moving=false;
movingBlock=NULL;
}
}
if(t==NUMBER_TOOLS+2){
//Open up level settings dialog
levelSettings();
}
if(t==NUMBER_TOOLS+4){
//Go back to the level selection screen of Level Editor
setNextState(STATE_LEVEL_EDIT_SELECT);
//Change the music back to menu music.
getMusicManager()->playMusic("menu");
}
if(t==NUMBER_TOOLS+3){
//Save current level
saveLevel(levelFile);
//And give feedback to the user.
if(levelName.empty())
msgBox(tfm::format(_("Level \"%s\" saved"),fileNameFromPath(levelFile)),MsgBoxOKOnly,_("Saved"));
else
msgBox(tfm::format(_("Level \"%s\" saved"),levelName),MsgBoxOKOnly,_("Saved"));
}
}
return;
}
//check if shift is pressed.
if(inputMgr.isKeyDownEvent(INPUTMGR_SHIFT)){
pressedShift=true;
}
if(inputMgr.isKeyUpEvent(INPUTMGR_SHIFT)){
pressedShift=false;
}
//Check if delete is pressed.
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_DELETE){
if(!selection.empty()){
//Loop through the selected game objects.
while(!selection.empty()){
//Remove the objects in the selection.
removeObject(selection[0]);
}
//And clear the selection vector.
selection.clear();
dragCenter=NULL;
selectionDrag=false;
}
}
//Check for copy (Ctrl+c) or cut (Ctrl+x).
if(event.type==SDL_KEYDOWN && (event.key.keysym.sym==SDLK_c || event.key.keysym.sym==SDLK_x) && (event.key.keysym.mod & KMOD_CTRL)){
//Clear the current clipboard.
clipboard.clear();
//Check if the selection isn't empty.
if(!selection.empty()){
//Loop through the selection to find the left-top block.
int x=selection[0]->getBox().x;
int y=selection[0]->getBox().y;
for(unsigned int o=1; o<selection.size(); o++){
if(selection[o]->getBox().x<x || selection[o]->getBox().y<y){
x=selection[o]->getBox().x;
y=selection[o]->getBox().y;
}
}
//Loop through the selection for the actual copying.
for(unsigned int o=0; o<selection.size(); o++){
//Get the editor data of the object.
vector<pair<string,string> > obj;
selection[o]->getEditorData(obj);
//Loop through the editor data and convert it.
map<string,string> objMap;
for(unsigned int i=0;i<obj.size();i++){
objMap[obj[i].first]=obj[i].second;
}
//Add some entries to the map.
char s[64];
sprintf(s,"%d",selection[o]->getBox().x-x);
objMap["x"]=s;
sprintf(s,"%d",selection[o]->getBox().y-y);
objMap["y"]=s;
sprintf(s,"%d",selection[o]->type);
objMap["type"]=s;
//Overwrite the id to prevent triggers, portals, buttons, movingblocks, etc. from malfunctioning.
//We give an empty string as id, which is invalid and thus suitable.
objMap["id"]="";
//Do the same for destination if the type is portal.
if(selection[o]->type==TYPE_PORTAL){
objMap["destination"]="";
}
//And add the map to the clipboard vector.
clipboard.push_back(objMap);
if(event.key.keysym.sym==SDLK_x){
//Cutting means deleting the game object.
removeObject(selection[o]);
o--;
}
}
//Only clear the selection when Ctrl+x;
if(event.key.keysym.sym==SDLK_x){
selection.clear();
dragCenter=NULL;
selectionDrag=false;
}
}
}
//Check for paste (Ctrl+v).
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_v && (event.key.keysym.mod & KMOD_CTRL)){
//First make sure that the clipboard isn't empty.
if(!clipboard.empty()){
//Clear the current selection.
selection.clear();
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
x+=camera.x;
y+=camera.y;
//Apply snap to grid.
if(!pressedShift){
snapToGrid(&x,&y);
}else{
x-=25;
y-=25;
}
//Integers containing the diff of the x that occurs when placing a block outside the level size on the top or left.
//We use it to compensate the corrupted x and y locations of the other clipboard blocks.
int diffX=0;
int diffY=0;
//Loop through the clipboard.
for(unsigned int o=0;o<clipboard.size();o++){
Block* block=new Block(this,0,0,atoi(clipboard[o]["type"].c_str()));
block->setBaseLocation(atoi(clipboard[o]["x"].c_str())+x+diffX,atoi(clipboard[o]["y"].c_str())+y+diffY);
block->setEditorData(clipboard[o]);
if(block->getBox().x<0){
//A block on the left side of the level, meaning we need to shift everything.
//First calc the difference.
diffX+=(0-(block->getBox().x));
}
if(block->getBox().y<0){
//A block on the left side of the level, meaning we need to shift everything.
//First calc the difference.
diffY+=(0-(block->getBox().y));
}
//And add the object using the addObject method.
addObject(block);
//Also add the block to the selection.
selection.push_back(block);
}
}
}
//Check if the return button is pressed.
//If so run the configure tool.
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_RETURN){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
SDL_Rect mouse={x+camera.x,y+camera.y,0,0};
//Loop through the selected game objects.
for(unsigned int o=0; o<levelObjects.size(); o++){
//Check for collision.
if(checkCollision(mouse,levelObjects[o]->getBox())){
//Invoke the onEnterObject.
onEnterObject(levelObjects[o]);
//Break out of the for loop.
break;
}
}
}
//Check for the arrow keys, used for moving the camera when playMode=false.
cameraXvel=0;
cameraYvel=0;
if(inputMgr.isKeyDown(INPUTMGR_RIGHT)){
if(pressedShift){
cameraXvel+=10;
}else{
cameraXvel+=5;
}
}
if(inputMgr.isKeyDown(INPUTMGR_LEFT)){
if(pressedShift){
cameraXvel-=10;
}else{
cameraXvel-=5;
}
}
if(inputMgr.isKeyDown(INPUTMGR_UP)){
if(pressedShift){
cameraYvel-=10;
}else{
cameraYvel-=5;
}
}
if(inputMgr.isKeyDown(INPUTMGR_DOWN)){
if(pressedShift){
cameraYvel+=10;
}else{
cameraYvel+=5;
}
}
//Check if the left mouse button is pressed/holded.
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_LEFT){
pressedLeftMouse=true;
}
if(event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT){
pressedLeftMouse=false;
//We also need to check if dragging is true.
if(dragging){
//Set dragging false and call the onDrop event.
dragging=false;
int x,y;
SDL_GetMouseState(&x,&y);
//We call the drop event.
onDrop(x+camera.x,y+camera.y);
}
}
//Check if the mouse is dragging.
if(pressedLeftMouse && event.type==SDL_MOUSEMOTION){
if(abs(event.motion.xrel)+abs(event.motion.yrel)>=2){
//Check if this is the start of the dragging.
if(!dragging){
//The mouse is moved enough so let's set dragging true.
dragging=true;
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//We call the dragStart event.
onDragStart(x+camera.x,y+camera.y);
}else{
//Dragging was already true meaning we call onDrag() instead of onDragStart().
onDrag(event.motion.xrel,event.motion.yrel);
}
}
}
//Update cursor.
if(dragging){
if(tool==REMOVE)
currentCursor=CURSOR_REMOVE;
else
currentCursor=CURSOR_DRAG;
}
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
SDL_Rect mouse={x,y,0,0};
//Check if we scroll up, meaning the currentType++;
if((event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_WHEELUP) || inputMgr.isKeyDownEvent(INPUTMGR_NEXT)){
switch(tool){
case ADD:
//Only change the current type when using the add tool.
currentType++;
if(currentType>=EDITOR_ORDER_MAX){
currentType=0;
}
break;
case SELECT:
//When in configure mode.
movingSpeed++;
//The movingspeed is capped at 100.
if(movingSpeed>100){
movingSpeed=100;
}
break;
default:
//When in other mode, just scrolling the map
if(pressedShift)
camera.x-=200;
else camera.y-=200;
break;
}
}
//Check if we scroll down, meaning the currentType--;
if((event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_WHEELDOWN) || inputMgr.isKeyDownEvent(INPUTMGR_PREVIOUS)){
switch(tool){
case ADD:
//Only change the current type when using the add tool.
currentType--;
if(currentType<0){
currentType=EDITOR_ORDER_MAX-1;
}
break;
case SELECT:
//When in configure mode.
movingSpeed--;
if(movingSpeed<=0){
movingSpeed=1;
}
break;
default:
//When in other mode, just scrolling the map
if(pressedShift) camera.x+=200;
else camera.y+=200;
break;
}
}
//Check if we should enter playMode.
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_p){
playMode=true;
GUIObjectRoot->visible=false;
cameraSave.x=camera.x;
cameraSave.y=camera.y;
}
//Check for tool shortcuts.
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_a){
tool=ADD;
}
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_s){
tool=SELECT;
}
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_d){
//We clear the selection since that can't be used in the deletion tool.
selection.clear();
tool=REMOVE;
}
//Check for certain events.
//First make sure the mouse isn't above the toolbar.
if(checkCollision(mouse,toolbarRect)==false){
mouse.x+=camera.x;
mouse.y+=camera.y;
//Boolean if there's a click event fired.
bool clickEvent=false;
//Check if a mouse button is pressed.
if(event.type==SDL_MOUSEBUTTONDOWN){
std::vector<GameObject*> clickObjects;
//Loop through the objects to check collision.
for(unsigned int o=0; o<levelObjects.size(); o++){
if(checkCollision(levelObjects[o]->getBox(),mouse)==true){
clickObjects.push_back(levelObjects[o]);
}
}
//Check if there are multiple objects above eachother or just one.
if(clickObjects.size()==1){
//We have collision meaning that the mouse is above an object.
std::vector<GameObject*>::iterator it;
it=find(selection.begin(),selection.end(),clickObjects[0]);
//Set event true since there's a click event.
clickEvent=true;
//Check if the clicked object is in the selection or not.
bool isSelected=(it!=selection.end());
if(event.button.button==SDL_BUTTON_LEFT){
onClickObject(clickObjects[0],isSelected);
}else if(event.button.button==SDL_BUTTON_RIGHT){
onRightClickObject(clickObjects[0],isSelected);
}
}else if(clickObjects.size()>1){
//There are more than one object under the mouse
std::vector<GameObject*>::iterator it;
it=find(selection.begin(),selection.end(),clickObjects[0]);
//Set event true since there's a click event.
clickEvent=true;
//Check if the clicked object is in the selection or not.
bool isSelected=(it!=selection.end());
//Only show the selection popup when right clicking.
if(event.button.button==SDL_BUTTON_LEFT){
onClickObject(clickObjects[0],isSelected);
}else if(event.button.button==SDL_BUTTON_RIGHT){
//Remove the selection popup if there's one.
if(selectionPopup!=NULL)
delete selectionPopup;
//Get the mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
selectionPopup=new LevelEditorSelectionPopup(this,clickObjects,x,y);
}
}
}
//If event is false then we clicked on void.
if(!clickEvent){
if(event.type==SDL_MOUSEBUTTONDOWN){
if(event.button.button==SDL_BUTTON_LEFT){
//Left mouse button on void.
onClickVoid(mouse.x,mouse.y);
}else if(event.button.button==SDL_BUTTON_RIGHT /*&& tool==SELECT*/){
//Stop linking.
if(linking){
linking=false;
linkingTrigger=NULL;
//NOTE: We shouldn't be able to be linking AND moving so return to prevent actions popup.
return;
}
//Write the path to the moving block.
if(moving){
std::map<std::string,std::string> editorData;
char s[64], s0[64];
sprintf(s,"%d",int(movingBlocks[movingBlock].size()));
editorData["MovingPosCount"]=s;
//Loop through the positions.
for(unsigned int o=0;o<movingBlocks[movingBlock].size();o++){
sprintf(s0+1,"%u",o);
sprintf(s,"%d",movingBlocks[movingBlock][o].x);
s0[0]='x';
editorData[s0]=s;
sprintf(s,"%d",movingBlocks[movingBlock][o].y);
s0[0]='y';
editorData[s0]=s;
sprintf(s,"%d",movingBlocks[movingBlock][o].time);
s0[0]='t';
editorData[s0]=s;
}
movingBlock->setEditorData(editorData);
//Stop moving.
moving=false;
movingBlock=NULL;
return;
}
//No return so far so call onRightClickVoid.
onRightClickVoid(mouse.x,mouse.y);
}
}
}
}
//Check for backspace when moving to remove a movingposition.
if(moving && event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_BACKSPACE){
if(movingBlocks[movingBlock].size()>0){
movingBlocks[movingBlock].pop_back();
}
}
//Check for the tab key, level settings.
if(inputMgr.isKeyDownEvent(INPUTMGR_TAB)){
//Show the levelSettings.
levelSettings();
}
//NOTE: Do we even need Ctrl+n?
//Check if we should a new level. (Ctrl+n)
//if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_n && (event.key.keysym.mod & KMOD_CTRL)){
// reset();
// //NOTE: We don't have anything to load from so we create an empty TreeStorageNode.
// Game::loadLevelFromNode(new TreeStorageNode,"");
// //Hide selection popup (if any)
// if(selectionPopup!=NULL){
// delete selectionPopup;
// selectionPopup=NULL;
// }
//}
//Check if we should save the level (Ctrl+s).
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_s && (event.key.keysym.mod & KMOD_CTRL)){
saveLevel(levelFile);
//And give feedback to the user.
if(levelName.empty())
msgBox(tfm::format(_("Level \"%s\" saved"),fileNameFromPath(levelFile)),MsgBoxOKOnly,_("Saved"));
else
msgBox(tfm::format(_("Level \"%s\" saved"),levelName),MsgBoxOKOnly,_("Saved"));
}
}
}
void LevelEditor::levelSettings(){
//It isn't so open a popup asking for a name.
GUIWindow* root=new GUIWindow((SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-300)/2,600,300,true,true,_("Level settings"));
root->name="lvlSettingsWindow";
root->eventCallback=this;
GUIObject* obj;
//Create the two textboxes with a label.
- obj=new GUIObject(40,50,240,36,GUIObjectLabel,_("Name:"));
+ obj=new GUILabel(40,50,240,36,_("Name:"));
root->addChild(obj);
- obj=new GUIObject(140,50,410,36,GUIObjectTextBox,levelName.c_str());
+ obj=new GUITextBox(140,50,410,36,levelName.c_str());
obj->name="name";
root->addChild(obj);
- obj=new GUIObject(40,100,240,36,GUIObjectLabel,_("Theme:"));
+ obj=new GUILabel(40,100,240,36,_("Theme:"));
root->addChild(obj);
- obj=new GUIObject(140,100,410,36,GUIObjectTextBox,levelTheme.c_str());
+ obj=new GUITextBox(140,100,410,36,levelTheme.c_str());
obj->name="theme";
root->addChild(obj);
//target time and recordings.
{
- obj=new GUIObject(40,150,240,36,GUIObjectLabel,_("Target time (s):"));
+ obj=new GUILabel(40,150,240,36,_("Target time (s):"));
root->addChild(obj);
GUISpinBox* obj2=new GUISpinBox(290,150,260,36);
obj2->name="time";
ostringstream ss;
ss << levelTime/40.0f;
obj2->caption=ss.str();
obj2->update();
obj2->limitMin=0.0f;
obj2->change=0.1f;
root->addChild(obj2);
- obj=new GUIObject(40,200,240,36,GUIObjectLabel,_("Target recordings:"));
+ obj=new GUILabel(40,200,240,36,_("Target recordings:"));
root->addChild(obj);
obj2=new GUISpinBox(290,200,260,36);
ostringstream ss2;
ss2 << levelRecordings;
obj2->caption=ss2.str();
obj2->limitMin=0.0f;
obj2->format="%1.0f";
obj2->name="recordings";
obj2->update();
root->addChild(obj2);
}
//Ok and cancel buttons.
- obj=new GUIObject(root->width*0.3,300-44,-1,36,GUIObjectButton,_("OK"),0,true,true,GUIGravityCenter);
+ obj=new GUIButton(root->width*0.3,300-44,-1,36,_("OK"),0,true,true,GUIGravityCenter);
obj->name="lvlSettingsOK";
obj->eventCallback=root;
root->addChild(obj);
- obj=new GUIObject(root->width*0.7,300-44,-1,36,GUIObjectButton,_("Cancel"),0,true,true,GUIGravityCenter);
+ obj=new GUIButton(root->width*0.7,300-44,-1,36,_("Cancel"),0,true,true,GUIGravityCenter);
obj->name="lvlSettingsCancel";
obj->eventCallback=root;
root->addChild(obj);
GUIObjectRoot->addChild(root);
}
void LevelEditor::postLoad(){
//We need to find the triggers.
for(unsigned int o=0;o<levelObjects.size();o++){
//Check for the highest id.
unsigned int id=atoi(levelObjects[o]->getEditorProperty("id").c_str());
if(id>=currentId)
currentId=id+1;
switch(levelObjects[o]->type){
case TYPE_BUTTON:
case TYPE_SWITCH:
{
//Add the object to the triggers vector.
vector<GameObject*> linked;
triggers[levelObjects[o]]=linked;
//Now loop through the levelObjects in search for objects with the same id.
for(unsigned int oo=0;oo<levelObjects.size();oo++){
//Check if it isn't the same object but has the same id.
if(o!=oo && (dynamic_cast<Block*>(levelObjects[o]))->id==(dynamic_cast<Block*>(levelObjects[oo]))->id){
//Add the object to the link vector of the trigger.
triggers[levelObjects[o]].push_back(levelObjects[oo]);
}
}
break;
}
case TYPE_PORTAL:
{
//Add the object to the triggers vector.
vector<GameObject*> linked;
triggers[levelObjects[o]]=linked;
//If the destination is empty we return.
if((dynamic_cast<Block*>(levelObjects[o]))->destination.empty()){
break;
}
//Now loop through the levelObjects in search for objects with the same id as destination.
for(unsigned int oo=0;oo<levelObjects.size();oo++){
//Check if it isn't the same object but has the same id.
if(o!=oo && (dynamic_cast<Block*>(levelObjects[o]))->destination==(dynamic_cast<Block*>(levelObjects[oo]))->id){
//Add the object to the link vector of the trigger.
triggers[levelObjects[o]].push_back(levelObjects[oo]);
}
}
break;
}
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
{
//Get the editor data.
vector<pair<string,string> > objMap;
levelObjects[o]->getEditorData(objMap);
//Add the object to the movingBlocks vector.
vector<MovingPosition> positions;
movingBlocks[levelObjects[o]]=positions;
//Get the number of entries of the editor data.
int m=objMap.size();
//Check if the editor data isn't empty.
if(m>0){
//Integer containing the positions.
int pos;
int currentPos=0;
//Get the number of movingpositions.
pos=atoi(objMap[1].second.c_str());
while(currentPos<pos){
int x=atoi(objMap[currentPos*3+4].second.c_str());
int y=atoi(objMap[currentPos*3+5].second.c_str());
int t=atoi(objMap[currentPos*3+6].second.c_str());
//Create a new movingPosition.
MovingPosition position(x,y,t);
movingBlocks[levelObjects[o]].push_back(position);
//Increase currentPos by one.
currentPos++;
}
}
break;
}
default:
break;
}
}
}
void LevelEditor::snapToGrid(int* x,int* y){
//Check if the x location is negative.
if(*x<0){
*x=-((abs(*x-50)/50)*50);
}else{
*x=(*x/50)*50;
}
//Now the y location.
if(*y<0){
*y=-((abs(*y-50)/50)*50);
}else{
*y=(*y/50)*50;
}
}
void LevelEditor::onClickObject(GameObject* obj,bool selected){
switch(tool){
case SELECT:
{
//Check if we are linking.
if(linking){
//Check if the obj is valid to link to.
switch(obj->type){
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
{
//It's only valid when not linking a portal.
if(linkingTrigger->type==TYPE_PORTAL){
//You can't link a portal to moving blocks, etc.
//Stop linking and return.
linkingTrigger=NULL;
linking=false;
return;
}
break;
}
case TYPE_PORTAL:
{
//Make sure that the linkingTrigger is also a portal.
if(linkingTrigger->type!=TYPE_PORTAL){
//The linkingTrigger isn't a portal so stop linking and return.
linkingTrigger=NULL;
linking=false;
return;
}
break;
}
default:
//It isn't valid so stop linking and return.
linkingTrigger=NULL;
linking=false;
return;
break;
}
//Check if the linkingTrigger can handle multiple or only one link.
switch(linkingTrigger->type){
case TYPE_PORTAL:
{
//Portals can only link to one so remove all existing links.
triggers[linkingTrigger].clear();
triggers[linkingTrigger].push_back(obj);
break;
}
default:
{
//The most can handle multiple links.
triggers[linkingTrigger].push_back(obj);
break;
}
}
//Check if it's a portal.
if(linkingTrigger->type==TYPE_PORTAL){
//Portals need to get the id of the other instead of give it's own id.
char s[64];
sprintf(s,"%d",atoi(obj->getEditorProperty("id").c_str()));
linkingTrigger->setEditorProperty("destination",s);
}else{
//Give the object the same id as the trigger.
char s[64];
sprintf(s,"%d",atoi(linkingTrigger->getEditorProperty("id").c_str()));
obj->setEditorProperty("id",s);
}
//We return to prevent configuring stuff like conveyor belts, etc...
linking=false;
linkingTrigger=NULL;
return;
}
//If we're moving add a movingposition.
if(moving){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
x+=camera.x;
y+=camera.y;
//Apply snap to grid.
if(!pressedShift){
snapToGrid(&x,&y);
}else{
x-=25;
y-=25;
}
x-=movingBlock->getBox().x;
y-=movingBlock->getBox().y;
//Calculate the length.
//First get the delta x and y.
int dx,dy;
if(movingBlocks[movingBlock].empty()){
dx=x;
dy=y;
}else{
dx=x-movingBlocks[movingBlock].back().x;
dy=y-movingBlocks[movingBlock].back().y;
}
double length=sqrt(double(dx*dx+dy*dy));
movingBlocks[movingBlock].push_back(MovingPosition(x,y,(int)(length*(10/(double)movingSpeed))));
return;
}
}
case ADD:
{
//Check if object is already selected.
if(!selected){
//First check if shift is pressed or not.
if(!pressedShift){
//Clear the selection.
selection.clear();
}
//Add the object to the selection.
selection.push_back(obj);
}
break;
}
case REMOVE:
{
//Remove the object.
removeObject(obj);
break;
}
default:
break;
}
}
void LevelEditor::onRightClickObject(GameObject* obj,bool selected){
//Create an actions popup for the game object.
if(actionsPopup==NULL){
//Get the mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
actionsPopup=new LevelEditorActionsPopup(this,obj,x,y);
return;
}
}
void LevelEditor::onClickVoid(int x,int y){
switch(tool){
case ADD:
{
//We need to clear the selection.
selection.clear();
//Now place an object.
//Apply snap to grid.
if(!pressedShift){
snapToGrid(&x,&y);
}else{
x-=25;
y-=25;
}
addObject(new Block(this,x,y,editorTileOrder[currentType]));
break;
}
case SELECT:
{
//We need to clear the selection.
selection.clear();
//If we're linking we should stop, user abort.
if(linking){
linking=false;
linkingTrigger=NULL;
//And return.
return;
}
//If we're moving we should add a point.
if(moving){
//Apply snap to grid.
if(!pressedShift){
snapToGrid(&x,&y);
}else{
x-=25;
y-=25;
}
x-=movingBlock->getBox().x;
y-=movingBlock->getBox().y;
//Calculate the length.
//First get the delta x and y.
int dx,dy;
if(movingBlocks[movingBlock].empty()){
dx=x;
dy=y;
}else{
dx=x-movingBlocks[movingBlock].back().x;
dy=y-movingBlocks[movingBlock].back().y;
}
double length=sqrt(double(dx*dx+dy*dy));
movingBlocks[movingBlock].push_back(MovingPosition(x,y,(int)(length*(10/(double)movingSpeed))));
//And return.
return;
}
break;
}
default:
break;
}
}
void LevelEditor::onRightClickVoid(int x,int y){
//Create an actions popup for the game object.
if(actionsPopup==NULL){
//Get the mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
actionsPopup=new LevelEditorActionsPopup(this,NULL,x,y);
return;
}
}
void LevelEditor::onDragStart(int x,int y){
switch(tool){
case SELECT:
case ADD:
{
//We can drag the selection so check if the selection isn't empty.
if(!selection.empty()){
//The selection isn't empty so search the dragCenter.
//Create a mouse rectangle.
SDL_Rect mouse={x,y,0,0};
//Loop through the objects to check collision.
for(unsigned int o=0; o<selection.size(); o++){
if(checkCollision(selection[o]->getBox(),mouse)==true){
//We have collision so set the dragCenter.
dragCenter=selection[o];
selectionDrag=true;
}
}
}
break;
}
default:
break;
}
}
void LevelEditor::onDrag(int dx,int dy){
switch(tool){
case REMOVE:
{
//No matter what we delete the item the mouse is above.
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
SDL_Rect mouse={x+camera.x,y+camera.y,0,0};
currentCursor=CURSOR_REMOVE;
//Loop through the objects to check collision.
for(unsigned int o=0; o<levelObjects.size(); o++){
if(checkCollision(levelObjects[o]->getBox(),mouse)==true){
//Remove the object.
removeObject(levelObjects[o]);
}
}
break;
}
default:
break;
}
}
void LevelEditor::onDrop(int x,int y){
switch(tool){
case SELECT:
case ADD:
{
//Check if the drag center isn't null.
if(dragCenter==NULL)
return;
//The location of the dragCenter.
SDL_Rect r=dragCenter->getBox();
//Apply snap to grid.
if(!pressedShift){
snapToGrid(&x,&y);
}else{
x-=25;
y-=25;
}
//Loop through the selection.
for(unsigned int o=0; o<selection.size(); o++){
SDL_Rect r1=selection[o]->getBox();
//We need to place the object at his drop place.
moveObject(selection[o],(r1.x-r.x)+x,(r1.y-r.y)+y);
}
//Make sure the dragCenter is null and set selectionDrag false.
dragCenter=NULL;
selectionDrag=false;
break;
}
default:
break;
}
}
void LevelEditor::onCameraMove(int dx,int dy){
switch(tool){
case REMOVE:
{
//Only delete when the left mouse button is pressed.
if(pressedLeftMouse){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
SDL_Rect mouse={x+camera.x,y+camera.y,0,0};
//Loop through the objects to check collision.
for(unsigned int o=0; o<levelObjects.size(); o++){
if(checkCollision(levelObjects[o]->getBox(),mouse)==true){
//Remove the object.
removeObject(levelObjects[o]);
}
}
}
break;
}
default:
break;
}
}
void LevelEditor::onEnterObject(GameObject* obj){
//NOTE: Function isn't used anymore.
}
void LevelEditor::addObject(GameObject* obj){
//Increase totalCollectables everytime we add a new collectable
if(obj->type==TYPE_COLLECTABLE) {
totalCollectables++;
}
//If it's a player or shadow start then we need to remove the previous one.
if(obj->type==TYPE_START_PLAYER || obj->type==TYPE_START_SHADOW){
//Loop through the levelObjects.
for(unsigned int o=0; o<levelObjects.size(); o++){
//Check if the type is the same.
if(levelObjects[o]->type==obj->type){
removeObject(levelObjects[o]);
}
}
}
//Add it to the levelObjects.
Block* block=dynamic_cast<Block*>(obj);
//Make sure it's a block.
if(!block)
return;
levelObjects.push_back(block);
//Check if the object is inside the level dimensions, etc.
//Just call moveObject() to perform this.
moveObject(obj,obj->getBox().x,obj->getBox().y);
//GameObject type specific stuff.
switch(obj->type){
case TYPE_BUTTON:
case TYPE_SWITCH:
case TYPE_PORTAL:
{
//Add the object to the triggers.
vector<GameObject*> linked;
triggers[block]=linked;
//Give it it's own id.
char s[64];
sprintf(s,"%u",currentId);
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;
movingBlocks[block]=positions;
//Get the editor data.
vector<pair<string,string> > objMap;
block->getEditorData(objMap);
//Get the number of entries of the editor data.
int m=objMap.size();
//Check if the editor data isn't empty.
if(m>0){
//Integer containing the positions.
int pos=0;
int currentPos=0;
//Get the number of movingpositions.
pos=atoi(objMap[1].second.c_str());
while(currentPos<pos){
int x=atoi(objMap[currentPos*3+4].second.c_str());
int y=atoi(objMap[currentPos*3+5].second.c_str());
int t=atoi(objMap[currentPos*3+6].second.c_str());
//Create a new movingPosition.
MovingPosition position(x,y,t);
movingBlocks[block].push_back(position);
//Increase currentPos by one.
currentPos++;
}
}
//Give it it's own id.
std::map<std::string,std::string> editorData;
char s[64];
sprintf(s,"%u",currentId);
currentId++;
editorData["id"]=s;
block->setEditorData(editorData);
break;
}
default:
break;
}
}
void LevelEditor::moveObject(GameObject* obj,int x,int y){
//Set the obj at it's new position.
obj->setBaseLocation(x,y);
//Check if the object is inside the level dimensions.
//If not let the level grow.
if(obj->getBox().x+50>LEVEL_WIDTH){
LEVEL_WIDTH=obj->getBox().x+50;
}
if(obj->getBox().y+50>LEVEL_HEIGHT){
LEVEL_HEIGHT=obj->getBox().y+50;
}
if(obj->getBox().x<0 || obj->getBox().y<0){
//A block on the left (or top) side of the level, meaning we need to shift everything.
//First calc the difference.
int diffx=(0-(obj->getBox().x));
int diffy=(0-(obj->getBox().y));
if(diffx<0) diffx=0;
if(diffy<0) diffy=0;
//Change the level size first.
//The level grows with the difference, 0-(x+50).
LEVEL_WIDTH+=diffx;
LEVEL_HEIGHT+=diffy;
//cout<<"x:"<<diffx<<",y:"<<diffy<<endl; //debug
camera.x+=diffx;
camera.y+=diffy;
//Set the position of player and shadow
//(although it's unnecessary if there is player and shadow start)
player.setLocation(player.getBox().x+diffx,player.getBox().y+diffy);
shadow.setLocation(shadow.getBox().x+diffx,shadow.getBox().y+diffy);
for(unsigned int o=0; o<levelObjects.size(); o++){
//FIXME: shouldn't recuesive call me (to prevent stack overflow bugs)
moveObject(levelObjects[o],levelObjects[o]->getBox().x+diffx,levelObjects[o]->getBox().y+diffy);
}
}
//If the object is a player or shadow start then change the start position of the player or shadow.
if(obj->type==TYPE_START_PLAYER){
//Center the player horizontally.
player.fx=obj->getBox().x+(50-23)/2;
player.fy=obj->getBox().y;
//Now reset the player to get him to it's new start position.
player.reset(true);
}
if(obj->type==TYPE_START_SHADOW){
//Center the shadow horizontally.
shadow.fx=obj->getBox().x+(50-23)/2;
shadow.fy=obj->getBox().y;
//Now reset the shadow to get him to it's new start position.
shadow.reset(true);
}
}
void LevelEditor::removeObject(GameObject* obj){
std::vector<GameObject*>::iterator it;
std::map<Block*,vector<GameObject*> >::iterator mapIt;
//Increase totalCollectables everytime we add a new collectable
if(obj->type==TYPE_COLLECTABLE){
totalCollectables--;
}
//Check if the object is in the selection.
it=find(selection.begin(),selection.end(),obj);
if(it!=selection.end()){
//It is so we delete it.
selection.erase(it);
}
//Check if the object is in the triggers.
mapIt=triggers.find(dynamic_cast<Block*>(obj));
if(mapIt!=triggers.end()){
//It is so we remove it.
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=triggers.begin();mapIt!=triggers.end();++mapIt){
//Now loop the target vector.
for(unsigned int o=0;o<(*mapIt).second.size();o++){
//Check if the obj is in the target vector.
if((*mapIt).second[o]==obj){
(*mapIt).second.erase(find((*mapIt).second.begin(),(*mapIt).second.end(),obj));
o--;
}
}
}
}
//Check if the object is in the movingObjects.
std::map<Block*,vector<MovingPosition> >::iterator movIt;
movIt=movingBlocks.find(dynamic_cast<Block*>(obj));
if(movIt!=movingBlocks.end()){
//It is so we remove it.
movingBlocks.erase(movIt);
}
//Check if the block isn't being configured with a window one way or another.
std::map<GUIObject*,GameObject*>::iterator confIt;
for(confIt=objectWindows.begin();confIt!=objectWindows.end();++confIt){
if((*confIt).second==obj){
destroyWindow((*confIt).first);
}
}
//Now we remove the object from the levelObjects.
{
std::vector<Block*>::iterator it;
it=find(levelObjects.begin(),levelObjects.end(),dynamic_cast<Block*>(obj));
if(it!=levelObjects.end()){
levelObjects.erase(it);
}
}
delete obj;
obj=NULL;
//Set dirty of selection popup
if(selectionPopup!=NULL) selectionPopup->dirty=true;
}
void LevelEditor::GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType){
//Check if one of the windows is closed.
if(eventType==GUIEventClick && (name=="lvlSettingsWindow" || name=="notificationBlockWindow" || name=="conveyorBlockWindow" || name=="scriptingWindow" || name=="levelScriptingWindow")){
destroyWindow(obj);
return;
}
//TODO: Add resize code for each GUIWindow.
if(name=="lvlSettingsWindow"){
return;
}
if(name=="notificationBlockWindow"){
return;
}
if(name=="conveyorBlockWindow"){
return;
}
if(name=="scriptingWindow"){
return;
}
if(name=="levelScriptingWindow"){
return;
}
//Check for GUI events.
//Notification block configure events.
if(name=="cfgNotificationBlockOK"){
//Get the configuredObject.
GameObject* configuredObject=objectWindows[obj];
if(configuredObject){
//Get the message textbox from the GUIWindow.
GUITextArea* message=(GUITextArea*)obj->getChild("message");
if(message){
//Set the message of the notification block.
configuredObject->setEditorProperty("message",message->getString());
}
}
}
//Conveyor belt block configure events.
if(name=="cfgConveyorBlockOK"){
//Get the configuredObject.
GameObject* configuredObject=objectWindows[obj];
if(configuredObject){
//Get the speed textbox from the GUIWindow.
GUISpinBox* speed=(GUISpinBox*)obj->getChild("speed");
if(speed){
//Set the speed of the conveyor belt.
configuredObject->setEditorProperty("speed",speed->caption);
}
}
}
//LevelSetting events.
if(name=="lvlSettingsOK"){
GUIObject* object=obj->getChild("name");
if(object)
levelName=object->caption;
object=obj->getChild("theme");
if(object)
levelTheme=object->caption;
//target time and recordings.
GUISpinBox* object2=(GUISpinBox*)obj->getChild("time");
if(object2){
float number=atof(object2->caption.c_str());
if(number<=0){
levelTime=-1;
}else{
levelTime=int(number*40.0+0.5);
}
}
object2=(GUISpinBox*)obj->getChild("recordings");
if(object){
float number=atof(object2->caption.c_str());
if(number<=0){
levelRecordings=-1;
}else{
levelRecordings=int(number);
}
}
}
//Level scripting window events.
if(name=="cfgLevelScriptingEventType"){
//Get the script textbox from the GUIWindow.
GUISingleLineListBox* list=(GUISingleLineListBox*)obj->getChild("cfgLevelScriptingEventType");
if(list){
//Loop through the scripts.
for(unsigned int i=0;i<list->item.size();i++){
GUIObject* script=obj->getChild(list->item[i].first);
if(script){
script->visible=(script->name==list->item[list->value].first);
script->enabled=(script->name==list->item[list->value].first);
}
}
}
return;
}
if(name=="cfgLevelScriptingOK"){
//Get the script textbox from the GUIWindow.
GUISingleLineListBox* list=(GUISingleLineListBox*)obj->getChild("cfgLevelScriptingEventType");
if(list){
//Loop through the scripts.
for(unsigned int i=0;i<list->item.size();i++){
//Get the GUITextArea.
GUITextArea* script=dynamic_cast<GUITextArea*>(obj->getChild(list->item[i].first));
if(script){
//Set the script for the target block.
string str=script->getString();
if(str.empty())
scripts.erase(levelEventNameMap[script->name]);
else
scripts[levelEventNameMap[script->name]]=str;
}
}
}
}
//Scripting window events.
if(name=="cfgScriptingEventType"){
//TODO: Save any unsaved scripts? (Or keep track of all scripts and save upon cfgScriptingOK?)
//Get the configuredObject.
Block* configuredObject=dynamic_cast<Block*>(objectWindows[obj]);
if(configuredObject){
//Get the script textbox from the GUIWindow.
GUISingleLineListBox* list=(GUISingleLineListBox*)obj->getChild("cfgScriptingEventType");
if(list){
//Loop through the scripts.
for(unsigned int i=0;i<list->item.size();i++){
GUIObject* script=obj->getChild(list->item[i].first);
if(script){
script->visible=script->enabled=(script->name==list->item[list->value].first);
}
}
}
}
return;
}
if(name=="cfgScriptingOK"){
//Get the configuredObject.
GameObject* configuredObject=objectWindows[obj];
if(configuredObject){
//Get the script textbox from the GUIWindow.
GUISingleLineListBox* list=(GUISingleLineListBox*)obj->getChild("cfgScriptingEventType");
GUIObject* id=obj->getChild("id");
Block* block=dynamic_cast<Block*>(configuredObject);
if(block){
if(list){
//Loop through the scripts.
for(unsigned int i=0;i<list->item.size();i++){
//Get the GUITextArea.
GUITextArea* script=dynamic_cast<GUITextArea*>(obj->getChild(list->item[i].first));
if(script){
//Set the script for the target block.
string str=script->getString();
if(str.empty())
block->scripts.erase(gameObjectEventNameMap[script->name]);
else
block->scripts[gameObjectEventNameMap[script->name]]=str;
}
}
}
if(id){
//Set the new id for the target block.
//TODO: Check for trigger links etc...
(dynamic_cast<Block*>(configuredObject))->id=id->caption;
}
}
}
}
//NOTE: We assume every event came from a window so remove it.
destroyWindow(obj);
}
void LevelEditor::destroyWindow(GUIObject* window){
//Make sure the given pointer isn't null.
if(!window)
return;
//Remove the window from the GUIObject root.
if(GUIObjectRoot){
vector<GUIObject*>::iterator it;
it=find(GUIObjectRoot->childControls.begin(),GUIObjectRoot->childControls.end(),window);
if(it!=GUIObjectRoot->childControls.end()){
GUIObjectRoot->childControls.erase(it);
}
}
//Also remove the window from the objectWindows map.
map<GUIObject*,GameObject*>::iterator it;
it=objectWindows.find(window);
if(it!=objectWindows.end()){
objectWindows.erase(it);
}
//And delete the GUIWindow.
delete window;
}
////////////////LOGIC////////////////////
void LevelEditor::logic(){
if(playMode){
//PlayMode so let the game do it's logic.
Game::logic();
}else{
//In case of a selection or actions popup prevent the camera from moving.
if(selectionPopup || actionsPopup)
return;
//Move the camera.
if(cameraXvel!=0 || cameraYvel!=0){
camera.x+=cameraXvel;
camera.y+=cameraYvel;
//Call the onCameraMove event.
onCameraMove(cameraXvel,cameraYvel);
}
//Move the camera with the mouse.
//Get the mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
SDL_Rect mouse={x,y,0,0};
{
//Check if the mouse isn't above a GUIObject (window).
bool inside=false;
for(unsigned int i=0;i<GUIObjectRoot->childControls.size();i++){
SDL_Rect box={0,0,0,0};
box.x=GUIObjectRoot->childControls[i]->left;
box.y=GUIObjectRoot->childControls[i]->top;
box.w=GUIObjectRoot->childControls[i]->width;
box.h=GUIObjectRoot->childControls[i]->height;
if(checkCollision(mouse,box))
inside=true;
}
if(!inside){
SDL_Rect r[3]={toolbarRect};
int m=1;
//TODO: Also call onCameraMove when moving using the mouse.
setCamera(r,m);
}
}
//It isn't playMode so the mouse should be checked.
tooltip=-1;
//We loop through the number of tools + the number of buttons.
for(int t=0; t<NUMBER_TOOLS+6; t++){
SDL_Rect toolRect={(SCREEN_WIDTH-410)/2+(t*40)+((t+1)*10),SCREEN_HEIGHT-45,40,40};
//Check for collision.
if(checkCollision(mouse,toolRect)==true){
//Set the tooltip tool.
tooltip=t;
}
}
}
}
/////////////////RENDER//////////////////////
void LevelEditor::render(){
//Always let the game render the game.
Game::render();
//Only render extra stuff like the toolbar, selection, etc.. when not in playMode.
if(!playMode){
//Render the selectionmarks.
//TODO: Check if block is in sight.
for(unsigned int o=0; o<selection.size(); o++){
//Get the location to draw.
SDL_Rect r=selection[o]->getBox();
r.x-=camera.x;
r.y-=camera.y;
drawGUIBox(r.x,r.y,50,50,screen,0xFFFFFF33);
//Draw the selectionMarks.
applySurface(r.x,r.y,selectionMark,screen,NULL);
applySurface(r.x+r.w-5,r.y,selectionMark,screen,NULL);
applySurface(r.x,r.y+r.h-5,selectionMark,screen,NULL);
applySurface(r.x+r.w-5,r.y+r.h-5,selectionMark,screen,NULL);
}
//Clear the placement surface.
SDL_FillRect(placement,NULL,0x00FF00FF);
Uint32 color=SDL_MapRGB(placement->format,themeTextColor.r,themeTextColor.g,themeTextColor.b);
//Draw the dark areas marking the outside of the level.
SDL_Rect r;
if(camera.x<0){
//Draw left side.
r.x=0;
r.y=0;
r.w=0-camera.x;
r.h=SCREEN_HEIGHT;
SDL_FillRect(placement,&r,color);
}
if(camera.x>LEVEL_WIDTH-SCREEN_WIDTH){
//Draw right side.
r.x=LEVEL_WIDTH-camera.x;
r.y=0;
r.w=SCREEN_WIDTH-(LEVEL_WIDTH-camera.x);
r.h=SCREEN_HEIGHT;
SDL_FillRect(placement,&r,color);
}
if(camera.y<0){
//Draw the top.
r.x=0;
r.y=0;
r.w=SCREEN_WIDTH;
r.h=0-camera.y;
SDL_FillRect(placement,&r,color);
}
if(camera.y>LEVEL_HEIGHT-SCREEN_HEIGHT){
//Draw the bottom.
r.x=0;
r.y=LEVEL_HEIGHT-camera.y;
r.w=SCREEN_WIDTH;
r.h=SCREEN_HEIGHT-(LEVEL_HEIGHT-camera.y);
SDL_FillRect(placement,&r,color);
}
//Check if we should draw on the placement surface.
showConfigure();
if(selectionDrag){
showSelectionDrag();
}else{
if(tool==ADD){
showCurrentObject();
}
}
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
SDL_Rect mouse={x+camera.x,y+camera.y,0,0};
//Find a block where the mouse is hovering on.
for(unsigned int o=0; o<levelObjects.size(); o++){
SDL_Rect rect=levelObjects[o]->getBox();
if(checkCollision(rect,mouse)==true){
if(tool==REMOVE){
drawGUIBox(rect.x-camera.x,rect.y-camera.y,50,50,screen,0xFF000055);
currentCursor=CURSOR_REMOVE;
}else{
drawGUIBox(rect.x-camera.x,rect.y-camera.y,50,50,screen,0xFFFFFF33);
}
}
}
//Draw the level borders.
drawRect(-camera.x,-camera.y,LEVEL_WIDTH,LEVEL_HEIGHT,screen);
//Render the placement surface.
applySurface(0,0,placement,screen,NULL);
//Render the hud layer.
renderHUD();
//Render selection popup (if any).
if(selectionPopup!=NULL){
if(linking || moving){
//If we switch to linking mode then delete it
//FIXME: Logic in the render method.
delete selectionPopup;
selectionPopup=NULL;
}else{
selectionPopup->render();
}
}
//Render actions popup (if any).
if(actionsPopup!=NULL){
actionsPopup->render();
}
}
}
void LevelEditor::renderHUD(){
//If moving show the moving speed in the top right corner.
if(moving){
//Calculate width of text "Movespeed: 100" to keep the same position with every value
if (movingSpeedWidth==-1){
int w;
TTF_SizeUTF8(fontText,tfm::format(_("Movespeed: %s"),100).c_str(),&w,NULL);
movingSpeedWidth=w+4;
}
//Now render the text.
SDL_Color black={0,0,0,0};
SDL_Surface* bm=TTF_RenderUTF8_Blended(fontText,tfm::format(_("Movespeed: %s"),movingSpeed).c_str(),black);
//Draw the text in box and free the surface.
drawGUIBox(SCREEN_WIDTH-movingSpeedWidth-2,-2,movingSpeedWidth+8,bm->h+6,screen,0xDDDDDDDD);
applySurface(SCREEN_WIDTH-movingSpeedWidth,2,bm,screen,NULL);
SDL_FreeSurface(bm);
}
//On top of all render the toolbar.
drawGUIBox(toolbarRect.x,toolbarRect.y,8*50+10,52,screen,0xEDEDEDFF);
//Draw the first four options.
SDL_Rect r={0,0,200,50};
applySurface(toolbarRect.x+5,toolbarRect.y,toolbar,screen,&r);
//And the last three.
r.x=200;
r.w=150;
applySurface(toolbarRect.x+255,toolbarRect.y,toolbar,screen,&r);
//Now render a tooltip.
if(tooltip>=0){
//The back and foreground colors.
SDL_Color fg={0,0,0};
//Tool specific text.
SDL_Surface* tip=NULL;
switch(tooltip){
case 0:
tip=TTF_RenderUTF8_Blended(fontText,_("Select"),fg);
break;
case 1:
tip=TTF_RenderUTF8_Blended(fontText,_("Add"),fg);
break;
case 2:
tip=TTF_RenderUTF8_Blended(fontText,_("Delete"),fg);
break;
case 3:
tip=TTF_RenderUTF8_Blended(fontText,_("Play"),fg);
break;
case 5:
tip=TTF_RenderUTF8_Blended(fontText,_("Level settings"),fg);
break;
case 6:
tip=TTF_RenderUTF8_Blended(fontText,_("Save level"),fg);
break;
case 7:
tip=TTF_RenderUTF8_Blended(fontText,_("Back to menu"),fg);
break;
default:
break;
}
//Draw only if there's a tooltip available
if(tip!=NULL){
SDL_Rect r={(SCREEN_WIDTH-390)/2+(tooltip*40)+(tooltip*10),SCREEN_HEIGHT-45,40,40};
r.y=SCREEN_HEIGHT-50-tip->h;
if(r.x+tip->w>SCREEN_WIDTH-50)
r.x=SCREEN_WIDTH-50-tip->w;
//Draw borders around text
Uint32 color=0xFFFFFF00|230;
drawGUIBox(r.x-2,r.y-2,tip->w+4,tip->h+4,screen,color);
//Draw tooltip's text
SDL_BlitSurface(tip,NULL,screen,&r);
SDL_FreeSurface(tip);
}
}
//Draw a rectangle around the current tool.
Uint32 color=0xFFFFFF00;
drawGUIBox((SCREEN_WIDTH-390)/2+(tool*40)+(tool*10),SCREEN_HEIGHT-46,42,42,screen,color);
}
void LevelEditor::showCurrentObject(){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
x+=camera.x;
y+=camera.y;
//Check if we should snap the block to grid or not.
if(!pressedShift){
snapToGrid(&x,&y);
}else{
x-=25;
y-=25;
}
//Check if the currentType is a legal type.
if(currentType>=0 && currentType<EDITOR_ORDER_MAX){
ThemeBlock* obj=objThemes.getBlock(editorTileOrder[currentType]);
if(obj){
obj->editorPicture.draw(placement,x-camera.x,y-camera.y);
}
}
}
void LevelEditor::showSelectionDrag(){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
x+=camera.x;
y+=camera.y;
//Check if we should snap the block to grid or not.
if(!pressedShift){
snapToGrid(&x,&y);
}else{
x-=25;
y-=25;
}
//Check if the drag center isn't null.
if(dragCenter==NULL) return;
//The location of the dragCenter.
SDL_Rect r=dragCenter->getBox();
//Loop through the selection.
//TODO: Check if block is in sight.
for(unsigned int o=0; o<selection.size(); o++){
ThemeBlock* obj=objThemes.getBlock(selection[o]->type);
if(obj){
SDL_Rect r1=selection[o]->getBox();
obj->editorPicture.draw(placement,(r1.x-r.x)+x-camera.x,(r1.y-r.y)+y-camera.y);
}
}
}
void LevelEditor::showConfigure(){
//arrow animation value. go through 0-65535 and loops.
static unsigned short arrowAnimation=0;
arrowAnimation++;
//By default use black color for arrows.
Uint32 color=0x00000000;
//Theme can change the color.
//TODO: use the actual color from the theme.
if(themeTextColor.r>128 && themeTextColor.g>128 && themeTextColor.b>128)
color=0xffffffff;
//Draw the trigger lines.
{
map<Block*,vector<GameObject*> >::iterator it;
for(it=triggers.begin();it!=triggers.end();++it){
//Check if the trigger has linked targets.
if(!(*it).second.empty()){
//The location of the trigger.
SDL_Rect r=(*it).first->getBox();
//Loop through the targets.
for(unsigned int o=0;o<(*it).second.size();o++){
//Get the location of the target.
SDL_Rect r1=(*it).second[o]->getBox();
//Draw the line from the center of the trigger to the center of the target.
drawLineWithArrow(r.x-camera.x+25,r.y-camera.y+25,r1.x-camera.x+25,r1.y-camera.y+25,placement,color,32,arrowAnimation%32);
//Also draw two selection marks.
applySurface(r.x-camera.x+25-2,r.y-camera.y+25-2,selectionMark,screen,NULL);
applySurface(r1.x-camera.x+25-2,r1.y-camera.y+25-2,selectionMark,screen,NULL);
}
}
}
//Draw a line to the mouse from the linkingTrigger when linking.
if(linking){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Draw the line from the center of the trigger to mouse.
drawLineWithArrow(linkingTrigger->getBox().x-camera.x+25,linkingTrigger->getBox().y-camera.y+25,x,y,placement,color,32,arrowAnimation%32);
}
}
//Draw the moving positions.
map<Block*,vector<MovingPosition> >::iterator it;
for(it=movingBlocks.begin();it!=movingBlocks.end();++it){
//Check if the block has positions.
if(!(*it).second.empty()){
//The location of the moving block.
SDL_Rect block=(*it).first->getBox();
block.x+=25-camera.x;
block.y+=25-camera.y;
//The location of the previous position.
//The first time it's the moving block's position self.
SDL_Rect r=block;
//Loop through the positions.
for(unsigned int o=0;o<(*it).second.size();o++){
//Draw the line from the center of the previous position to the center of the position.
//x and y are the coordinates for the current moving position.
int x=block.x+(*it).second[o].x;
int y=block.y+(*it).second[o].y;
//Check if we need to draw line
double dx=r.x-x;
double dy=r.y-y;
double d=sqrt(dx*dx+dy*dy);
if(d>0.001f){
if(it->second[o].time>0){
//Calculate offset to contain the moving speed.
int offset=int(d*arrowAnimation/it->second[o].time)%32;
drawLineWithArrow(r.x,r.y,x,y,placement,color,32,offset);
}else{
//time==0 ???? so don't draw arrow at all
drawLine(r.x,r.y,x,y,placement);
}
}
//And draw a marker at the end.
applySurface(x-13,y-13,movingMark,screen,NULL);
//Get the box of the previous position.
SDL_Rect tmp={x,y,0,0};
r=tmp;
}
}
}
//Draw a line to the mouse from the previous moving pos.
if(moving){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Check if we should snap the block to grid or not.
if(!pressedShift){
x+=camera.x;
y+=camera.y;
snapToGrid(&x,&y);
x-=camera.x;
y-=camera.y;
}else{
x-=25;
y-=25;
}
int posX,posY;
//Check if there are moving positions for the moving block.
if(!movingBlocks[movingBlock].empty()){
//Draw the line from the center of the previouse moving positions to mouse.
posX=movingBlocks[movingBlock].back().x;
posY=movingBlocks[movingBlock].back().y;
posX-=camera.x;
posY-=camera.y;
posX+=movingBlock->getBox().x;
posY+=movingBlock->getBox().y;
}else{
//Draw the line from the center of the movingblock to mouse.
posX=movingBlock->getBox().x-camera.x;
posY=movingBlock->getBox().y-camera.y;
}
//Calculate offset to contain the moving speed.
int offset=int(double(arrowAnimation)*movingSpeed/10.0)%32;
drawLineWithArrow(posX+25,posY+25,x+25,y+25,placement,color,32,offset);
applySurface(x+12,y+12,movingMark,screen,NULL);
}
}
void LevelEditor::resize(){
//Call the resize method of the Game.
Game::resize();
//Now update the placement surface.
if(placement)
SDL_FreeSurface(placement);
placement=SDL_CreateRGBSurface(SDL_SWSURFACE|SDL_SRCALPHA,SCREEN_WIDTH,SCREEN_HEIGHT,32,RMASK,GMASK,BMASK,0);
SDL_SetColorKey(placement,SDL_SRCCOLORKEY|SDL_RLEACCEL,SDL_MapRGB(placement->format,255,0,255));
SDL_SetAlpha(placement,SDL_SRCALPHA,125);
//Move the toolbar's position rect used for collision.
toolbarRect.x=(SCREEN_WIDTH-410)/2;
toolbarRect.y=SCREEN_HEIGHT-50;
}
//Filling the order array
const int LevelEditor::editorTileOrder[EDITOR_ORDER_MAX]={
TYPE_BLOCK,
TYPE_SHADOW_BLOCK,
TYPE_SPIKES,
TYPE_FRAGILE,
TYPE_MOVING_BLOCK,
TYPE_MOVING_SHADOW_BLOCK,
TYPE_MOVING_SPIKES,
TYPE_CONVEYOR_BELT,
TYPE_SHADOW_CONVEYOR_BELT,
TYPE_BUTTON,
TYPE_SWITCH,
TYPE_PORTAL,
TYPE_SWAP,
TYPE_CHECKPOINT,
TYPE_NOTIFICATION_BLOCK,
TYPE_START_PLAYER,
TYPE_START_SHADOW,
TYPE_EXIT,
TYPE_COLLECTABLE,
TYPE_PUSHABLE
};
diff --git a/src/LevelPlaySelect.cpp b/src/LevelPlaySelect.cpp
index d75ccd3..3fae013 100644
--- a/src/LevelPlaySelect.cpp
+++ b/src/LevelPlaySelect.cpp
@@ -1,465 +1,465 @@
/*
* 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 "LevelPlaySelect.h"
#include "GameState.h"
#include "Functions.h"
#include "FileManager.h"
#include "Globals.h"
#include "Objects.h"
#include "LevelSelect.h"
#include "GUIObject.h"
#include "GUIListBox.h"
#include "GUIScrollBar.h"
#include "InputManager.h"
#include "Game.h"
#ifdef __APPLE__
#include <SDL_ttf/SDL_ttf.h>
#else
#include <SDL/SDL_ttf.h>
#endif
#include <stdio.h>
#include <string>
#include <sstream>
#include <iostream>
using namespace std;
/////////////////////LEVEL SELECT/////////////////////
static string levelDescription,levelMedal2,levelMedal3,levelTime,levelRecs;
static string bestTimeFilePath,bestRecordingFilePath;
LevelPlaySelect::LevelPlaySelect():LevelSelect(_("Select Level")){
//Load the play button if needed.
playButtonImage=loadImage(getDataPath()+"gfx/playbutton.png");
timeIcon=loadImage(getDataPath()+"gfx/time.png");
recordingsIcon=loadImage(getDataPath()+"gfx/recordings.png");
//Create the gui.
createGUI(true);
//Show level list
refresh();
}
LevelPlaySelect::~LevelPlaySelect(){
play=NULL;
recordingsIcon=NULL;
timeIcon=NULL;
//Clear the selected level.
if(selectedNumber!=NULL){
delete selectedNumber;
selectedNumber=NULL;
}
}
void LevelPlaySelect::createGUI(bool initial){
//Create the play button.
if(initial){
- play=new GUIObject(SCREEN_WIDTH-240,SCREEN_HEIGHT-60,240,32,GUIObjectButton,_("Play"));
+ play=new GUIButton(SCREEN_WIDTH-240,SCREEN_HEIGHT-60,240,32,_("Play"));
}else{
play->left=SCREEN_WIDTH-240;
play->top=SCREEN_HEIGHT-60;
}
play->name="cmdPlay";
play->eventCallback=this;
play->enabled=false;
if(initial)
GUIObjectRoot->addChild(play);
}
void LevelPlaySelect::refresh(bool change){
int m=levels->getLevelCount();
numbers.clear();
//Clear the selected level.
if(selectedNumber!=NULL){
delete selectedNumber;
selectedNumber=NULL;
}
//Recreate the non selected number.
selectedNumber=new Number();
SDL_Rect box={40,SCREEN_HEIGHT-130,50,50};
selectedNumber->init(" ",box);
selectedNumber->setLocked(true);
levelDescription=_("Choose a level");
levelMedal2=string(_("Time:"));
levelMedal3=string(_("Recordings:"));
levelTime=string("- / -");
levelRecs=string("- / -");
bestTimeFilePath.clear();
bestRecordingFilePath.clear();
//Disable the play button.
play->enabled=false;
for(int n=0; n<m; n++){
numbers.push_back(Number());
}
for(int n=0; n<m; n++){
SDL_Rect box={(n%LEVELS_PER_ROW)*64+(SCREEN_WIDTH*0.2)/2,(n/LEVELS_PER_ROW)*64+184,0,0};
numbers[n].init(n,box);
numbers[n].setLocked(levels->getLocked(n));
int medal=levels->getLevel(n)->won;
if(medal){
if(levels->getLevel(n)->targetTime<0 || levels->getLevel(n)->time<=levels->getLevel(n)->targetTime)
medal++;
if(levels->getLevel(n)->targetRecordings<0 || levels->getLevel(n)->recordings<=levels->getLevel(n)->targetRecordings)
medal++;
}
numbers[n].setMedal(medal);
}
if(m>LEVELS_DISPLAYED_IN_SCREEN){
levelScrollBar->maxValue=(m-LEVELS_DISPLAYED_IN_SCREEN+(LEVELS_PER_ROW-1))/LEVELS_PER_ROW;
levelScrollBar->visible=true;
}else{
levelScrollBar->maxValue=0;
levelScrollBar->visible=false;
}
if(!levels->levelpackDescription.empty())
levelpackDescription->caption=_CC(levels->getDictionaryManager(),levels->levelpackDescription);
else
levelpackDescription->caption="";
}
void LevelPlaySelect::selectNumber(unsigned int number,bool selected){
if(selected){
levels->setCurrentLevel(number);
setNextState(STATE_GAME);
//Pick music from the current music list.
getMusicManager()->pickMusic();
}else{
displayLevelInfo(number);
}
}
void LevelPlaySelect::checkMouse(){
int x,y;
//Get the current mouse location.
SDL_GetMouseState(&x,&y);
//Check if we should replay the record.
if(selectedNumber!=NULL){
SDL_Rect mouse={x,y,0,0};
if(!bestTimeFilePath.empty()){
SDL_Rect box={SCREEN_WIDTH-420,SCREEN_HEIGHT-130,372,32};
if(checkCollision(box,mouse)){
Game::recordFile=bestTimeFilePath;
levels->setCurrentLevel(selectedNumber->getNumber());
setNextState(STATE_GAME);
//Pick music from the current music list.
getMusicManager()->pickMusic();
return;
}
}
if(!bestRecordingFilePath.empty()){
SDL_Rect box={SCREEN_WIDTH-420,SCREEN_HEIGHT-98,372,32};
if(checkCollision(box,mouse)){
Game::recordFile=bestRecordingFilePath;
levels->setCurrentLevel(selectedNumber->getNumber());
setNextState(STATE_GAME);
//Pick music from the current music list.
getMusicManager()->pickMusic();
return;
}
}
}
//Call the base method from the super class.
LevelSelect::checkMouse();
}
void LevelPlaySelect::displayLevelInfo(int number){
//Update currently selected level
if(selectedNumber==NULL){
selectedNumber=new Number();
}
SDL_Rect box={40,SCREEN_HEIGHT-130,50,50};
selectedNumber->init(number,box);
selectedNumber->setLocked(false);
//Show level description
levelDescription=levels->getLevelName(number);
//Show level medal
int medal=levels->getLevel(number)->won;
int time=levels->getLevel(number)->time;
int targetTime=levels->getLevel(number)->targetTime;
int recordings=levels->getLevel(number)->recordings;
int targetRecordings=levels->getLevel(number)->targetRecordings;
if(medal){
if(targetTime<0){
medal=-1;
}else{
if(targetTime<0 || time<=targetTime)
medal++;
if(targetRecordings<0 || recordings<=targetRecordings)
medal++;
}
}
selectedNumber->setMedal(medal);
//Show best time and recordings
if(medal){
char s[64];
if(time>0)
if(targetTime>0)
sprintf(s,"%-.2fs / %-.2fs",time/40.0f,targetTime/40.0f);
else
sprintf(s,"%-.2fs / -",time/40.0f);
else
s[0]='\0';
levelTime=string(s);
if(recordings>=0)
if(targetRecordings>=0)
sprintf(s,"%5d / %d",recordings,targetRecordings);
else
sprintf(s,"%5d / -",recordings);
else
s[0]='\0';
levelRecs=string(s);
}else{
levelTime=string("- / -");
levelRecs=string("- / -");
}
//Show the play button.
play->enabled=true;
//Check if there is auto record file
levels->getLevelAutoSaveRecordPath(number,bestTimeFilePath,bestRecordingFilePath,false);
if(!bestTimeFilePath.empty()){
FILE *f;
f=fopen(bestTimeFilePath.c_str(),"rb");
if(f==NULL){
bestTimeFilePath.clear();
}else{
fclose(f);
}
}
if(!bestRecordingFilePath.empty()){
FILE *f;
f=fopen(bestRecordingFilePath.c_str(),"rb");
if(f==NULL){
bestRecordingFilePath.clear();
}else{
fclose(f);
}
}
}
void LevelPlaySelect::render(){
//First let the levelselect render.
LevelSelect::render();
int x,y,dy=0;
//Get the current mouse location.
SDL_GetMouseState(&x,&y);
if(levelScrollBar)
dy=levelScrollBar->value;
//Upper bound of levels we'd like to display.
y+=dy*64;
SDL_Rect mouse={x,y,0,0};
//Show currently selected level (if any)
if(selectedNumber!=NULL){
selectedNumber->show(0);
SDL_Surface* bm;
if(!levelDescription.empty()){
bm=TTF_RenderUTF8_Blended(fontText,_CC(levels->getDictionaryManager(),levelDescription.c_str()),themeTextColor);
applySurface(100,SCREEN_HEIGHT-130+(50-bm->h)/2,bm,screen,NULL);
SDL_FreeSurface(bm);
}
//Only show the replay if the level is completed (won).
if(selectedNumber->getNumber()>=0 && selectedNumber->getNumber()<levels->getLevelCount()) {
if(levels->getLevel(selectedNumber->getNumber())->won){
if(!bestTimeFilePath.empty()){
SDL_Rect r={0,0,32,32};
SDL_Rect box={SCREEN_WIDTH-420,SCREEN_HEIGHT-130,372,32};
if(checkCollision(box,mouse)){
r.x=32;
SDL_FillRect(screen,&box,0xFFCCCCCC);
}
applySurface(SCREEN_WIDTH-80,SCREEN_HEIGHT-130,playButtonImage,screen,&r);
}
if(!bestRecordingFilePath.empty()){
SDL_Rect r={0,0,32,32};
SDL_Rect box={SCREEN_WIDTH-420,SCREEN_HEIGHT-98,372,32};
if(checkCollision(box,mouse)){
r.x=32;
SDL_FillRect(screen,&box,0xFFCCCCCC);
}
applySurface(SCREEN_WIDTH-80,SCREEN_HEIGHT-98,playButtonImage,screen,&r);
}
}
}
if(!levelMedal2.empty()){
//Draw the icon.
applySurface(SCREEN_WIDTH-405,SCREEN_HEIGHT-130+3,timeIcon,screen,NULL);
//Now draw the text (title).
bm=TTF_RenderUTF8_Blended(fontText,levelMedal2.c_str(),themeTextColor);
applySurface(SCREEN_WIDTH-380,SCREEN_HEIGHT-130+3,bm,screen,NULL);
SDL_FreeSurface(bm);
//Now draw the second text (value).
bm=TTF_RenderUTF8_Blended(fontText,levelTime.c_str(),themeTextColor);
applySurface(SCREEN_WIDTH-bm->w-80,SCREEN_HEIGHT-130+3,bm,screen,NULL);
SDL_FreeSurface(bm);
}
if(!levelMedal3.empty()){
//Draw the icon.
applySurface(SCREEN_WIDTH-405,SCREEN_HEIGHT-98+(6)/2,recordingsIcon,screen,NULL);
//Now draw the text (title).
bm=TTF_RenderUTF8_Blended(fontText,levelMedal3.c_str(),themeTextColor);
applySurface(SCREEN_WIDTH-380,SCREEN_HEIGHT-98+(32-bm->h)/2,bm,screen,NULL);
SDL_FreeSurface(bm);
//Now draw the second text (value).
bm=TTF_RenderUTF8_Blended(fontText,levelRecs.c_str(),themeTextColor);
applySurface(SCREEN_WIDTH-bm->w-80,SCREEN_HEIGHT-98+(32-bm->h)/2,bm,screen,NULL);
SDL_FreeSurface(bm);
}
}
}
void LevelPlaySelect::renderTooltip(unsigned int number,int dy){
SDL_Color themeTextColor={0,0,0};
char s[64];
//Render the name of the level.
SDL_Surface* name=TTF_RenderUTF8_Blended(fontText,_CC(levels->getDictionaryManager(),levels->getLevelName(number)),themeTextColor);
SDL_Surface* time=NULL;
SDL_Surface* recordings=NULL;
//The time it took.
if(levels->getLevel(number)->time>0){
sprintf(s,"%-.2fs",levels->getLevel(number)->time/40.0f);
time=TTF_RenderUTF8_Blended(fontText,s,themeTextColor);
}
//The number of recordings it took.
if(levels->getLevel(number)->recordings>=0){
sprintf(s,"%d",levels->getLevel(number)->recordings);
recordings=TTF_RenderUTF8_Blended(fontText,s,themeTextColor);
}
//Now draw a square the size of the three texts combined.
SDL_Rect r=numbers[number].box;
r.y-=dy*64;
if(time!=NULL && recordings!=NULL){
r.w=(name->w)>(25+time->w+40+recordings->w)?(name->w):(25+time->w+40+recordings->w);
r.h=name->h+5+20;
}else{
r.w=name->w;
r.h=name->h;
}
//Make sure the tooltip doesn't go outside the window.
if(r.y>SCREEN_HEIGHT-200){
r.y-=name->h+4;
}else{
r.y+=numbers[number].box.h+2;
}
if(r.x+r.w>SCREEN_WIDTH-50)
r.x=SCREEN_WIDTH-50-r.w;
//Draw a rectange
Uint32 color=0xFFFFFFFF;
drawGUIBox(r.x-5,r.y-5,r.w+10,r.h+10,screen,color);
//Calc the position to draw.
SDL_Rect r2=r;
//Now we render the name if the surface isn't null.
if(name!=NULL){
//Draw the name.
SDL_BlitSurface(name,NULL,screen,&r2);
}
//Increase the height to leave a gap between name and stats.
r2.y+=30;
if(time!=NULL){
//Now draw the time.
applySurface(r2.x,r2.y,timeIcon,screen,NULL);
r2.x+=25;
SDL_BlitSurface(time,NULL,screen,&r2);
r2.x+=time->w+15;
}
if(recordings!=NULL){
//Now draw the recordings.
applySurface(r2.x,r2.y,recordingsIcon,screen,NULL);
r2.x+=25;
SDL_BlitSurface(recordings,NULL,screen,&r2);
}
//And free the surfaces.
SDL_FreeSurface(name);
SDL_FreeSurface(time);
SDL_FreeSurface(recordings);
}
void LevelPlaySelect::resize(){
//Let the LevelSelect do his stuff.
LevelSelect::resize();
//Now create our gui again.
createGUI(false);
}
void LevelPlaySelect::GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType){
//Let the level select handle his GUI events.
LevelSelect::GUIEventCallback_OnEvent(name,obj,eventType);
//Check for the play button.
if(name=="cmdPlay"){
if(selectedNumber!=NULL){
levels->setCurrentLevel(selectedNumber->getNumber());
setNextState(STATE_GAME);
//Pick music from the current music list.
getMusicManager()->pickMusic();
}
}
}
diff --git a/src/LevelSelect.cpp b/src/LevelSelect.cpp
index dd5b56c..00d110a 100644
--- a/src/LevelSelect.cpp
+++ b/src/LevelSelect.cpp
@@ -1,416 +1,416 @@
/*
* 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 "GameState.h"
#include "Functions.h"
#include "FileManager.h"
#include "Globals.h"
#include "Objects.h"
#include "LevelSelect.h"
#include "GUIObject.h"
#include "GUIListBox.h"
#include "GUIScrollBar.h"
#include "InputManager.h"
#include "Game.h"
#ifdef __APPLE__
#include <SDL_ttf/SDL_ttf.h>
#else
#include <SDL/SDL_ttf.h>
#endif
#include <stdio.h>
#include <string>
#include <sstream>
#include <iostream>
#include "libs/tinyformat/tinyformat.h"
using namespace std;
////////////////////NUMBER////////////////////////
Number::Number(){
image=NULL;
number=0;
medal=0;
selected=false;
locked=false;
//Set the default dimensions.
box.x=0;
box.y=0;
box.h=50;
box.w=50;
//Load the medals image.
medals=loadImage(getDataPath()+"gfx/medals.png");
}
Number::~Number(){
//We only need to free the SDLSurface.
if(image) SDL_FreeSurface(image);
}
void Number::init(int number,SDL_Rect box){
//First set the number and update our status.
this->number=number;
//Write our text, number+1 since the counting doens't start with 0, but with 1.
std::stringstream text;
number++;
text<<number;
//Create the text image.
SDL_Color black={0,0,0};
if(image) SDL_FreeSurface(image);
//Create the text image.
//Also check which font to use, if the number is higher than 100 use the small font.
image=TTF_RenderUTF8_Blended(fontGUI,text.str().c_str(),black);
//Set the new location of the number.
this->box.x=box.x;
this->box.y=box.y;
//Load background blocks.
objThemes.getBlock(TYPE_BLOCK,true)->createInstance(&block);
block.changeState("unlocked");
objThemes.getBlock(TYPE_SHADOW_BLOCK,true)->createInstance(&blockLocked);
blockLocked.changeState("locked");
}
void Number::init(std::string text,SDL_Rect box){
//First set the number and update our status.
this->number=-1;
//Create the text image.
SDL_Color black={0,0,0};
if(image) SDL_FreeSurface(image);
image=TTF_RenderUTF8_Blended(fontGUI,text.c_str(),black);
//Set the new location of the number.
this->box.x=box.x;
this->box.y=box.y;
//Load background blocks.
objThemes.getBlock(TYPE_BLOCK,true)->createInstance(&block);
block.changeState("unlocked");
objThemes.getBlock(TYPE_SHADOW_BLOCK,true)->createInstance(&blockLocked);
blockLocked.changeState("locked");
}
void Number::show(int dy){
//First draw the background, also apply the yOffset(dy).
if(!locked)
block.draw(screen,box.x,box.y-dy);
else
blockLocked.draw(screen,box.x,box.y-dy);
//Now draw the text image over the background.
//We draw it centered inside the box.
applySurface((box.x+25-(image->w/2)),box.y-dy,image,screen,NULL);
//Draw the selection mark.
if(selected){
drawGUIBox(box.x,box.y-dy,50,50,screen,0xFFFFFF23);
}
//Draw the medal.
if(medal>0){
SDL_Rect r={(medal-1)*30,0,30,30};
applySurface(box.x+30,(box.y+30)-dy,medals,screen,&r);
}
}
void Number::setLocked(bool locked){
this->locked=locked;
}
void Number::setMedal(int medal){
this->medal=medal;
}
/////////////////////LEVEL SELECT/////////////////////
LevelSelect::LevelSelect(string titleText,LevelPackManager::LevelPackLists packType){
//clear the selected level
selectedNumber=NULL;
//Calculate the LEVELS_PER_ROW and LEVEL_ROWS if they aren't calculated already.
calcRows();
//Render the title.
title=TTF_RenderUTF8_Blended(fontTitle,titleText.c_str(),themeTextColor);
//create GUI (test only)
GUIObject* obj;
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
GUIObjectRoot=new GUIObject(0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
//the level select scroll bar
levelScrollBar=new GUIScrollBar(SCREEN_WIDTH*0.9,184,16,SCREEN_HEIGHT-344,ScrollBarVertical,0,0,0,1,4,true,false);
GUIObjectRoot->addChild(levelScrollBar);
//level pack description
- levelpackDescription=new GUIObject(0,140,SCREEN_WIDTH,32,GUIObjectLabel,"",0,true,true,GUIGravityCenter);
+ levelpackDescription=new GUILabel(0,140,SCREEN_WIDTH,32,"",0,true,true,GUIGravityCenter);
GUIObjectRoot->addChild(levelpackDescription);
levelpacks=new GUISingleLineListBox((SCREEN_WIDTH-500)/2,104,500,32);
levelpacks->name="cmdLvlPack";
levelpacks->eventCallback=this;
vector<pair<string,string> > v=getLevelPackManager()->enumLevelPacks(packType);
levelpacks->addItems(v);
levelpacks->value=0;
//Check if we can find the lastlevelpack.
for(vector<pair<string,string> >::iterator i=v.begin(); i!=v.end(); ++i){
if(i->first==getSettings()->getValue("lastlevelpack")){
levelpacks->value=i-v.begin();
}
}
//Load the progress.
levels=getLevelPackManager()->getLevelPack(v[levelpacks->value].first);
levels->loadProgress();
//And add the levelpack single line listbox to the GUIObjectRoot.
GUIObjectRoot->addChild(levelpacks);
- obj=new GUIObject(20,20,-1,32,GUIObjectButton,_("Back"));
+ obj=new GUIButton(20,20,-1,32,_("Back"));
obj->name="cmdBack";
obj->eventCallback=this;
GUIObjectRoot->addChild(obj);
section=1;
}
LevelSelect::~LevelSelect(){
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
levelScrollBar=NULL;
levelpackDescription=NULL;
selectedNumber=NULL;
//Free the rendered title surface.
SDL_FreeSurface(title);
}
void LevelSelect::calcRows(){
//Calculate the number of rows and the number of levels per row.
LEVELS_PER_ROW=(SCREEN_WIDTH*0.8)/64;
int LEVEL_ROWS=(SCREEN_HEIGHT-344)/64;
LEVELS_DISPLAYED_IN_SCREEN=LEVELS_PER_ROW*LEVEL_ROWS;
}
void LevelSelect::selectNumberKeyboard(int x,int y){
if(section==2){
//Move selection
int realNumber=0;
if(selectedNumber)
realNumber=selectedNumber->getNumber()+x+(y*LEVELS_PER_ROW);
//If selection is outside of the map grid, change section
if(realNumber<0 || realNumber>(int)numbers.size()-1){
section=1;
for(int i=0;i<levels->getLevelCount();i++){
numbers[i].selected=false;
refresh();
}
}else{
//If not, move selection
if(!numbers[realNumber].getLocked()){
for(int i=0;i<levels->getLevelCount();i++){
numbers[i].selected=(i==realNumber);
}
selectNumber(realNumber,false);
}
}
}else if(section==1){
//Loop through levelpacks and update GUI
levelpacks->value+=x;
if(levelpacks->value<0){
levelpacks->value=levelpacks->item.size()-1;
}else if(levelpacks->value>(int)levelpacks->item.size()-1){
levelpacks->value=0;
}
GUIEventCallback_OnEvent("cmdLvlPack",static_cast<GUIObject*>(levelpacks),0);
//If up is pressed, change section
if(y==1){
section=2;
selectNumber(0,false);
numbers[0].selected=true;
}
}else{
section=clamp(section+y,0,2);
}
}
void LevelSelect::handleEvents(){
//Check for an SDL_QUIT event.
if(event.type==SDL_QUIT){
setNextState(STATE_EXIT);
}
//Check for a mouse click.
if(event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT){
checkMouse();
}
//Check focus movement
if(inputMgr.isKeyDownEvent(INPUTMGR_RIGHT)){
selectNumberKeyboard(1,0);
}else if(inputMgr.isKeyDownEvent(INPUTMGR_LEFT)){
selectNumberKeyboard(-1,0);
}else if(inputMgr.isKeyDownEvent(INPUTMGR_UP)){
selectNumberKeyboard(0,-1);
}else if(inputMgr.isKeyDownEvent(INPUTMGR_DOWN)){
selectNumberKeyboard(0,1);
}
//Check if enter is pressed
if(section==2 && inputMgr.isKeyUpEvent(INPUTMGR_SELECT)){
selectNumber(selectedNumber->getNumber(),true);
}
//Check if escape is pressed.
if(inputMgr.isKeyUpEvent(INPUTMGR_ESCAPE)){
setNextState(STATE_MENU);
}
//Check for scrolling down and up.
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_WHEELDOWN && levelScrollBar){
if(levelScrollBar->value<levelScrollBar->maxValue) levelScrollBar->value++;
return;
}else if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_WHEELUP && levelScrollBar){
if(levelScrollBar->value>0) levelScrollBar->value--;
return;
}
}
void LevelSelect::checkMouse(){
int x,y,dy=0,m=numbers.size();
//Get the current mouse location.
SDL_GetMouseState(&x,&y);
//Check if there's a scrollbar, if so get the value.
if(levelScrollBar)
dy=levelScrollBar->value;
//Upper bound of levels we'd like to display.
if(m>dy*LEVELS_PER_ROW+LEVELS_DISPLAYED_IN_SCREEN)
m=dy*LEVELS_PER_ROW+LEVELS_DISPLAYED_IN_SCREEN;
y+=dy*64;
SDL_Rect mouse={x,y,0,0};
for(int n=dy*LEVELS_PER_ROW; n<m; n++){
if(!numbers[n].getLocked()){
if(checkCollision(mouse,numbers[n].box)==true){
if(numbers[n].selected){
selectNumber(n,true);
}else{
//Select current level
for(int i=0;i<levels->getLevelCount();i++){
numbers[i].selected=(i==n);
}
selectNumber(n,false);
}
section=2;
break;
}
}
}
}
void LevelSelect::logic(){}
void LevelSelect::render(){
int x,y,dy=0,m=numbers.size();
int idx=-1;
//Get the current mouse location.
SDL_GetMouseState(&x,&y);
if(levelScrollBar)
dy=levelScrollBar->value;
//Upper bound of levels we'd like to display.
if(m>dy*LEVELS_PER_ROW+LEVELS_DISPLAYED_IN_SCREEN)
m=dy*LEVELS_PER_ROW+LEVELS_DISPLAYED_IN_SCREEN;
y+=dy*64;
SDL_Rect mouse={x,y,0,0};
//Draw background.
objThemes.getBackground(true)->draw(screen);
objThemes.getBackground(true)->updateAnimation();
//Draw the title.
applySurface((SCREEN_WIDTH-title->w)/2,40-TITLE_FONT_RAISE,title,screen,NULL);
//Loop through the level blocks and draw them.
for(int n=dy*LEVELS_PER_ROW;n<m;n++){
numbers[n].show(dy*64);
if(numbers[n].getLocked()==false && checkCollision(mouse,numbers[n].box)==true)
idx=n;
}
//Show the tool tip text.
if(idx>=0){
renderTooltip(idx,dy);
}
}
void LevelSelect::resize(){
calcRows();
refresh(false);
//NOTE: We don't need to recreate the listbox and the back button, only resize the list.
levelpacks->left=(SCREEN_WIDTH-500)/2;
levelpackDescription->width = SCREEN_WIDTH;
}
void LevelSelect::GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType){
if(name=="cmdLvlPack"){
getSettings()->setValue("lastlevelpack",static_cast<GUISingleLineListBox*>(obj)->item[obj->value].first);
}else if(name=="cmdBack"){
setNextState(STATE_MENU);
return;
}else{
return;
}
//new: reset the level list scroll bar
if(levelScrollBar)
levelScrollBar->value=0;
levels=getLevelPackManager()->getLevelPack(static_cast<GUISingleLineListBox*>(obj)->item[obj->value].first);
//Load the progress file.
levels->loadProgress();
//And refresh the numbers.
refresh();
}
diff --git a/src/StatisticsScreen.cpp b/src/StatisticsScreen.cpp
index 6fa2a8e..8ff715e 100644
--- a/src/StatisticsScreen.cpp
+++ b/src/StatisticsScreen.cpp
@@ -1,354 +1,354 @@
/*
* 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 <stdio.h>
#include <string>
#include <vector>
#include <map>
#include "StatisticsManager.h"
#include "StatisticsScreen.h"
#include "Globals.h"
#include "Functions.h"
#include "ThemeManager.h"
#include "InputManager.h"
#include "GUIListBox.h"
#include "GUIScrollBar.h"
#ifdef __APPLE__
#include <SDL_image/SDL_image.h>
#include <SDL_gfx/SDL_gfxPrimitives.h>
#else
#include <SDL/SDL_image.h>
#include <SDL/SDL_gfxPrimitives.h>
#endif
using namespace std;
//GUI events are handled here.
//name: The name of the element that invoked the event.
//obj: Pointer to the object that invoked the event.
//eventType: Integer containing the type of event.
void StatisticsScreen::GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType){
//Check what type of event it was.
if(eventType==GUIEventClick){
if(name=="cmdBack"){
//Goto the main menu.
setNextState(STATE_MENU);
}
}
}
#define DRAW_PLAYER_STATISTICS(name,var,format) { \
surface=TTF_RenderUTF8_Blended(fontGUISmall,name,themeTextColor); \
SDL_SetAlpha(surface,0,0xFF); \
applySurface(0,y,surface,stats,NULL); \
y+=(h1=surface->h); \
SDL_FreeSurface(surface); \
sprintf(s,format,statsMgr.player##var+statsMgr.shadow##var); \
surface=TTF_RenderUTF8_Blended(fontText,s,themeTextColor); \
SDL_SetAlpha(surface,0,0xFF); \
applySurface(400-surface->w,y-(surface->h+h1)/2,surface,stats,NULL); \
SDL_FreeSurface(surface); \
sprintf(s,format,statsMgr.player##var); \
surface=TTF_RenderUTF8_Blended(fontText,s,themeTextColor); \
SDL_SetAlpha(surface,0,0xFF); \
applySurface(520-surface->w,y-(surface->h+h1)/2,surface,stats,NULL); \
SDL_FreeSurface(surface); \
sprintf(s,format,statsMgr.shadow##var); \
surface=TTF_RenderUTF8_Blended(fontText,s,themeTextColor); \
SDL_SetAlpha(surface,0,0xFF); \
applySurface(640-surface->w,y-(surface->h+h1)/2,surface,stats,NULL); \
SDL_FreeSurface(surface); \
}
#define DRAW_MISC_STATISTICS_1(name1,var1,format1) { \
surface=TTF_RenderUTF8_Blended(fontGUISmall,name1,themeTextColor); \
SDL_SetAlpha(surface,0,0xFF); \
applySurface(0,y,surface,stats,NULL); \
x=surface->w+8; \
y+=(h1=surface->h); \
SDL_FreeSurface(surface); \
sprintf(s,format1,statsMgr.var1); \
surface=TTF_RenderUTF8_Blended(fontText,s,themeTextColor); \
SDL_SetAlpha(surface,0,0xFF); \
applySurface(x,y-(surface->h+h1)/2,surface,stats,NULL); \
SDL_FreeSurface(surface); \
}
//we are so lazy that we just use height of the first one, ignore second one
#define DRAW_MISC_STATISTICS_2(name1,var1,format1,name2,var2,format2) { \
surface=TTF_RenderUTF8_Blended(fontGUISmall,name1,themeTextColor); \
SDL_SetAlpha(surface,0,0xFF); \
applySurface(0,y,surface,stats,NULL); \
x=surface->w+8; \
y+=(h1=surface->h); \
SDL_FreeSurface(surface); \
sprintf(s,format1,statsMgr.var1); \
surface=TTF_RenderUTF8_Blended(fontText,s,themeTextColor); \
SDL_SetAlpha(surface,0,0xFF); \
applySurface(x,y-(surface->h+h1)/2,surface,stats,NULL); \
SDL_FreeSurface(surface); \
surface=TTF_RenderUTF8_Blended(fontGUISmall,name2,themeTextColor); \
SDL_SetAlpha(surface,0,0xFF); \
applySurface(320,y-surface->h,surface,stats,NULL); \
x=surface->w+328; \
SDL_FreeSurface(surface); \
sprintf(s,format2,statsMgr.var2); \
surface=TTF_RenderUTF8_Blended(fontText,s,themeTextColor); \
SDL_SetAlpha(surface,0,0xFF); \
applySurface(x,y-(surface->h+h1)/2,surface,stats,NULL); \
SDL_FreeSurface(surface); \
}
//Constructor.
StatisticsScreen::StatisticsScreen(){
//Update in-game time.
statsMgr.updatePlayTime();
//Load needed pictures.
//Note: we don't use ImageManager because we need to process these pictures.
SDL_Surface* bmPlayer=IMG_Load((getDataPath()+"themes/Cloudscape/characters/player.png").c_str());
SDL_Surface* bmShadow=IMG_Load((getDataPath()+"themes/Cloudscape/characters/shadow.png").c_str());
SDL_Surface* bmMedal=IMG_Load((getDataPath()+"gfx/medals.png").c_str());
//Disable the alpha channel.
SDL_SetAlpha(bmPlayer,0,0xFF);
SDL_SetAlpha(bmShadow,0,0xFF);
SDL_SetAlpha(bmMedal,0,0xFF);
//Render the title.
title=TTF_RenderUTF8_Blended(fontTitle,_("Achievements and Statistics"),themeTextColor);
//Render stats.
stats=SDL_CreateRGBSurface(SDL_SWSURFACE,640,400,32,RMASK,GMASK,BMASK,AMASK);
char s[64];
SDL_Surface *surface;
SDL_Rect r;
int x,y=0,h1;
Uint32 clr=SDL_MapRGB(stats->format,themeTextColor.r,themeTextColor.g,themeTextColor.b);
//Player and shadow specific statistics
surface=TTF_RenderUTF8_Blended(fontGUISmall,_("Total"),themeTextColor);
SDL_SetAlpha(surface,0,0xFF);
applySurface(400-surface->w,40-surface->h,surface,stats,NULL);
SDL_FreeSurface(surface);
r.x=0;r.y=0;r.w=23;r.h=40;
applySurface(520-r.w,0,bmPlayer,stats,&r);
applySurface(640-r.w,0,bmShadow,stats,&r);
y+=40;
DRAW_PLAYER_STATISTICS(_("Traveling distance (m)"),TravelingDistance,"%0.2f");
DRAW_PLAYER_STATISTICS(_("Jump times"),Jumps,"%d");
DRAW_PLAYER_STATISTICS(_("Die times"),Dies,"%d");
DRAW_PLAYER_STATISTICS(_("Squashed times"),Squashed,"%d");
//Game specific statistics.
r.x=0;r.y=y;r.w=stats->w;r.h=2;
SDL_FillRect(stats,&r,clr);
y+=2;
DRAW_MISC_STATISTICS_2(_("Recordings:"),recordTimes,"%d",_("Switch pulled times:"),switchTimes,"%d");
DRAW_MISC_STATISTICS_1(_("Swap times:"),swapTimes,"%d");
DRAW_MISC_STATISTICS_2(_("Save times:"),saveTimes,"%d",_("Load times:"),loadTimes,"%d");
//Level specific statistics
r.x=0;r.y=y;r.w=stats->w;r.h=2;
SDL_FillRect(stats,&r,clr);
y+=2;
surface=TTF_RenderUTF8_Blended(fontGUISmall,_("Completed levels:"),themeTextColor);
SDL_SetAlpha(surface,0,0xFF);
applySurface(0,y,surface,stats,NULL);
x=surface->w+8;
y+=(h1=surface->h);
SDL_FreeSurface(surface);
sprintf(s,"%d",statsMgr.completedLevels);
surface=TTF_RenderUTF8_Blended(fontText,s,themeTextColor);
SDL_SetAlpha(surface,0,0xFF);
applySurface(x,y-(surface->h+h1)/2,surface,stats,NULL);
SDL_FreeSurface(surface);
sprintf(s,"%d",statsMgr.completedLevels-statsMgr.goldLevels-statsMgr.silverLevels);
surface=TTF_RenderUTF8_Blended(fontText,s,themeTextColor);
SDL_SetAlpha(surface,0,0xFF);
applySurface(400-surface->w,y-(surface->h+h1)/2,surface,stats,NULL);
r.x=0;r.y=0;r.w=30;r.h=30;
applySurface(400-surface->w-30,y-(30+h1)/2,bmMedal,stats,&r);
SDL_FreeSurface(surface);
sprintf(s,"%d",statsMgr.silverLevels);
surface=TTF_RenderUTF8_Blended(fontText,s,themeTextColor);
SDL_SetAlpha(surface,0,0xFF);
applySurface(520-surface->w,y-(surface->h+h1)/2,surface,stats,NULL);
r.x+=30;
applySurface(520-surface->w-30,y-(30+h1)/2,bmMedal,stats,&r);
SDL_FreeSurface(surface);
sprintf(s,"%d",statsMgr.goldLevels);
surface=TTF_RenderUTF8_Blended(fontText,s,themeTextColor);
SDL_SetAlpha(surface,0,0xFF);
applySurface(640-surface->w,y-(surface->h+h1)/2,surface,stats,NULL);
r.x+=30;
applySurface(640-surface->w-30,y-(30+h1)/2,bmMedal,stats,&r);
SDL_FreeSurface(surface);
//Other statistics.
r.x=0;r.y=y;r.w=stats->w;r.h=2;
SDL_FillRect(stats,&r,clr);
y+=2;
surface=TTF_RenderUTF8_Blended(fontGUISmall,_("In-game time:"),themeTextColor);
SDL_SetAlpha(surface,0,0xFF);
applySurface(0,y,surface,stats,NULL);
x=surface->w+8;
y+=(h1=surface->h);
SDL_FreeSurface(surface);
sprintf(s,"%02d:%02d:%02d",statsMgr.playTime/3600,(statsMgr.playTime/60)%60,statsMgr.playTime%60);
surface=TTF_RenderUTF8_Blended(fontText,s,themeTextColor);
SDL_SetAlpha(surface,0,0xFF);
applySurface(x,y-(surface->h+h1)/2,surface,stats,NULL);
SDL_FreeSurface(surface);
surface=TTF_RenderUTF8_Blended(fontGUISmall,_("Level editing time:"),themeTextColor);
SDL_SetAlpha(surface,0,0xFF);
applySurface(320,y-surface->h,surface,stats,NULL);
x=surface->w+328;
SDL_FreeSurface(surface);
sprintf(s,"%02d:%02d:%02d",statsMgr.levelEditTime/3600,(statsMgr.levelEditTime/60)%60,statsMgr.levelEditTime%60);
surface=TTF_RenderUTF8_Blended(fontText,s,themeTextColor);
SDL_SetAlpha(surface,0,0xFF);
applySurface(x,y-(surface->h+h1)/2,surface,stats,NULL);
SDL_FreeSurface(surface);
DRAW_MISC_STATISTICS_1(_("Created levels:"),createdLevels,"%d");
//Free loaded surface.
SDL_FreeSurface(bmPlayer);
SDL_FreeSurface(bmShadow);
SDL_FreeSurface(bmMedal);
//Create GUI.
createGUI();
}
//Destructor.
StatisticsScreen::~StatisticsScreen(){
//Delete the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Free images.
SDL_FreeSurface(title);
SDL_FreeSurface(stats);
}
//Method that will create the GUI for the options menu.
void StatisticsScreen::createGUI(){
//Create the root element of the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
- GUIObjectRoot=new GUIObject(0,0,SCREEN_WIDTH,SCREEN_HEIGHT,GUIObjectNone);
+ GUIObjectRoot=new GUIObject(0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
//Create back button.
- GUIObject* obj=new GUIObject(SCREEN_WIDTH*0.5,SCREEN_HEIGHT-60,-1,36,GUIObjectButton,_("Back"),0,true,true,GUIGravityCenter);
+ GUIObject* obj=new GUIButton(SCREEN_WIDTH*0.5,SCREEN_HEIGHT-60,-1,36,_("Back"),0,true,true,GUIGravityCenter);
obj->name="cmdBack";
obj->eventCallback=this;
GUIObjectRoot->addChild(obj);
//Create list box.
listBox=new GUISingleLineListBox((SCREEN_WIDTH-500)/2,104,500,32);
listBox->addItem(_("Achievements"));
listBox->addItem(_("Statistics"));
listBox->value=0;
GUIObjectRoot->addChild(listBox);
list=new GUIListBox(64,150,SCREEN_WIDTH-128,SCREEN_HEIGHT-150-72);
list->selectable=false;
GUIObjectRoot->addChild(list);
for(int idx=0;achievementList[idx].id!=NULL;++idx){
time_t *lpt=NULL;
map<string,OwnedAchievement>::iterator it=statsMgr.achievements.find(achievementList[idx].id);
if(it!=statsMgr.achievements.end()){
lpt=&it->second.achievedTime;
}
SDL_Rect r;
r.x=r.y=0;
r.w=list->width-16;
SDL_Surface *surface=statsMgr.createAchievementSurface(&achievementList[idx],NULL,&r,false,lpt);
if(surface!=NULL){
hlineRGBA(surface,0,surface->w,0,0,0,0,32);
hlineRGBA(surface,0,surface->w,surface->h-1,0,0,0,128);
hlineRGBA(surface,0,surface->w,surface->h-2,0,0,0,32);
list->addItem("",surface);
}
}
}
//In this method all the key and mouse events should be handled.
//Note: The GUIEvents won't be handled here.
void StatisticsScreen::handleEvents(){
//Check if we need to quit, if so enter the exit state.
if(event.type==SDL_QUIT){
setNextState(STATE_EXIT);
}
//Check if the escape button is pressed, if so go back to the main menu.
if(inputMgr.isKeyUpEvent(INPUTMGR_ESCAPE)){
setNextState(STATE_MENU);
}
}
//All the logic that needs to be done should go in this method.
void StatisticsScreen::logic(){
}
//This method handles all the rendering.
void StatisticsScreen::render(){
//Draw background.
objThemes.getBackground(true)->draw(screen);
objThemes.getBackground(true)->updateAnimation();
//Draw title.
applySurface((SCREEN_WIDTH-title->w)/2,40-TITLE_FONT_RAISE,title,screen,NULL);
//Draw statistics.
if(listBox->value==1){
list->visible=false;
applySurface((SCREEN_WIDTH-stats->w)/2,144,stats,screen,NULL);
}else{
list->visible=true;
}
}
//Method that will be called when the screen size has been changed in runtime.
void StatisticsScreen::resize(){
//Recreate the gui to fit the new resolution.
createGUI();
}
diff --git a/src/TitleMenu.cpp b/src/TitleMenu.cpp
index b27970f..967117a 100644
--- a/src/TitleMenu.cpp
+++ b/src/TitleMenu.cpp
@@ -1,887 +1,887 @@
/*
* Copyright (C) 2011-2013 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 "GameState.h"
#include "Globals.h"
#include "TitleMenu.h"
#include "GUIListBox.h"
#include "GUITextArea.h"
#include "InputManager.h"
#include "StatisticsManager.h"
#include <iostream>
#include <algorithm>
#include <sstream>
#include "libs/tinygettext/tinygettext.hpp"
using namespace std;
/////////////////////////MAIN_MENU//////////////////////////////////
//Integer containing the highlighted/selected menu option.
static int highlight=0;
Menu::Menu(){
highlight=0;
animation=0;
//Load the title image.
title=loadImage(getDataPath()+"gfx/menu/title.png");
//Now render the five entries.
//SDL_Color black={0,0,0};
entries[0]=TTF_RenderUTF8_Blended(fontTitle,_("Play"),themeTextColor);
entries[1]=TTF_RenderUTF8_Blended(fontTitle,_("Options"),themeTextColor);
entries[2]=TTF_RenderUTF8_Blended(fontTitle,_("Map Editor"),themeTextColor);
entries[3]=TTF_RenderUTF8_Blended(fontTitle,_("Addons"),themeTextColor);
entries[4]=TTF_RenderUTF8_Blended(fontTitle,_("Quit"),themeTextColor);
entries[5]=TTF_RenderUTF8_Blended(fontTitle,">",themeTextColor);
entries[6]=TTF_RenderUTF8_Blended(fontTitle,"<",themeTextColor);
//Load the credits icon.
creditsIcon=loadImage(getDataPath()+"gfx/menu/credits.png");
statisticsIcon=loadImage(getDataPath()+"gfx/menu/statistics.png");
}
Menu::~Menu(){
//We need to free the five text surfaceses.
for(unsigned int i=0;i<7;i++)
SDL_FreeSurface(entries[i]);
}
void Menu::handleEvents(){
//Get the x and y location of the mouse.
int x,y;
SDL_GetMouseState(&x,&y);
//Calculate which option is highlighted using the location of the mouse.
//Only if mouse is 'doing something'
if(event.type==SDL_MOUSEMOTION || event.type==SDL_MOUSEBUTTONDOWN){
highlight=0;
if(x>=200&&x<SCREEN_WIDTH-200&&y>=(SCREEN_HEIGHT-250)/2&&y<(SCREEN_HEIGHT-200)/2+320){
highlight=(y-((SCREEN_HEIGHT-200)/2-64))/64;
if(highlight>5) highlight=0;
}
//Also check the icons.
if(y>=SCREEN_HEIGHT-56&&y<SCREEN_HEIGHT-8){
if(x>=SCREEN_WIDTH-8){
//do nothing
}else if(x>=SCREEN_WIDTH-56){
highlight=7;
}else if(x>=SCREEN_WIDTH-104){
highlight=6;
}
}
}
//Down/Up -arrows move highlight
if(inputMgr.isKeyDownEvent(INPUTMGR_DOWN)){
highlight++;
if(highlight>7)
highlight=0;
}
if(inputMgr.isKeyDownEvent(INPUTMGR_UP)){
highlight--;
if(highlight<1)
highlight=7;
}
//Check if there's a press event.
if((event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT) ||
(inputMgr.isKeyUpEvent(INPUTMGR_SELECT))){
//We have one so check which selected/highlighted option needs to be done.
switch(highlight){
case 1:
//Enter the levelSelect state.
setNextState(STATE_LEVEL_SELECT);
break;
case 2:
//Enter the options state.
setNextState(STATE_OPTIONS);
break;
case 3:
//Enter the levelEditor, but first set the level to a default leveledit map.
levelName="";
setNextState(STATE_LEVEL_EDIT_SELECT);
break;
case 4:
//Check if internet is enabled.
if(!getSettings()->getBoolValue("internet")){
msgBox(_("Enable internet in order to install addons."),MsgBoxOKOnly,_("Internet disabled"));
break;
}
//Enter the addons state.
setNextState(STATE_ADDONS);
break;
case 5:
//We quit, so we enter the exit state.
setNextState(STATE_EXIT);
break;
case 6:
//Show the credits screen.
setNextState(STATE_CREDITS);
break;
case 7:
//Show the statistics screen.
setNextState(STATE_STATISTICS);
break;
}
}
//We also need to quit the menu when escape is pressed.
if(inputMgr.isKeyUpEvent(INPUTMGR_ESCAPE)){
setNextState(STATE_EXIT);
}
//Check if we need to quit, if so we enter the exit state.
if(event.type==SDL_QUIT){
setNextState(STATE_EXIT);
}
}
//Nothing to do here
void Menu::logic(){
animation++;
if(animation>10)
animation=-10;
}
void Menu::render(){
//Draw background.
objThemes.getBackground(true)->draw(screen);
objThemes.getBackground(true)->updateAnimation();
//Draw the title.
applySurface((SCREEN_WIDTH-title->w)/2,40,title,screen,NULL);
//Draw the menu entries.
for(unsigned int i=0;i<5;i++){
applySurface((SCREEN_WIDTH-entries[i]->w)/2,(SCREEN_HEIGHT-200)/2+64*i+(64-entries[i]->h)/2,entries[i],screen,NULL);
}
//Check if an option is selected/highlighted.
if(highlight>0 && highlight<=5){
//Draw the '>' sign, which is entry 5.
int x=(SCREEN_WIDTH-entries[highlight-1]->w)/2-(25-abs(animation)/2)-entries[5]->w;
int y=(SCREEN_HEIGHT-200)/2-64+64*highlight+(64-entries[5]->h)/2;
applySurface(x,y,entries[5],screen,NULL);
//Draw the '<' sign, which is entry 6.
x=(SCREEN_WIDTH-entries[highlight-1]->w)/2+entries[highlight-1]->w+(25-abs(animation)/2);
y=(SCREEN_HEIGHT-200)/2-64+64*highlight+(64-entries[6]->h)/2;
applySurface(x,y,entries[6],screen,NULL);
}
//Check if an icon is selected/highlighted and draw tooltip
if(highlight==6 || highlight==7){
SDL_Color fg={0,0,0};
SDL_Surface *surface=NULL;
if(highlight==6)
surface=TTF_RenderUTF8_Blended(fontText,_("Credits"),fg);
else
surface=TTF_RenderUTF8_Blended(fontText,_("Achievements and Statistics"),fg);
drawGUIBox(-2,SCREEN_HEIGHT-surface->h-2,surface->w+4,surface->h+4,screen,0xFFFFFFFF);
applySurface(0,SCREEN_HEIGHT-surface->h,surface,screen,NULL);
SDL_FreeSurface(surface);
}
//Draw icons.
applySurface(SCREEN_WIDTH-96,SCREEN_HEIGHT-48,creditsIcon,screen,NULL);
applySurface(SCREEN_WIDTH-48,SCREEN_HEIGHT-48,statisticsIcon,screen,NULL);
}
void Menu::resize(){}
/////////////////////////OPTIONS_MENU//////////////////////////////////
//Some varables for the options.
static bool fullscreen,leveltheme,internet,fade,quickrec;
static string themeName,languageName;
static int lastLang,lastRes;
static bool useProxy;
static string internetProxy;
static bool restartFlag;
static _res currentRes;
static vector<_res> resolutionList;
Options::Options(){
//Render the title.
title=TTF_RenderUTF8_Blended(fontTitle,_("Settings"),themeTextColor);
//Initialize variables.
lastJumpSound=0;
clearIconHower=false;
//Load icon image.
clearIcon=loadImage(getDataPath()+"gfx/menu/clear-progress.png");
//Set some default settings.
fullscreen=getSettings()->getBoolValue("fullscreen");
languageName=getSettings()->getValue("lang");
themeName=processFileName(getSettings()->getValue("theme"));
leveltheme=getSettings()->getBoolValue("leveltheme");
internet=getSettings()->getBoolValue("internet");
internetProxy=getSettings()->getValue("internet-proxy");
useProxy=!internetProxy.empty();
fade=getSettings()->getBoolValue("fading");
quickrec=getSettings()->getBoolValue("quickrecord");
//Set the restartFlag false.
restartFlag=false;
//Now create the gui.
createGUI();
}
Options::~Options(){
//Delete the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Free the title image.
SDL_FreeSurface(title);
}
void Options::createGUI(){
//Variables for positioning
const int columnW=SCREEN_WIDTH*0.3;
const int column1X=SCREEN_WIDTH*0.15;
const int column2X=SCREEN_WIDTH*0.55;
const int lineHeight=40;
//Create the root element of the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
- GUIObjectRoot=new GUIObject(0,0,SCREEN_WIDTH,SCREEN_HEIGHT,GUIObjectNone);
+ GUIObjectRoot=new GUIObject(0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
//Single line list for different tabs.
GUISingleLineListBox* listBox=new GUISingleLineListBox((SCREEN_WIDTH-500)/2,104,500,32);
listBox->addItem(_("General"));
listBox->addItem(_("Controls"));
listBox->value=0;
listBox->name="lstTabs";
listBox->eventCallback=this;
GUIObjectRoot->addChild(listBox);
//Create general tab.
- tabGeneral=new GUIObject(0,150,SCREEN_WIDTH,SCREEN_HEIGHT,GUIObjectNone);
+ tabGeneral=new GUIObject(0,150,SCREEN_WIDTH,SCREEN_HEIGHT);
GUIObjectRoot->addChild(tabGeneral);
//Now we create GUIObjects for every option.
- GUIObject* obj=new GUIObject(column1X,0,columnW,36,GUIObjectLabel,_("Music"));
+ GUIObject* obj=new GUILabel(column1X,0,columnW,36,_("Music"));
tabGeneral->addChild(obj);
musicSlider=new GUISlider(column2X,0,columnW,36,atoi(getSettings()->getValue("music").c_str()),0,128,15);
musicSlider->name="sldMusic";
musicSlider->eventCallback=this;
tabGeneral->addChild(musicSlider);
- obj=new GUIObject(column1X,lineHeight,columnW,36,GUIObjectLabel,_("Sound"));
+ obj=new GUILabel(column1X,lineHeight,columnW,36,_("Sound"));
tabGeneral->addChild(obj);
soundSlider=new GUISlider(column2X,lineHeight,columnW,36,atoi(getSettings()->getValue("sound").c_str()),0,128,15);
soundSlider->name="sldSound";
soundSlider->eventCallback=this;
tabGeneral->addChild(soundSlider);
- obj=new GUIObject(column1X,2*lineHeight,columnW,36,GUIObjectLabel,_("Resolution"));
+ obj=new GUILabel(column1X,2*lineHeight,columnW,36,_("Resolution"));
obj->name="lstResolution";
tabGeneral->addChild(obj);
//Create list with many different resolutions.
resolutions = new GUISingleLineListBox(column2X,2*lineHeight,columnW,36);
resolutions->value=-1;
//Enumerate available resolutions using SDL_ListModes()
//Note: we enumerate fullscreen resolutions because
// windowed resolutions always can be arbitrary.
if(resolutionList.empty()){
SDL_Rect **modes=SDL_ListModes(NULL,SDL_FULLSCREEN|SCREEN_FLAGS|SDL_ANYFORMAT);
if(modes==NULL || ((intptr_t)modes) == -1){
cout<<"Error: Can't enumerate available screen resolutions."
" Use predefined screen resolutions list instead."<<endl;
static const _res predefinedResolutionList[] = {
{800,600},
{1024,600},
{1024,768},
{1152,864},
{1280,720},
{1280,768},
{1280,800},
{1280,960},
{1280,1024},
{1360,768},
{1366,768},
{1440,900},
{1600,900},
{1600,1200},
{1680,1080},
{1920,1080},
{1920,1200},
{2560,1440},
{3840,2160}
};
for(unsigned int i=0;i<sizeof(predefinedResolutionList)/sizeof(_res);i++){
resolutionList.push_back(predefinedResolutionList[i]);
}
}else{
for(unsigned int i=0;modes[i]!=NULL;i++){
//Check if the resolution is big enough
if(modes[i]->w>=800 && modes[i]->h>=600){
_res res={modes[i]->w, modes[i]->h};
resolutionList.push_back(res);
}
}
reverse(resolutionList.begin(),resolutionList.end());
}
}
//Get current resolution from config file. Thus it can be user defined.
currentRes.w=atoi(getSettings()->getValue("width").c_str());
currentRes.h=atoi(getSettings()->getValue("height").c_str());
for (int i=0; i<(int)resolutionList.size();i++){
//Create a string from width and height and then add it to list.
ostringstream out;
out << resolutionList[i].w << "x" << resolutionList[i].h;
resolutions->addItem(out.str());
//Check if current resolution matches, select it.
if(resolutionList[i].w==currentRes.w && resolutionList[i].h==currentRes.h){
resolutions->value=i;
}
}
//Add current resolution if it isn't already in the list.
if(resolutions->value==-1){
ostringstream out;
out << currentRes.w << "x" << currentRes.h;
resolutions->addItem(out.str());
resolutions->value=resolutions->item.size()-1;
}
lastRes=resolutions->value;
tabGeneral->addChild(resolutions);
- obj=new GUIObject(column1X,3*lineHeight,columnW,36,GUIObjectLabel,_("Language"));
+ obj=new GUILabel(column1X,3*lineHeight,columnW,36,_("Language"));
tabGeneral->addChild(obj);
//Create GUI list with available languages.
langs = new GUISingleLineListBox(column2X,3*lineHeight,columnW,36);
langs->name="lstLanguages";
/// TRANSLATORS: as detect user's language automatically
langs->addItem("",_("Auto-Detect"));
langs->addItem("en","English");
//Get a list of every available language.
set<tinygettext::Language> languages = dictionaryManager->get_languages();
for (set<tinygettext::Language>::iterator s0 = languages.begin(); s0 != languages.end(); ++s0){
//If language in loop is the same in config file, then select it
if(getSettings()->getValue("lang")==s0->str()){
lastLang=distance(languages.begin(),s0)+2;
}
//Add language in loop to list and listbox.
langs->addItem(s0->str(),s0->get_name());
}
//If Auto or English are selected.
if(getSettings()->getValue("lang")==""){
lastLang=0;
}else if(getSettings()->getValue("lang")=="en"){
lastLang=1;
}
langs->value=lastLang;
tabGeneral->addChild(langs);
- obj=new GUIObject(column1X,4*lineHeight,columnW,36,GUIObjectLabel,_("Theme"));
+ obj=new GUILabel(column1X,4*lineHeight,columnW,36,_("Theme"));
obj->name="theme";
tabGeneral->addChild(obj);
//Create the theme option gui element.
theme=new GUISingleLineListBox(column2X,4*lineHeight,columnW,36);
theme->name="lstTheme";
//Vector containing the theme locations and names.
vector<pair<string,string> > themes;
vector<string> v=enumAllDirs(getUserPath(USER_DATA)+"themes/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
string location=getUserPath(USER_DATA)+"themes/"+*i;
themes.push_back(pair<string,string>(location,*i));
}
vector<string> v2=enumAllDirs(getDataPath()+"themes/");
for(vector<string>::iterator i=v2.begin(); i!=v2.end(); ++i){
string location=getDataPath()+"themes/"+*i;
themes.push_back(pair<string,string>(location,*i));
}
//Try to find the configured theme so we can display it.
int value=-1;
for(vector<pair<string,string> >::iterator i=themes.begin(); i!=themes.end(); ++i){
if(i->first==themeName) {
value=i-themes.begin();
}
}
theme->addItems(themes);
if(value==-1)
value=theme->item.size()-1;
theme->value=value;
//NOTE: We call the event handling method to correctly set the themename.
GUIEventCallback_OnEvent("lstTheme",theme,GUIEventChange);
theme->eventCallback=this;
tabGeneral->addChild(theme);
//Proxy settings.
- obj=new GUIObject(column1X,5*lineHeight,columnW,36,GUIObjectLabel,_("Internet proxy"));
+ obj=new GUILabel(column1X,5*lineHeight,columnW,36,_("Internet proxy"));
obj->name="chkProxy";
obj->eventCallback=this;
tabGeneral->addChild(obj);
- obj=new GUIObject(column2X,5*lineHeight,columnW,36,GUIObjectTextBox,internetProxy.c_str());
+ obj=new GUITextBox(column2X,5*lineHeight,columnW,36,internetProxy.c_str());
obj->name="txtProxy";
obj->eventCallback=this;
tabGeneral->addChild(obj);
- obj=new GUIObject(column1X,6*lineHeight,columnW,36,GUIObjectCheckBox,_("Fullscreen"),fullscreen?1:0);
+ obj=new GUICheckBox(column1X,6*lineHeight,columnW,36,_("Fullscreen"),fullscreen?1:0);
obj->name="chkFullscreen";
obj->eventCallback=this;
tabGeneral->addChild(obj);
- obj=new GUIObject(column1X,7*lineHeight,columnW,36,GUIObjectCheckBox,_("Level themes"),leveltheme?1:0);
+ obj=new GUICheckBox(column1X,7*lineHeight,columnW,36,_("Level themes"),leveltheme?1:0);
obj->name="chkLeveltheme";
obj->eventCallback=this;
tabGeneral->addChild(obj);
- obj=new GUIObject(column2X,6*lineHeight,columnW,36,GUIObjectCheckBox,_("Internet"),internet?1:0);
+ obj=new GUICheckBox(column2X,6*lineHeight,columnW,36,_("Internet"),internet?1:0);
obj->name="chkInternet";
obj->eventCallback=this;
tabGeneral->addChild(obj);
- obj=new GUIObject(column2X,7*lineHeight,columnW,36,GUIObjectCheckBox,_("Fade transition"),fade?1:0);
+ obj=new GUICheckBox(column2X,7*lineHeight,columnW,36,_("Fade transition"),fade?1:0);
obj->name="chkFade";
obj->eventCallback=this;
tabGeneral->addChild(obj);
- obj=new GUIObject(column1X,8*lineHeight,columnW,36,GUIObjectCheckBox,_("Quick record"),quickrec?1:0);
+ obj=new GUICheckBox(column1X,8*lineHeight,columnW,36,_("Quick record"),quickrec?1:0);
obj->name="chkQuickRec";
obj->eventCallback=this;
tabGeneral->addChild(obj);
//Create the controls tab.
tabControls=inputMgr.showConfig(SCREEN_HEIGHT-210);
tabControls->top=140;
tabControls->visible=false;
GUIObjectRoot->addChild(tabControls);
//Save original keys.
for(int i=0;i<INPUTMGR_MAX;i++){
tmpKeys[i]=inputMgr.getKeyCode((InputManagerKeys)i,false);
tmpAlternativeKeys[i]=inputMgr.getKeyCode((InputManagerKeys)i,true);
}
//Create buttons.
- GUIObject*b1=new GUIObject(SCREEN_WIDTH*0.3,SCREEN_HEIGHT-60,-1,36,GUIObjectButton,_("Cancel"),0,true,true,GUIGravityCenter);
+ GUIObject*b1=new GUIButton(SCREEN_WIDTH*0.3,SCREEN_HEIGHT-60,-1,36,_("Cancel"),0,true,true,GUIGravityCenter);
b1->name="cmdBack";
b1->eventCallback=this;
GUIObjectRoot->addChild(b1);
- GUIObject* b2=new GUIObject(SCREEN_WIDTH*0.7,SCREEN_HEIGHT-60,-1,36,GUIObjectButton,_("Save Changes"),0,true,true,GUIGravityCenter);
+ GUIObject* b2=new GUIButton(SCREEN_WIDTH*0.7,SCREEN_HEIGHT-60,-1,36,_("Save Changes"),0,true,true,GUIGravityCenter);
b2->name="cmdSave";
b2->eventCallback=this;
GUIObjectRoot->addChild(b2);
}
static string convertInt(int i){
stringstream ss;
ss << i;
return ss.str();
}
void Options::GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType){
//Check what type of event it was.
if(eventType==GUIEventClick){
if(name=="cmdBack"){
//Reset the key changes.
for(int i=0;i<INPUTMGR_MAX;i++){
inputMgr.setKeyCode((InputManagerKeys)i,tmpKeys[i],false);
inputMgr.setKeyCode((InputManagerKeys)i,tmpAlternativeKeys[i],true);
}
//Reset the music volume.
getMusicManager()->setVolume(atoi(getSettings()->getValue("music").c_str()));
Mix_Volume(-1,atoi(getSettings()->getValue("sound").c_str()));
//And goto the main menu.
setNextState(STATE_MENU);
}else if(name=="cmdSave"){
//Save is pressed thus save
char s[64];
sprintf(s,"%d",soundSlider->value);
getSettings()->setValue("sound",s);
sprintf(s,"%d",musicSlider->value);
getSettings()->setValue("music",s);
getMusicManager()->setEnabled(musicSlider->value>0);
Mix_Volume(-1,soundSlider->value);
getSettings()->setValue("fullscreen",fullscreen?"1":"0");
getSettings()->setValue("leveltheme",leveltheme?"1":"0");
getSettings()->setValue("internet",internet?"1":"0");
getSettings()->setValue("theme",themeName);
getSettings()->setValue("fading",fade?"1":"0");
getSettings()->setValue("quickrecord",quickrec?"1":"0");
//Before loading the theme remove the previous one from the stack.
objThemes.removeTheme();
loadTheme(themeName);
if(!useProxy)
internetProxy.clear();
getSettings()->setValue("internet-proxy",internetProxy);
getSettings()->setValue("lang",langs->getName());
//Is resolution from the list or is it user defined in config file
if(resolutions->value<(int)resolutionList.size()){
getSettings()->setValue("width",convertInt(resolutionList[resolutions->value].w));
getSettings()->setValue("height",convertInt(resolutionList[resolutions->value].h));
}else{
getSettings()->setValue("width",convertInt(currentRes.w));
getSettings()->setValue("height",convertInt(currentRes.h));
}
//Save the key configuration.
inputMgr.saveConfig();
//Save the settings.
saveSettings();
//Before we return check if some .
if(restartFlag || resolutions->value!=lastRes){
//The resolution changed so we need to recreate the screen.
if(!createScreen()){
//Screen creation failed so set to safe settings.
getSettings()->setValue("fullscreen","0");
getSettings()->setValue("width",convertInt(resolutionList[lastRes].w));
getSettings()->setValue("height",convertInt(resolutionList[lastRes].h));
if(!createScreen()){
//Everything fails so quit.
setNextState(STATE_EXIT);
return;
}
}
//The screen is created, now load the (menu) theme.
if(!loadTheme("")){
//Loading the theme failed so quit.
setNextState(STATE_EXIT);
return;
}
}
if(langs->value!=lastLang){
//We set the language.
language=langs->getName();
dictionaryManager->set_language(tinygettext::Language::from_name(language));
getLevelPackManager()->updateLanguage();
//And reload the font.
if(!loadFonts()){
//Loading failed so quit.
setNextState(STATE_EXIT);
return;
}
}
//Now return to the main menu.
setNextState(STATE_MENU);
}else if(name=="chkFullscreen"){
fullscreen=obj->value?true:false;
//Check if fullscreen changed.
if(fullscreen==getSettings()->getBoolValue("fullscreen")){
//We disable the restart message flag.
restartFlag=false;
}else{
//We set the restart message flag.
restartFlag=true;
}
}else if(name=="chkLeveltheme"){
leveltheme=obj->value?true:false;
}else if(name=="chkInternet"){
internet=obj->value?true:false;
}else if(name=="chkProxy"){
useProxy=obj->value?true:false;
}else if(name=="chkFade"){
fade=obj->value?true:false;
}else if(name=="chkQuickRec"){
quickrec=obj->value?true:false;
}
}
if(name=="lstTheme"){
if(theme!=NULL && theme->value>=0 && theme->value<(int)theme->item.size()){
//Convert the themeName to contain %DATA%, etc...
themeName=compressFileName(theme->item[theme->value].first);
}
}else if(name=="txtProxy"){
internetProxy=obj->caption;
//Check if the internetProxy field is empty.
useProxy=!internetProxy.empty();
}else if(name=="sldMusic"){
getMusicManager()->setEnabled(musicSlider->value>0);
getMusicManager()->setVolume(musicSlider->value);
}else if(name=="sldSound"){
Mix_Volume(-1,soundSlider->value);
if(lastJumpSound==0){
Mix_PlayChannel(-1,jumpSound,0);
lastJumpSound=15;
}
}
if(name=="lstTabs"){
if(obj->value==0){
tabGeneral->visible=true;
tabControls->visible=false;
}else{
tabGeneral->visible=false;
tabControls->visible=true;
}
}
}
void Options::handleEvents(){
//Get the x and y location of the mouse.
int x,y;
SDL_GetMouseState(&x,&y);
//Check icon.
if(event.type==SDL_MOUSEMOTION || event.type==SDL_MOUSEBUTTONDOWN){
if(y>=SCREEN_HEIGHT-56&&y<SCREEN_HEIGHT-8&&x>=SCREEN_WIDTH-56)
clearIconHower=true;
else
clearIconHower=false;
}
if(event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT && clearIconHower){
if(msgBox(_("Do you really want to reset level progress?"),MsgBoxYesNo,_("Warning"))==MsgBoxYes){
//We delete the progress folder.
#ifdef WIN32
removeDirectory((getUserPath()+"progress").c_str());
createDirectory((getUserPath()+"progress").c_str());
#else
removeDirectory((getUserPath(USER_DATA)+"/progress").c_str());
createDirectory((getUserPath(USER_DATA)+"/progress").c_str());
#endif
//Reset statistics.
statsMgr.reloadCompletedLevelsAndAchievements();
}
}
//Check if we need to quit, if so enter the exit state.
if(event.type==SDL_QUIT){
setNextState(STATE_EXIT);
}
//Check if the escape button is pressed, if so go back to the main menu.
if(inputMgr.isKeyUpEvent(INPUTMGR_ESCAPE)){
setNextState(STATE_MENU);
}
}
void Options::logic(){
//Increase the lastJumpSound variable if needed.
if(lastJumpSound!=0){
lastJumpSound--;
}
}
void Options::render(){
//Draw background.
objThemes.getBackground(true)->draw(screen);
objThemes.getBackground(true)->updateAnimation();
//Now render the title.
applySurface((SCREEN_WIDTH-title->w)/2,40-TITLE_FONT_RAISE,title,screen,NULL);
//Check if an icon is selected/highlighted and draw tooltip
if(clearIconHower){
SDL_Color fg={0,0,0};
/// TRANSLATORS: Used for button which clear any level progress like unlocked levels and highscores.
SDL_Surface *surface=TTF_RenderUTF8_Blended(fontText,_("Clear Progress"),fg);
drawGUIBox(-2,SCREEN_HEIGHT-surface->h-2,surface->w+4,surface->h+4,screen,0xFFFFFFFF);
applySurface(0,SCREEN_HEIGHT-surface->h,surface,screen,NULL);
SDL_FreeSurface(surface);
}
//Draw icon.
applySurface(SCREEN_WIDTH-48,SCREEN_HEIGHT-48,clearIcon,screen,NULL);
//NOTE: The rendering of the GUI is done in Main.
}
void Options::resize(){
//Recreate the gui to fit the new resolution.
createGUI();
}
/////////////////////////CREDITS_MENU//////////////////////////////////
Credits::Credits(){
//Render the title.
title=TTF_RenderUTF8_Blended(fontTitle,_("Credits"),themeTextColor);
//Vector that will hold every line of the credits.
vector<string> credits;
//Open the AUTHORS file and read every line.
{
ifstream fin((getDataPath()+"/../AUTHORS").c_str());
if(!fin.is_open()) {
cerr<<"ERROR: Unable to open the AUTHORS file."<<endl;
credits.push_back("ERROR: Unable to open the AUTHORS file.");
credits.push_back("");
}
//Loop the lines of the file.
string line;
while(getline(fin,line)){
credits.push_back(line);
}
}
//Enter a new line between the two files.
credits.push_back("");
//Open the Credits.text file and read every line.
{
ifstream fin((getDataPath()+"/Credits.txt").c_str());
if(!fin.is_open()) {
cerr<<"ERROR: Unable to open the Credits.txt file."<<endl;
credits.push_back("ERROR: Unable to open the Credits.txt file.");
credits.push_back("");
}
//Loop the lines of the file.
string line;
while(getline(fin,line)){
credits.push_back(line);
//NOTE: Some sections point to other credits files.
if(line=="music/") {
vector<string> musicCredits=getMusicManager()->createCredits();
credits.insert(credits.end(),musicCredits.begin(),musicCredits.end());
}
}
}
//Create the root element of the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
- GUIObjectRoot=new GUIObject(0,0,SCREEN_WIDTH,SCREEN_HEIGHT,GUIObjectNone);
+ GUIObjectRoot=new GUIObject(0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
//Create back button.
- backButton=new GUIObject(SCREEN_WIDTH*0.5,SCREEN_HEIGHT-60,-1,36,GUIObjectButton,_("Back"),0,true,true,GUIGravityCenter);
+ backButton=new GUIButton(SCREEN_WIDTH*0.5,SCREEN_HEIGHT-60,-1,36,_("Back"),0,true,true,GUIGravityCenter);
backButton->name="cmdBack";
backButton->eventCallback=this;
GUIObjectRoot->addChild(backButton);
//Create a text area for credits.
textArea=new GUITextArea(SCREEN_WIDTH*0.05,114,SCREEN_WIDTH*0.9,SCREEN_HEIGHT-200);
textArea->setFont(fontMono);
textArea->setStringArray(credits);
textArea->editable=false;
GUIObjectRoot->addChild(textArea);
}
Credits::~Credits(){
//Delete the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Free images
SDL_FreeSurface(title);
}
void Credits::GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType){
//Check what type of event it was.
if(eventType==GUIEventClick){
if(name=="cmdBack"){
//Goto the main menu.
setNextState(STATE_MENU);
}
}
}
void Credits::handleEvents(){
//Check if we need to quit, if so enter the exit state.
if(event.type==SDL_QUIT){
setNextState(STATE_EXIT);
}
//Check if the escape button is pressed, if so go back to the main menu.
if(inputMgr.isKeyUpEvent(INPUTMGR_ESCAPE)){
setNextState(STATE_MENU);
}
}
void Credits::logic(){
}
void Credits::render(){
//Draw background.
objThemes.getBackground(true)->draw(screen);
objThemes.getBackground(true)->updateAnimation();
//Now render the title.
applySurface((SCREEN_WIDTH-title->w)/2,40-TITLE_FONT_RAISE,title,screen,NULL);
//NOTE: The rendering of the GUI is done in Main.
}
void Credits::resize(){
//Resize and position widgets.
GUIObjectRoot->width=SCREEN_WIDTH;
GUIObjectRoot->height=SCREEN_HEIGHT;
backButton->left=SCREEN_WIDTH*0.5;
backButton->top=SCREEN_HEIGHT-60;
textArea->left=SCREEN_WIDTH*0.05;
textArea->width=SCREEN_WIDTH*0.9;
textArea->height=SCREEN_HEIGHT-200;
textArea->resize();
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, May 16, 8:28 PM (1 d, 16 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
63926
Default Alt Text
(482 KB)

Event Timeline