diff --git a/asteroids/AsteroidsEvents.h b/asteroids/AsteroidsEvents.h index 3c8e177..8fdd373 100644 --- a/asteroids/AsteroidsEvents.h +++ b/asteroids/AsteroidsEvents.h @@ -10,6 +10,7 @@ BEGIN_ENUM(Event) DECL_ENUM_ELEMENT(EventAccelerateStart), DECL_ENUM_ELEMENT(EventAccelerateStop), DECL_ENUM_ELEMENT(EventLevelComplete), + DECL_ENUM_ELEMENT(EventChangeGameState), DECL_ENUM_ELEMENT(EventGameOver), DECL_ENUM_ELEMENT(EventShipExplode) } diff --git a/asteroids/Controller.cc b/asteroids/Controller.cc index 69c3de9..6b35b42 100644 --- a/asteroids/Controller.cc +++ b/asteroids/Controller.cc @@ -1,4 +1,5 @@ #include "Controller.h" +#include "AsteroidsEvents.h" namespace asteroids { @@ -16,7 +17,16 @@ int Controller::OnInit (int argc, char *argv[]) { mBindings[SDLK_F8] = "toggleconsole"; mBindings[SDLK_F9] = "set playerspeed 5.0"; + Engine::RegisterListener (this, EventChangeGameState); + return 0; } +bool Controller::OnReceiveEvent (const Engine::EventBasePtr &event) { + if (event->mEventType == EventChangeGameState) { + IMGUIClear(); + } + return false; +} + } diff --git a/asteroids/Controller.h b/asteroids/Controller.h index d44c879..119a3e4 100644 --- a/asteroids/Controller.h +++ b/asteroids/Controller.h @@ -26,6 +26,7 @@ class Controller : public Engine::ControllerBase { virtual int OnInit (int argc, char* argv[]); /** \brief Registers the commands of the cnotroller */ virtual void OnRegisterCommands (); + virtual bool OnReceiveEvent (const Engine::EventBasePtr &event); }; } diff --git a/asteroids/Model.cc b/asteroids/Model.cc index e6d9ed4..ba7f841 100644 --- a/asteroids/Model.cc +++ b/asteroids/Model.cc @@ -359,6 +359,15 @@ int Model::DoSaveLevel (const char* filename) { level_file.close(); return 0; } +void Model::SetGameState (const unsigned int &state) { + mLastGameState = mGameState; + + Engine::EventBasePtr changegamestate_event (new Engine::EventBase()); + changegamestate_event->mEventType = EventChangeGameState; + QueueEvent (changegamestate_event); + + mGameState = state; +} bool Model::OnLevelComplete() { Engine::LogMessage ("Level complete!"); diff --git a/asteroids/Model.h b/asteroids/Model.h index 1762332..b77a557 100644 --- a/asteroids/Model.h +++ b/asteroids/Model.h @@ -17,10 +17,15 @@ class Model : public Engine::ModelBase { /** \brief Resets values from a previous game */ void OnNewGame (); + virtual void SetGameState (const unsigned int &state); + int GetPlayerLives () { return mPlayerLives; }; unsigned int GetPoints () { return mPoints; }; std::string GetPlayerName() { return mPlayerName; }; - void SetPlayerName(const std::string &name) { mPlayerName = name; }; + void SetPlayerName(const std::string &name) { + Engine::LogDebug("new player name: %s", name.c_str()); + mPlayerName = name; + }; float GetWorldWidth (); float GetWorldHeight (); diff --git a/asteroids/UserInterface.cc b/asteroids/UserInterface.cc index 13ef928..bf882a5 100644 --- a/asteroids/UserInterface.cc +++ b/asteroids/UserInterface.cc @@ -33,19 +33,9 @@ void MainMenuOverlay::Init () { } bool MainMenuOverlay::OnKeyDown (const SDL_keysym &keysym) { - switch (keysym.sym) { - case SDLK_ESCAPE: - Engine::RunCommand ("quit"); - return true; - case SDLK_RETURN: - GetModel()->SetGameState(GameStateEnterPlayername); - return true; - case SDLK_h: - GetModel()->SetGameState(GameStateShowHighscore); - return true; - default: - return true; - } + if (keysym.sym == SDLK_ESCAPE) + Engine::RunCommand("quit"); + return false; } void MainMenuOverlay::Draw () { @@ -70,16 +60,16 @@ void MainMenuOverlay::Draw () { glPushMatrix (); glLoadIdentity (); -// Engine::DrawGLString ( right * 0.5 - 100, 100, "A s t e r o i d s"); - Engine::GUI::Label (4, "A s t e r o i d s", right * 0.5 - 100, 180); - if (Engine::GUI::Button (1, "Start Game", right * 0.5 - 100, 200, 250, 40)) { + if (Engine::GUI::Button (1, "New Game", right * 0.5 - 100, 200, 250, 40)) { GetModel()->SetGameState(GameStateEnterPlayername); + GetController()->uistate.hotitem = 0; } if (Engine::GUI::Button (2, "Highscores", right * 0.5 - 100, 250, 250, 40)) { GetModel()->SetGameState(GameStateShowHighscore); + GetController()->uistate.hotitem = 0; } if (Engine::GUI::Button (3, "Quit", right * 0.5 - 100, 330, 250, 40)) { @@ -88,13 +78,6 @@ void MainMenuOverlay::Draw () { GetView()->SelectFont("console.ttf"); -/* - // then we do the drawings - Engine::DrawGLString ( right * 0.5 - 100, bottom * 0.5 - 8 - 32, "Main Menu"); - Engine::DrawGLString ( right * 0.5 - 80, bottom * 0.5 - 8 - 16, "[Return] - Start Game"); - Engine::DrawGLString ( right * 0.5 - 80, bottom * 0.5 - 8, "[h] - Show Highscore"); - Engine::DrawGLString ( right * 0.5 - 80, bottom * 0.5 + 8, "[Escape] - Quit"); -*/ glPopMatrix (); glMatrixMode (GL_PROJECTION); @@ -553,39 +536,13 @@ void EnterPlayernameOverlay::Init () { } bool EnterPlayernameOverlay::OnKeyDown (const SDL_keysym &keysym) { - GetController()->EnableTextinput(true); - - switch (keysym.sym) { - case SDLK_ESCAPE: - GetModel()->SetGameState(GameStateMainMenu); - return true; - case SDLK_BACKSPACE: - if (mPlayerNameInput.size() > 0) - mPlayerNameInput = mPlayerNameInput.substr (0, mPlayerNameInput.size() - 1 ); - return true; - break; - case SDLK_RETURN: - // If we just entered a new entry we simply show the highscore table, - // otherwise we switch back to the main menu - GetModel()->SetPlayerName(mPlayerNameInput); - GetController()->EnableTextinput(false); - GetModel()->SetGameState(GameStateRunning); - return true; - default: - break; + if (keysym.sym == SDLK_ESCAPE) { + GetModel()->SetGameState(GameStateMainMenu); + GetController()->uistate.hotitem = 0; + return true; } - if (keysym.unicode) { - if ((keysym.unicode & 0xFF80) == 0) { - mPlayerNameInput += keysym.unicode & 0x7F; - return true; - } else { - Engine::LogWarning ("Input key not supported!"); - return false; - } - } - - return true; + return false; } void EnterPlayernameOverlay::Draw () { @@ -611,21 +568,28 @@ void EnterPlayernameOverlay::Draw () { glLoadIdentity (); GetView()->SelectFont("console.ttf"); - float x = right * 0.5 - 100; - float y = bottom * 0.5 - 8 - 128; - // then we do the drawings - Engine::DrawGLString ( x, y, "A s t e r o i d s"); - y += 30; - Engine::DrawGLString ( x, y, "Enter your name: "); + Engine::GUI::Label (4, "A s t e r o i d s", right * 0.5 - 100, 180); - std::string name_output (mPlayerNameInput); - if (SDL_GetTicks() >> 9 & 1) - name_output += "_"; + Engine::GUI::Label (1, "Enter your name: ", right * 0.5 - 100, 250); + + // Make sure we have UNICODE processing enabled! + GetController()->EnableTextinput(true); + + std::string player_name = GetModel()->GetPlayerName(); + if (Engine::GUI::LineEdit (2, right * 0.5 + 20, 238, player_name, 16)) { + GetModel()->SetPlayerName(player_name); + } + + if (Engine::GUI::Button (3, "Start Game", right - 150 - 20, 500, 150, 40)) { + GetModel()->SetGameState(GameStateRunning); + } + + if (Engine::GUI::Button (5, "Back", 20, 500, 150, 40)) { + GetModel()->SetGameState(GameStateMainMenu); + GetController()->uistate.hotitem = 0; + } - Engine::DrawGLString ( x + 15*8, y, name_output.c_str()); - Engine::DrawGLString ( x + 16, y + 16, "Press [Return] to continue."); - glPopMatrix (); glMatrixMode (GL_PROJECTION); diff --git a/engine/ControllerBase.cc b/engine/ControllerBase.cc index c4a73b4..9741fc1 100644 --- a/engine/ControllerBase.cc +++ b/engine/ControllerBase.cc @@ -57,6 +57,12 @@ int ControllerBase::OnInit (int argc, char* argv[]) { uistate.activeitem = 0; uistate.hotitem = 0; + uistate.kbditem = 0; + uistate.last_keysym = SDLK_CLEAR; + uistate.last_unicode = 0; + + uistate.lastwidget = 0; + ControllerInstance = this; return 0; @@ -158,6 +164,12 @@ void ControllerBase::IMGUIFinish () { /** \brief Keyboard processing */ bool ControllerBase::OnKeyDown (const SDL_keysym &keysym) { mButtonStates.set(keysym.sym, true); + uistate.last_keysym = keysym.sym; + + // Only when Unicode processing is activated store the unicode value + if (SDL_EnableUNICODE(-1)) { + uistate.last_unicode = keysym.unicode; + } if (mView->mOverlayManager.SendKeyDown (keysym)) return true; diff --git a/engine/ControllerBase.h b/engine/ControllerBase.h index 8aa9ed4..fa60d59 100644 --- a/engine/ControllerBase.h +++ b/engine/ControllerBase.h @@ -16,6 +16,12 @@ class Module; struct UIState { int hotitem; int activeitem; + + int kbditem; + SDLKey last_keysym; + Uint16 last_unicode; + + int lastwidget; }; /** \brief Defines the number of keys (keyboard + mous) that we can bind to. @@ -67,6 +73,11 @@ class ControllerBase : public Module { void IMGUIPrepare () { uistate.hotitem = 0; } + void IMGUIClear () { + LogMessage ("Called IMGUIClear()"); + uistate.hotitem = 0; + uistate.kbditem = 0; + } void IMGUIFinish (); UIState uistate; diff --git a/engine/IMGUIControls.cc b/engine/IMGUIControls.cc index f269005..41c589c 100644 --- a/engine/IMGUIControls.cc +++ b/engine/IMGUIControls.cc @@ -30,6 +30,15 @@ bool regionhit (int x, int y, int w, int h) { return true; } +/** \brief Draws a label with a given caption at the position (vertically centered) + * + * This function draws the label at the horizontal x position and centers the + * label with regard to the height of the rendered caption. + * + * \TODO [med] The vertical alignment around the center of the vertical height + * of the string is rather unfortunate as different contents will be rendered + * at different vertical positions (e.g. "aaa" and "ggg") + */ void Label (int id, const char* caption, int x, int y) { if (caption != NULL) { float width, height; @@ -43,6 +52,9 @@ void Label (int id, const char* caption, int x, int y) { } /** \brief Draws a button at the given position + * + * The area defined by the parameters defines the clickable area. However + * decorations might be drawn on a smaller or bigger area. * * \returns true if it was clicked * @@ -51,6 +63,8 @@ bool Button (int id, const char* caption, int x, int y, int w, int h) { controller = EngineGetController(); assert (controller); +// LogMessage ("id = %d hotitem = %d activeitem = %d kbditem = %d", id, controller->uistate.hotitem, controller->uistate.activeitem, controller->uistate.kbditem); + // Check for hotness if (regionhit (x, y, w, h)) { controller->uistate.hotitem = id; @@ -59,11 +73,22 @@ bool Button (int id, const char* caption, int x, int y, int w, int h) { controller->uistate.activeitem = id; } + // If nothing is selected + if (controller->uistate.hotitem != 0) { + controller->uistate.kbditem = controller->uistate.hotitem; + } + + if (controller->uistate.kbditem == 0) { + controller->uistate.hotitem = id; + controller->uistate.kbditem = id; + } + // Render glColor3f (0.2, 0.2, 0.2); DrawRect2D (x + 4, y + 4, w, h); - if (controller->uistate.hotitem == id) { + if (controller->uistate.hotitem == id + || controller->uistate.kbditem == id) { if (controller->uistate.activeitem == id) { glColor3f (0.8, 0.8, 0.8); DrawRect2D (x, y, w, h); @@ -87,11 +112,179 @@ bool Button (int id, const char* caption, int x, int y, int w, int h) { view->DrawGLString(x + w * 0.5 - width * 0.5, y + h * 0.5 - height * 0.5, caption); } - // Logic + // Mouse Logic if (controller->GetButtonState(MouseButtonLeft) == false && controller->uistate.hotitem == id - && controller->uistate.activeitem == id) + && controller->uistate.activeitem == id) { + controller->uistate.lastwidget = id; + return true; + } + + // Keyboard Logic + if (controller->uistate.kbditem == id) { + // We have to make sure, that we always clear the uistate.last_keysym + // value, otherwise the same action might be repeated over and over. + switch (controller->uistate.last_keysym) { + case SDLK_DOWN: + controller->uistate.kbditem = 0; + controller->uistate.hotitem = 0; + controller->uistate.last_keysym = SDLK_CLEAR; + break; + case SDLK_UP: + controller->uistate.kbditem = controller->uistate.lastwidget; + controller->uistate.hotitem = controller->uistate.lastwidget; + controller->uistate.last_keysym = SDLK_CLEAR; + break; + case SDLK_RETURN: + controller->uistate.last_keysym = SDLK_CLEAR; + // As we (probably) exit the current set of widgets, we have to clear + // the uistate.kbditem value. + controller->uistate.kbditem = 0; + return true; + break; + } + } + + controller->uistate.lastwidget = id; + + return false; +} + +/** \brief An Edit widget that allows editing of a string consisting of one line + * + * \TODO [med] vertical alignment (especially of text) is a bit whacky + * + * \returns true if it was clicked + * + */ +bool LineEdit (int id, int x, int y, std::string &text_value, const int &maxlength) { + controller = EngineGetController(); + assert (controller); + + int w = maxlength * 16; + int h = 16; + + // LogMessage ("id = %d hotitem = %d activeitem = %d kbditem = %d", id, controller->uistate.hotitem, controller->uistate.activeitem, controller->uistate.kbditem); + + // Check for hotness + if (regionhit (x, y, w, h)) { + controller->uistate.hotitem = id; + if (controller->uistate.activeitem == 0 + && controller->GetButtonState(MouseButtonLeft)) + controller->uistate.activeitem = id; + } + + // If nothing is selected + if (controller->uistate.hotitem != 0) { + controller->uistate.kbditem = controller->uistate.hotitem; + } + + if (controller->uistate.kbditem == 0) { + controller->uistate.hotitem = id; + controller->uistate.kbditem = id; + } + + // Render + glColor3f (0.2, 0.2, 0.2); + DrawRect2D (x + 4, y + 4, w, h); + + if (controller->uistate.hotitem == id + || controller->uistate.kbditem == id) { + if (controller->uistate.activeitem == id) { + glColor3f (0.8, 0.8, 0.8); + DrawRect2D (x, y, w, h); + } else { + glColor3f (0.7, 0.7, 0.7); + DrawRect2D (x, y, w, h); + } + } else { + glColor3f (0.4, 0.4, 0.4); + DrawRect2D (x, y, w, h); + } + + // Rendering of the current value + float width, height; + view = EngineGetView (); + assert (view); + + glColor3f (1., 1., 1.); + + std::string text_output = text_value; + + if (controller->uistate.kbditem == id && SDL_GetTicks() >> 9 & 1) + text_output += "_"; + + view->DrawGLStringMeasure(text_value.c_str(), &width, &height); + view->DrawGLString(x + 16, y + 12, text_output.c_str()); + + // Keyboard Logic + if (controller->uistate.kbditem == id) { + switch (controller->uistate.last_keysym) { + case SDLK_DOWN: + controller->uistate.kbditem = 0; + controller->uistate.hotitem = 0; + controller->uistate.last_keysym = SDLK_CLEAR; + break; + case SDLK_UP: + controller->uistate.kbditem = controller->uistate.lastwidget; + controller->uistate.hotitem = controller->uistate.lastwidget; + controller->uistate.last_keysym = SDLK_CLEAR; + break; + case SDLK_CLEAR: + controller->uistate.last_keysym = SDLK_CLEAR; + controller->uistate.last_unicode = 0; + return false; + break; + case SDLK_ESCAPE: + controller->uistate.last_keysym = SDLK_CLEAR; + controller->uistate.last_unicode = 0; + controller->uistate.hotitem = 0; + controller->uistate.kbditem = 0; + return false; + break; + case SDLK_RETURN: + controller->uistate.last_keysym = SDLK_CLEAR; + controller->uistate.last_unicode = 0; + controller->uistate.hotitem = 0; + controller->uistate.kbditem = 0; + return true; + break; + case SDLK_BACKSPACE: + if (text_value.size() > 0) { + text_value = text_value.substr(0, text_value.size() - 1); + controller->uistate.last_keysym = SDLK_CLEAR; + controller->uistate.last_unicode = 0; + return true; + } + break; + default: + // The raw input processing + if (maxlength > 0 && text_value.size() < maxlength) { + if (controller->uistate.last_unicode) { + if ((controller->uistate.last_unicode & 0xFF80) == 0) { + text_value += controller->uistate.last_unicode & 0x7F; + controller->uistate.last_unicode = 0; + return true; + } else { + LogWarning ("Input key not supported!"); + return false; + } + } + } + break; + + } + } + + controller->uistate.lastwidget = id; + + // Mouse Logic + if (controller->GetButtonState(MouseButtonLeft) == false + && controller->uistate.hotitem == id + && controller->uistate.activeitem == id) { + controller->uistate.kbditem = id; + } return false; } diff --git a/engine/IMGUIControls.h b/engine/IMGUIControls.h index e23c81a..78177a6 100644 --- a/engine/IMGUIControls.h +++ b/engine/IMGUIControls.h @@ -1,6 +1,8 @@ #ifndef _IMGUICONTROLS_H #define _IMGUICONTROLS_H +#include + namespace Engine { namespace GUI { @@ -20,6 +22,8 @@ void Label (int id, const char* caption, int x, int y); */ bool Button (int id, const char* caption, int x, int y, int w, int h); +bool LineEdit (int id, int x, int y, std::string &text_value, const int &maxlength); + }; }; diff --git a/engine/ModelBase.h b/engine/ModelBase.h index 4aac708..538e339 100644 --- a/engine/ModelBase.h +++ b/engine/ModelBase.h @@ -73,7 +73,7 @@ class ModelBase : public Module { void SendEntityCollisionEvent (const unsigned int reference_entity_id, const unsigned int incidence_entity_id, float collision_time, vector3d normal); - void SetGameState (const unsigned int &state) { + virtual void SetGameState (const unsigned int &state) { mLastGameState = mGameState; mGameState = state; }; diff --git a/engine/ViewBase.cc b/engine/ViewBase.cc index 476037e..c399dd5 100644 --- a/engine/ViewBase.cc +++ b/engine/ViewBase.cc @@ -146,7 +146,7 @@ void ViewBase::UpdateCamera () { mCamera->Update (); } -void ViewBase::Draw () { +void ViewBase::PreDraw() { // Clear the screen glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); @@ -166,7 +166,13 @@ void ViewBase::Draw () { last_fps_update = 0; frame_counter = 0; } - +} + +void ViewBase::Draw () { + // Perform pre-Draw actions + PreDraw (); + + // Actual Drawing UpdateCamera (); if (mDrawGrid) @@ -179,7 +185,11 @@ void ViewBase::Draw () { mOverlayManager.Draw(); - // and update the screen + // Perform post-Draw actions + PostDraw(); +} + +void ViewBase::PostDraw() { SDL_GL_SwapBuffers (); } diff --git a/engine/ViewBase.h b/engine/ViewBase.h index 652e9a9..9b0ef64 100644 --- a/engine/ViewBase.h +++ b/engine/ViewBase.h @@ -27,6 +27,11 @@ class ViewBase : public Module{ /** \brief Resizes the View */ void Resize (int width, int height); + /** \brief Performs actions before drawing (e.g. timer stuff) */ + void PreDraw(); + /** \brief Performs actions after drawing (e.g. swapping of buffers, etc.) */ + void PostDraw(); + /** \brief Performs all drawing */ virtual void Draw ();