From ca10800c79423242f1eb4bf111491f0a79366da0 Mon Sep 17 00:00:00 2001
From: Tamir Atias <engine.games@gmail.com>
Date: Thu, 19 Apr 2012 08:14:35 +0300
Subject: [PATCH] [Add] Initial work on a title screen.

---
 Bin/VC10/VC10.vcxproj         |   6 ++
 Bin/VC10/VC10.vcxproj.filters |  21 +++++++
 src/Font/Font.cpp             |  27 ++++++++-
 src/Font/Font.h               |   9 ++-
 src/Main/Game.cpp             | 111 +++++++++++++++++++++++++---------
 src/Main/Game.h               |  15 +++++
 src/Main/TitleScreen.cpp      |  61 +++++++++++++++++++
 src/Main/TitleScreen.h        |  30 +++++++++
 src/Main/main.cpp             |   5 +-
 src/UI/Button.cpp             |  58 ++++++++++++++++++
 src/UI/Button.h               |  44 ++++++++++++++
 src/UI/Menu.cpp               |  67 ++++++++++++++++++++
 src/UI/Menu.h                 |  37 ++++++++++++
 13 files changed, 451 insertions(+), 40 deletions(-)
 create mode 100644 src/Main/TitleScreen.cpp
 create mode 100644 src/Main/TitleScreen.h
 create mode 100644 src/UI/Button.cpp
 create mode 100644 src/UI/Button.h
 create mode 100644 src/UI/Menu.cpp
 create mode 100644 src/UI/Menu.h

diff --git a/Bin/VC10/VC10.vcxproj b/Bin/VC10/VC10.vcxproj
index 3336f69..6107e44 100644
--- a/Bin/VC10/VC10.vcxproj
+++ b/Bin/VC10/VC10.vcxproj
@@ -100,6 +100,7 @@
     <ClInclude Include="..\..\src\Level\MapTile.h" />
     <ClInclude Include="..\..\src\Level\Tileset.h" />
     <ClInclude Include="..\..\src\Main\Game.h" />
+    <ClInclude Include="..\..\src\Main\TitleScreen.h" />
     <ClInclude Include="..\..\src\Math\FPS.h" />
     <ClInclude Include="..\..\src\Math\MathBox.h" />
     <ClInclude Include="..\..\src\Math\Rect.h" />
@@ -128,6 +129,8 @@
     <ClInclude Include="..\..\src\TMXParser\TmxTile.h" />
     <ClInclude Include="..\..\src\TMXParser\TmxTileset.h" />
     <ClInclude Include="..\..\src\TMXParser\TmxUtil.h" />
+    <ClInclude Include="..\..\src\UI\Button.h" />
+    <ClInclude Include="..\..\src\UI\Menu.h" />
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="..\..\src\Actor\Actor.cpp" />
@@ -145,6 +148,7 @@
     <ClCompile Include="..\..\src\Level\Tileset.cpp" />
     <ClCompile Include="..\..\src\Main\Game.cpp" />
     <ClCompile Include="..\..\src\Main\main.cpp" />
+    <ClCompile Include="..\..\src\Main\TitleScreen.cpp" />
     <ClCompile Include="..\..\src\Math\FPS.cpp" />
     <ClCompile Include="..\..\src\Math\Timer.cpp" />
     <ClCompile Include="..\..\src\Math\Vec2.cpp" />
@@ -167,6 +171,8 @@
     <ClCompile Include="..\..\src\TMXParser\TmxTile.cpp" />
     <ClCompile Include="..\..\src\TMXParser\TmxTileset.cpp" />
     <ClCompile Include="..\..\src\TMXParser\TmxUtil.cpp" />
+    <ClCompile Include="..\..\src\UI\Button.cpp" />
+    <ClCompile Include="..\..\src\UI\Menu.cpp" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="tinyxml.vcxproj">
diff --git a/Bin/VC10/VC10.vcxproj.filters b/Bin/VC10/VC10.vcxproj.filters
index 52683a1..8424bb1 100644
--- a/Bin/VC10/VC10.vcxproj.filters
+++ b/Bin/VC10/VC10.vcxproj.filters
@@ -46,6 +46,9 @@
     <Filter Include="Animation">
       <UniqueIdentifier>{2826112d-5f07-4ee2-b4b4-2be9289c6811}</UniqueIdentifier>
     </Filter>
+    <Filter Include="UI">
+      <UniqueIdentifier>{da9ad984-368f-4f6f-8f68-648733ae3a44}</UniqueIdentifier>
+    </Filter>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\src\Main\Game.h">
@@ -183,6 +186,15 @@
     <ClInclude Include="..\..\src\Animation\AnimationSequence.h">
       <Filter>Animation</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\UI\Button.h">
+      <Filter>UI</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\Main\TitleScreen.h">
+      <Filter>Main</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\UI\Menu.h">
+      <Filter>UI</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="..\..\src\Main\main.cpp">
@@ -296,5 +308,14 @@
     <ClCompile Include="..\..\src\Animation\AnimatingSprite.cpp">
       <Filter>Animation</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\UI\Button.cpp">
+      <Filter>UI</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\src\Main\TitleScreen.cpp">
+      <Filter>Main</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\src\UI\Menu.cpp">
+      <Filter>UI</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>
\ No newline at end of file
diff --git a/src/Font/Font.cpp b/src/Font/Font.cpp
index f06b2f6..247ddd5 100644
--- a/src/Font/Font.cpp
+++ b/src/Font/Font.cpp
@@ -19,8 +19,8 @@ Font::~Font(void) {
   }
 }
 
-bool Font::Load(const std::string& filename) {
-  TTF_Font* font = TTF_OpenFont(filename.c_str(), 16);
+bool Font::Load(const std::string& filename, int size) {
+  TTF_Font* font = TTF_OpenFont(filename.c_str(), size);
   if(!font) {
     Debug::logger->message("Error loading %s: %s", filename.c_str(), TTF_GetError());
     return false;
@@ -94,7 +94,7 @@ bool Font::Load(const std::string& filename) {
   return true;
 }
 
-void Font::DrawText(int xOffset, int yOffset, const char* text) {
+void Font::RenderText(int xOffset, int yOffset, const char* text) {
   glEnable(GL_TEXTURE_2D);
   BindTexture(_texture);
 
@@ -159,3 +159,24 @@ void Font::DrawText(int xOffset, int yOffset, const char* text) {
 
   glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
 }
+
+void Font::TextSize(const char* text, int& width, int& height) {
+  width   = 0;
+  height  = _lineSkip;
+  
+  int textLength = strlen(text);
+  for(int i = 0; i < textLength; i++) {
+    char c = text[i];
+    if(c == '\n') {
+      height += _lineSkip;
+      continue;
+    } else if(c == ' ') {
+      width += _spaceWidth;
+      continue;
+    } else if(c == '\t') {
+      width += _tabWidth;
+      continue;
+    }
+    width += _characters[(int)c].advance;
+  }
+}
diff --git a/src/Font/Font.h b/src/Font/Font.h
index 28356f6..5c85738 100644
--- a/src/Font/Font.h
+++ b/src/Font/Font.h
@@ -16,16 +16,15 @@ struct FontChar {
   int advance;
 };
 
-class Font : public Resource {
-  template<class T> friend class ResourceManager;
-
+class Font {
 public:
   Font(void);
   ~Font(void);
 
-  bool Load(const std::string& filename);
+  bool Load(const std::string& filename, int size);
 
-  void DrawText(int xOffset, int yOffset, const char* text);
+  void RenderText(int xOffset, int yOffset, const char* text);
+  void TextSize(const char* text, int& width, int& height);
 
   int     GetLineSkip() const { return _lineSkip; }
   float*  GetColor() { return _color; }
diff --git a/src/Main/Game.cpp b/src/Main/Game.cpp
index 36a032e..80e95a5 100644
--- a/src/Main/Game.cpp
+++ b/src/Main/Game.cpp
@@ -12,8 +12,10 @@
 #include "../System/Debug.h"
 #include "../Sprite/Sprite.h"
 #include "../Texture/Texture.h"
+
 #include "../Level/Level.h"
 #include "Game.h"
+#include "TitleScreen.h"
 
 Game::Game(void) {
   _level  = new Level();
@@ -23,8 +25,13 @@ Game::Game(void) {
   _NPC->SetXY(30.0f, 30.0f);
 
   _testFont = new Font();
-  _testFont->Load("../Data/Font/Fairydust.ttf");
+  _testFont->Load("../Data/Font/Fairydust.ttf", 24);
   _testFont->SetColor(0.0f, 1.0f, 1.0f, 1.0f);
+
+  _titleScreen = new TitleScreen();
+  _inTitleScreen = true;
+
+  _running = true;
 }
 
 Game::~Game(void) {
@@ -56,7 +63,80 @@ void Game::Prepare(float dt) {
 
 void Game::Render(void) {
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+  if(_inTitleScreen) {
+    RenderTitle();
+  } else {
+    RenderGame();
+  }
+}
 
+void Game::Shutdown(void) {
+  Debug::logger->message("\n ----- Cleaning Engine -----");
+  delete _testFont;
+  delete _NPC;
+  delete _player;
+  delete _level;
+  delete _titleScreen;
+}
+
+void Game::ProcessEvents(float dt) {
+  if(_inTitleScreen) {
+    UpdateTitle(dt);
+  } else {
+    UpdateGame(dt);
+  }
+}
+
+void Game::OnResize(int width, int height) {
+  glViewport(0, 0, width, height);
+
+  windowWidth = width;
+  windowHeight = height;
+
+  glMatrixMode(GL_PROJECTION);
+  glLoadIdentity();
+  glOrtho(0.0, (GLdouble)windowWidth, (GLdouble)windowHeight, 0.0, 0.0, 1.0);
+
+  glMatrixMode(GL_MODELVIEW);
+  glLoadIdentity();
+}
+
+void Game::UpdateTitle(float dt) {
+  _titleScreen->Update(dt);
+
+  if(!_titleScreen->IsAlive()) {
+    switch(_titleScreen->GetResult()) {
+    case TitleScreen::QUIT:
+      _running = false;
+      break;
+
+    case TitleScreen::NEW_GAME:
+      _inTitleScreen = false;
+      break;
+    }
+  }
+}
+
+void Game::UpdateGame(float dt) {
+  _player->Update(dt);
+  _NPC->Update(dt);
+  _level->Update(dt);
+}
+
+void Game::RenderTitle(void) {
+  glMatrixMode(GL_MODELVIEW);
+  glLoadIdentity();
+
+  glDisable(GL_DEPTH_TEST);
+  glDisable(GL_ALPHA_TEST);
+
+  glEnable(GL_BLEND);
+  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+  _titleScreen->Render();
+}
+
+void Game::RenderGame(void) {
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
 
@@ -80,35 +160,8 @@ void Game::Render(void) {
   _level->Draw(xOffset, yOffset);
   _player->Render();
   _NPC->Render();
-  _testFont->DrawText(
+  _testFont->RenderText(
     _player->GetX() - 5,
     _player->GetY() - _testFont->GetLineSkip() - 2,
     "Miss D");
 }
-
-void Game::Shutdown(void) {
-  Debug::logger->message("\n ----- Cleaning Engine -----");
-  delete _testFont;
-  delete _NPC;
-  delete _player;
-  delete _level;
-}
-
-void Game::ProcessEvents(float dt) {
-  _player->Update(dt);
-  _NPC->Update(dt);
-}
-
-void Game::OnResize(int width, int height) {
-  glViewport(0, 0, width, height);
-
-  windowWidth = width;
-  windowHeight = height;
-
-  glMatrixMode(GL_PROJECTION);
-  glLoadIdentity();
-  glOrtho(0.0, (GLdouble)windowWidth, (GLdouble)windowHeight, 0.0, 0.0, 1.0);
-
-  glMatrixMode(GL_MODELVIEW);
-  glLoadIdentity();
-}
diff --git a/src/Main/Game.h b/src/Main/Game.h
index d80066b..254b83b 100644
--- a/src/Main/Game.h
+++ b/src/Main/Game.h
@@ -6,6 +6,8 @@
 
 class Sprite;
 class Level;
+class Button;
+class TitleScreen;
 
 class Game {
 public:
@@ -21,9 +23,22 @@ public:
 
   void OnResize(int width, int height);
 
+  bool IsRunning()              { return _running; }
+  void SetRunning(bool running) { _running = running; }
+
 private:
+  void UpdateTitle(float dt);
+  void UpdateGame(float dt);
+  void RenderTitle(void);
+  void RenderGame(void);
+
   Font*   _testFont;
   Player* _player;
   NPC*    _NPC;
   Level*  _level;
+
+  TitleScreen* _titleScreen;
+  bool         _inTitleScreen;
+
+  bool _running;
 };
diff --git a/src/Main/TitleScreen.cpp b/src/Main/TitleScreen.cpp
new file mode 100644
index 0000000..b67108d
--- /dev/null
+++ b/src/Main/TitleScreen.cpp
@@ -0,0 +1,61 @@
+#include "TitleScreen.h"
+#include "../Font/Font.h"
+#include "../UI/Button.h"
+#include "../Global/Globals.h"
+
+TitleScreen::TitleScreen(void) {
+  _alive = true;
+  _result = TitleScreen::QUIT;
+
+  _font = new Font();
+  _font->Load("../Data/Font/fairydust.ttf", 24);
+
+  Button* newGameButton = new Button();
+  Button* loadGameButton = new Button();
+  Button* quitButton = new Button();
+
+  newGameButton->SetFont(_font);
+  loadGameButton->SetFont(_font);
+  quitButton->SetFont(_font);
+
+  newGameButton->SetText("New Game");
+  loadGameButton->SetText("Load Game");
+  quitButton->SetText("Quit");
+
+  _menu.AddButton(newGameButton);
+  _menu.AddButton(loadGameButton);
+  _menu.AddButton(quitButton);
+  _menu.AlignButtons(Menu::ALIGN_VERTICALLY);
+  _menu.SetXY(32, windowHeight - 128);
+}
+
+TitleScreen::~TitleScreen(void) {
+  if(_font) {
+    delete _font;
+    _font = NULL;
+  }
+}
+
+void TitleScreen::Update(float dt) {
+  _menu.Update();
+  switch(_menu.GetTriggeredButton()) {
+  case 0:
+    _alive = false;
+    _result = TitleScreen::NEW_GAME;
+    break;
+
+  case 1:
+    _alive = false;
+    _result = TitleScreen::LOAD_GAME;
+    break;
+
+  case 2:
+    _alive = false;
+    _result = TitleScreen::QUIT;
+    break;
+  }
+}
+
+void TitleScreen::Render(void) {
+  _menu.Render();
+}
diff --git a/src/Main/TitleScreen.h b/src/Main/TitleScreen.h
new file mode 100644
index 0000000..7555bc9
--- /dev/null
+++ b/src/Main/TitleScreen.h
@@ -0,0 +1,30 @@
+#pragma once
+
+#include "../UI/Menu.h"
+
+class Font;
+
+class TitleScreen {
+public:
+  enum {
+    NEW_GAME,
+    LOAD_GAME,
+    QUIT
+  };
+
+  TitleScreen(void);
+  ~TitleScreen(void);
+  
+  void Update(float dt);
+  void Render(void);
+
+  bool  IsAlive(void)   { return _alive; }
+  int   GetResult(void) { return _result; }
+
+private:
+  bool  _alive;
+  int   _result;
+
+  Font* _font;
+  Menu  _menu;
+};
\ No newline at end of file
diff --git a/src/Main/main.cpp b/src/Main/main.cpp
index 19ac5d3..3a01324 100644
--- a/src/Main/main.cpp
+++ b/src/Main/main.cpp
@@ -99,12 +99,11 @@ int main(int argc, char** argv) {
   // screws up for me. -- Allanis.
   game.OnResize(windowWidth, windowHeight);
 
-  bool isRunning = true;
-  while(isRunning) {
+  while(game.IsRunning()) {
 
     while(SDL_PollEvent(&event)) {
       if((event.type == SDL_QUIT) || KeyStillDown(SDLK_ESCAPE)) {
-        isRunning = false;
+        game.SetRunning(false);
         break;
       }
       if(event.type == SDL_VIDEORESIZE) {
diff --git a/src/UI/Button.cpp b/src/UI/Button.cpp
new file mode 100644
index 0000000..34f160c
--- /dev/null
+++ b/src/UI/Button.cpp
@@ -0,0 +1,58 @@
+#include "Button.h"
+#include "../Font/Font.h"
+#include "../IO/Input.h"
+
+Button::Button(void) {
+  _text = "";
+  _font = NULL;
+  _highlighted = false;
+  _triggered = false;
+  x = 0;
+  y = 0;
+  w = 0;
+  h = 0;
+}
+
+void Button::Update(void) {
+  _triggered = false;
+  
+  int mouseX = ::GetX();
+  int mouseY = ::GetY();
+
+  if((mouseX >= x) && (mouseX < (x + w)) &&
+     (mouseY >= y) && (mouseY < (y + h)))
+  {
+    _highlighted = true;
+    if(MouseUp(SDL_BUTTON(1))) {
+      _triggered = true;
+    }
+  } else {
+    _highlighted = false;
+  }
+}
+
+void Button::Render(void) {
+  if(_font) {
+    if(_highlighted) {
+      _font->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
+    } else {
+      _font->SetColor(0.5f, 0.5f, 0.5f, 1.0f);
+    }
+    _font->RenderText(x, y, _text.GetPointer());
+    _font->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
+  }
+}
+
+void Button::SetFont(Font* font) {
+  _font = font;
+  if(_text.Length() > 0) {
+    font->TextSize(_text.GetPointer(), w, h);
+  }
+}
+
+void Button::SetText(const String& text) {
+  _text = text;
+  if((_text.Length() > 0) && _font) {
+    _font->TextSize(text.GetPointer(), w, h);
+  }
+}
diff --git a/src/UI/Button.h b/src/UI/Button.h
new file mode 100644
index 0000000..31b7deb
--- /dev/null
+++ b/src/UI/Button.h
@@ -0,0 +1,44 @@
+#pragma once
+
+#include "../System/String.h"
+#include "../Font/Font.h"
+
+class Button {
+public:
+  Button(void);
+
+  void Update(void);
+  void Render(void);
+
+  Font*   GetFont(void) { return _font; }
+  void    SetFont(Font* font);
+  
+  const String& GetText(void) const { return _text;}
+  void          SetText(const String& text);
+  
+  bool IsHighlighted(void) const        { return _highlighted; }
+  void SetHighlighted(bool highlighted) { _highlighted = highlighted; }
+
+  bool Triggered(void) const { return _triggered; }
+
+  int   GetX(void) const    { return x; }
+  int   GetY(void) const    { return y; }
+  void  SetX(int x)         { this->x = x; }
+  void  SetY(int y)         { this->y = y; }
+  void  SetXY(int x, int y) { SetX(x); SetY(y); }
+
+  int GetWidth(void) const { return w; }
+  int GetHeight(void) const { return h; }
+
+private:
+  Font*   _font;
+  String  _text;
+ 
+  bool _highlighted;
+  bool _triggered;
+
+  int x;
+  int y;
+  int w;
+  int h;
+};
diff --git a/src/UI/Menu.cpp b/src/UI/Menu.cpp
new file mode 100644
index 0000000..58baf71
--- /dev/null
+++ b/src/UI/Menu.cpp
@@ -0,0 +1,67 @@
+#include "Menu.h"
+#include "Button.h"
+
+Menu::Menu(void) {
+  _triggeredButton = -1;
+  x = 0;
+  y = 0;
+}
+
+Menu::~Menu(void) {
+  for(std::list<Button*>::iterator i = _buttons.begin(); i != _buttons.end(); ++i) {
+    delete (*i);
+  }
+  _buttons.clear();
+}
+
+void Menu::AddButton(Button* button) {
+  _buttons.push_back(button);
+}
+
+void Menu::AlignButtons(int how) {
+  int x = 0;
+  int y = 0;
+  for(std::list<Button*>::iterator i = _buttons.begin(); i != _buttons.end(); ++i) {   
+    Button* button = (*i);
+    button->SetXY(x, y);
+
+    if(how == Menu::ALIGN_HORIZONTALLY) {
+      x += button->GetWidth() + 24;
+    } else if(how == Menu::ALIGN_VERTICALLY) {
+      y += button->GetHeight() + 2;
+    }
+  }
+}
+
+void Menu::Update(void) {
+  _triggeredButton = -1;
+
+  int index = 0;
+  for(std::list<Button*>::iterator i = _buttons.begin(); i != _buttons.end(); ++i) {
+    Button* button = (*i);
+    
+    int oldX = button->GetX();
+    int oldY = button->GetY();
+    button->SetXY(oldX + x, oldY + y);
+    button->Update();
+    button->SetXY(oldX, oldY);
+
+    if(button->Triggered()) {
+      _triggeredButton = index;
+    }
+
+    index++;
+  }
+}
+
+void Menu::Render(void) {
+  for(std::list<Button*>::iterator i = _buttons.begin(); i != _buttons.end(); ++i) {
+    Button* button = (*i);
+    
+    int oldX = button->GetX();
+    int oldY = button->GetY();
+    button->SetXY(oldX + x, oldY + y);
+    button->Render();
+    button->SetXY(oldX, oldY);
+  }
+}
diff --git a/src/UI/Menu.h b/src/UI/Menu.h
new file mode 100644
index 0000000..c403847
--- /dev/null
+++ b/src/UI/Menu.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include <list>
+
+class Button;
+
+class Menu {
+public:
+  enum {
+    ALIGN_HORIZONTALLY,
+    ALIGN_VERTICALLY
+  };
+
+  Menu(void);
+  ~Menu(void);
+
+  void AddButton(Button* button);
+  void AlignButtons(int how);
+
+  void Update(void);
+  void Render(void);
+
+  int GetTriggeredButton() const { return _triggeredButton; }
+
+  int   GetX(void) const    { return x; }
+  int   GetY(void) const    { return y; }
+  void  SetX(int x)         { this->x = x; }
+  void  SetY(int y)         { this->y = y; }
+  void  SetXY(int x, int y) { SetX(x); SetY(y); }
+
+private:
+  std::list<Button*> _buttons;
+  int _triggeredButton;
+  
+  int x;
+  int y;
+};
\ No newline at end of file