Implemented some basic UI

This commit is contained in:
Tiberiu Chibici 2016-11-28 00:17:20 +02:00
parent 8173937797
commit 70f79935da
21 changed files with 846 additions and 32 deletions

View File

@ -10,10 +10,10 @@
#include <model/Level.h>
#include <model/Player.h>
#include <model/GameConfig.h>
#include <graphics/SdlRenderer.h>
#include <graphics/GameRenderer.h>
#include <graphics/GuiRenderer.h>
#include <model/Configuration.h>
#include <resources/ResourceManager.h>
#include <memory>
@ -51,7 +51,7 @@ namespace farmlands {
GuiState gui;
// Settings
model::GameConfig gameConfig;
model::Configuration config;
// Current game
ViewportState viewport;

View File

@ -62,6 +62,7 @@ void FarmlandsGame::onRender()
m_gameState.sdlRenderer.renderBegin();
m_gameState.gameRenderer.render();
m_gameState.guiRenderer.render();
m_guiController.render();
m_gameState.sdlRenderer.renderEnd();
}

View File

@ -5,7 +5,10 @@
* Author: tibi
*/
#include <GameState.h>
#include <controller/GuiController.h>
#include <gui/primitives/RenderContext.h>
#include <gui/primitives/TextArea.h>
#include <cassert>
@ -26,13 +29,56 @@ GuiController::~GuiController()
void GuiController::initialize(GameState* gameState)
{
assert(gameState != nullptr);
assert(gameState->viewport.initialized);
m_gameState = gameState;
// Set up canvas
m_canvas.setSize(m_gameState->viewport.width, m_gameState->viewport.height);
// Add a text element
auto text = new gui::primitives::TextArea();
text->setText("Hello world!\nMy name is Tibi!\nThis is a really really long long long, even the longest ever, line.");
text->setSize(200, 100);
text->setPosition(100, 10);
text->setColor(0, 1, 0);
text->setBackColor(0.5f, 0, 0, 0.5f);
text->setTextSize(11);
text->setHorizontalWrap(gui::primitives::TextHorizontalWrapping::Ellipsis);
m_canvas.addChild(text);
}
bool GuiController::processEvent(SDL_Event& event)
{
return false;
bool handled = m_canvas.handleEvent(event);
float currentW = m_canvas.child(0)->width();
float currentH = m_canvas.child(0)->height();
if (event.type == SDL_EventType::SDL_KEYDOWN && event.key.keysym.scancode == SDL_SCANCODE_HOME)
{
m_canvas.child(0)->setSize(currentW + 50, currentH);
}
if (event.type == SDL_EventType::SDL_KEYDOWN && event.key.keysym.scancode == SDL_SCANCODE_END)
{
m_canvas.child(0)->setSize(currentW - 50, currentH);
}
return handled;
}
void GuiController::render()
{
// Compute render context
gui::primitives::RenderContext renderContext =
{
.sdlRenderer = &m_gameState->sdlRenderer,
.resManager = &m_gameState->resManager,
.uiScale = 1,
};
// Render
m_canvas.render(renderContext);
}
} /* namespace controller */

View File

@ -8,6 +8,8 @@
#ifndef CONTROLLER_GUICONTROLLER_H_
#define CONTROLLER_GUICONTROLLER_H_
#include <gui/Canvas.h>
#include <SDL2/SDL.h>
namespace farmlands {
@ -33,8 +35,14 @@ namespace controller {
*/
bool processEvent(SDL_Event& event);
/**
* Renders the GUI
*/
void render();
private:
GameState* m_gameState;
gui::Canvas m_canvas;
};
} /* namespace controller */

View File

@ -39,31 +39,30 @@ void PlayerController::updateLogic()
{
// Get keyboard status
const Uint8* keys = SDL_GetKeyboardState(NULL);
const SDL_Keymod mods = SDL_GetModState();
float deltaX = 0;
float deltaY = 0;
// Compute movement delta multiplier
float deltaMultiplier = 5.0f * m_gameState->elapsedTime; // Equivalent to units per second
if ((mods & KMOD_LSHIFT) || (mods & KMOD_RSHIFT))
if (keys[m_gameState->config.keys.Run])
deltaMultiplier *= 5;
// Handle scale changes (debugging only)
if (keys[SDL_SCANCODE_KP_MINUS])
if (keys[m_gameState->config.keys.Debug_ZoomOut])
m_gameState->camera.scale *= 1.0f - 0.05f * deltaMultiplier;
if (keys[SDL_SCANCODE_KP_PLUS])
if (keys[m_gameState->config.keys.Debug_ZoomIn])
m_gameState->camera.scale *= 1.0f + 0.05f * deltaMultiplier;
// Handle movement
if (keys[SDL_SCANCODE_UP])
deltaY -= 1;
if (keys[SDL_SCANCODE_DOWN])
deltaY += 1;
if (keys[SDL_SCANCODE_LEFT])
deltaX -= 1;
if (keys[SDL_SCANCODE_RIGHT])
if (keys[m_gameState->config.keys.Right] || keys[m_gameState->config.keys.AltRight])
deltaX += 1;
if (keys[m_gameState->config.keys.Up] || keys[m_gameState->config.keys.AltUp])
deltaY -= 1;
if (keys[m_gameState->config.keys.Left] || keys[m_gameState->config.keys.AltLeft])
deltaX -= 1;
if (keys[m_gameState->config.keys.Down] || keys[m_gameState->config.keys.AltDown])
deltaY += 1;
float newX = m_gameState->player.posX + deltaX * deltaMultiplier;
float newY = m_gameState->player.posY + deltaY * deltaMultiplier;

View File

@ -58,6 +58,7 @@ bool SdlRenderer::initialize(GameState* gameState)
return false;
}
m_gameState->viewport.initialized = true;
m_gameState->viewport.width = 1024;
m_gameState->viewport.height = 768;
m_sdlWindow = SDL_CreateWindow("Farmlands", 0, 0, m_gameState->viewport.width, m_gameState->viewport.height, SDL_WINDOW_SHOWN);
@ -71,12 +72,14 @@ bool SdlRenderer::initialize(GameState* gameState)
std::cerr << "Failed to create renderer!\n";
return false;
}
SDL_SetRenderDrawBlendMode(m_sdlRenderer, SDL_BlendMode::SDL_BLENDMODE_BLEND);
return true;
}
void SdlRenderer::renderBegin()
{
SDL_SetRenderDrawColor(m_sdlRenderer, 0, 0, 0, 255);
SDL_RenderClear(m_sdlRenderer);
}

28
src/gui/Canvas.cpp Normal file
View File

@ -0,0 +1,28 @@
/*
* Canvas.cpp
*
* Created on: Nov 27, 2016
* Author: tibi
*/
#include <gui/Canvas.h>
namespace farmlands {
namespace gui {
Canvas::Canvas()
{
}
Canvas::~Canvas()
{
}
void Canvas::render(primitives::RenderContext& context)
{
for (auto child : m_children)
child->render(context);
}
} /* namespace gui */
} /* namespace farmlands */

29
src/gui/Canvas.h Normal file
View File

@ -0,0 +1,29 @@
/*
* Canvas.h
*
* Created on: Nov 27, 2016
* Author: tibi
*/
#ifndef GUI_PRIMITIVES_CANVAS_H_
#define GUI_PRIMITIVES_CANVAS_H_
#include <gui/primitives/UILayout.h>
#include <gui/primitives/RenderContext.h>
namespace farmlands {
namespace gui {
class Canvas: public primitives::UILayout
{
public:
Canvas();
virtual ~Canvas();
virtual void render(primitives::RenderContext& context) override;
};
} /* namespace gui */
} /* namespace farmlands */
#endif /* GUI_PRIMITIVES_CANVAS_H_ */

View File

@ -0,0 +1,30 @@
/*
* Image.cpp
*
* Created on: Nov 27, 2016
* Author: tibi
*/
#include <gui/primitives/Image.h>
namespace farmlands
{
namespace gui
{
namespace primitives
{
Image::Image()
{
// TODO Auto-generated constructor stub
}
Image::~Image()
{
// TODO Auto-generated destructor stub
}
} /* namespace primitives */
} /* namespace gui */
} /* namespace farmlands */

View File

@ -0,0 +1,28 @@
/*
* Image.h
*
* Created on: Nov 27, 2016
* Author: tibi
*/
#ifndef GUI_PRIMITIVES_IMAGE_H_
#define GUI_PRIMITIVES_IMAGE_H_
#include <gui/primitives/UIElement.h>
namespace farmlands {
namespace gui {
namespace primitives {
class Image: public UIElement
{
public:
Image();
virtual ~Image();
};
} /* namespace primitives */
} /* namespace gui */
} /* namespace farmlands */
#endif /* GUI_PRIMITIVES_IMAGE_H_ */

View File

@ -0,0 +1,29 @@
/*
* RenderContext.h
*
* Created on: Nov 27, 2016
* Author: tibi
*/
#ifndef GUI_PRIMITIVES_RENDERCONTEXT_H_
#define GUI_PRIMITIVES_RENDERCONTEXT_H_
#include <graphics/SdlRenderer.h>
#include <resources/ResourceManager.h>
namespace farmlands {
namespace gui {
namespace primitives {
struct RenderContext
{
graphics::SdlRenderer* sdlRenderer;
resources::ResourceManager* resManager;
float uiScale;
};
}
}
}
#endif /* GUI_PRIMITIVES_RENDERCONTEXT_H_ */

View File

@ -0,0 +1,234 @@
/*
* TextArea.cpp
*
* Created on: Nov 27, 2016
* Author: tibi
*/
#include <gui/primitives/TextArea.h>
#include <resources/Resources.h>
#include <cassert>
#include <vector>
#include <boost/algorithm/string.hpp>
#include <SDL2/SDL_ttf.h>
namespace farmlands
{
namespace gui
{
namespace primitives
{
TextArea::TextArea()
: m_text(),
m_wrappedText(),
m_textChanged(true),
m_fontId(resources::R::Fonts::DejaVuSans),
m_fontSize(9),
m_color(),
m_align(TextAlign::MiddleCenter),
m_horWrap(TextHorizontalWrapping::Wrap),
m_verWrap(TextVerticalWrapping::Overflow)
{
}
TextArea::~TextArea()
{
}
void TextArea::render(RenderContext& context)
{
UIElement::render(context);
// Obtain font
TTF_Font* font = context.resManager->font(m_fontId, m_fontSize * context.uiScale);
if (font == nullptr)
return; // TODO: handle error (maybe log it)
// Split text in lines
if (m_textChanged)
wrapText(font);
std::vector<std::string> lines;
boost::split(lines, m_wrappedText, boost::is_any_of("\n"), boost::token_compress_off);
// Render lines
int lineH = TTF_FontLineSkip(font);
int totalH = 0;
for (auto line : lines)
{
SDL_Rect dest =
{
(int) x(), (int) (y() + totalH),
0, 0
};
SDL_Texture* tex = context.sdlRenderer->renderText(line.c_str(), font, m_color);
SDL_QueryTexture(tex, NULL, NULL, &dest.w, &dest.h);
SDL_RenderCopy(context.sdlRenderer->internalRenderer(), tex, NULL, &dest);
totalH += lineH;
}
}
void TextArea::setText(const std::string& text)
{
m_text = text;
m_textChanged = true;
}
void TextArea::setFont(int fontId)
{
m_fontId = fontId;
m_textChanged = true;
}
void TextArea::setTextSize(int size)
{
m_fontSize = size;
m_textChanged = true;
}
void TextArea::setColor(SDL_Color color)
{
m_color = color;
}
void TextArea::setColor(float r, float g, float b, float a)
{
m_color.r = r * 255;
m_color.g = g * 255;
m_color.b = b * 255;
m_color.a = a * 255;
}
void TextArea::setAlignment(TextAlign align)
{
m_align = align;
}
void TextArea::setHorizontalWrap(TextHorizontalWrapping wrap)
{
m_horWrap = wrap;
m_textChanged = true;
}
void TextArea::setVerticalWrap(TextVerticalWrapping wrap)
{
m_verWrap = wrap;
m_textChanged = true;
}
void TextArea::wrapText(TTF_Font* font)
{
assert(font != NULL);
// Current width and total height
int currentW = 0;
int totalH = 0;
int lineBegin = 0;
int wordBegin = 0;
// Measure required sizes
int lineHeight = TTF_FontLineSkip(font);
int ellipsisW = 0;
if (m_horWrap == TextHorizontalWrapping::Ellipsis)
TTF_SizeText(font, "...", &ellipsisW, NULL);
// Start
m_wrappedText = m_text + " ";
// Process characters
for (int i = 0; i < m_wrappedText.size(); i++)
{
// Remove unwanted character
if (m_wrappedText[i] == '\r')
{
m_wrappedText.erase(i--, 1);
continue;
}
if (m_horWrap != TextHorizontalWrapping::Overflow && isspace(m_wrappedText[i]))
{
std::string word = m_wrappedText.substr(wordBegin, i - wordBegin);
int wordW = 0;
TTF_SizeText(font, word.c_str(), &wordW, NULL);
// Word doesn't fit?
if (currentW + wordW + ellipsisW < width())
{
currentW += wordW;
wordBegin = i;
}
else
{
if (wordBegin == lineBegin)
{
// Search for a split point
int lastGood = -1;
for (int j = wordBegin + 1; j < i; j++)
{
std::string word = m_wrappedText.substr(wordBegin, j - wordBegin);
TTF_SizeText(font, word.c_str(), &wordW, NULL);
if (currentW + wordW + ellipsisW < width())
lastGood = j;
else break;
}
// Cannot split anything :( we don't have enough space
if (lastGood == -1)
return;
m_wrappedText.insert(lastGood + 1, "\n");
i = lastGood + 1;
wordBegin = i;
}
if (m_horWrap == TextHorizontalWrapping::Ellipsis)
{
m_wrappedText.insert(wordBegin, "...");
wordBegin += 3;
i += 3;
}
m_wrappedText[wordBegin] = '\n';
// Trim line
if (m_horWrap == TextHorizontalWrapping::Trim || m_horWrap == TextHorizontalWrapping::Ellipsis)
{
i = wordBegin + 1;
size_t nextLine = i;
while (nextLine < m_wrappedText.size() && m_wrappedText[nextLine] != '\n')
++nextLine;
m_wrappedText.erase(i, nextLine + 1);
}
}
}
if (m_wrappedText[i] == '\n')
{
// Trim rest of text
if (m_verWrap == TextVerticalWrapping::Trim && totalH + lineHeight > height())
m_wrappedText.erase(i);
// Update positions
lineBegin = i + 1;
wordBegin = i + 1;
currentW = 0;
totalH += lineHeight;
}
}
}
} /* namespace primitives */
} /* namespace gui */
} /* namespace farmlands */

View File

@ -0,0 +1,91 @@
/*
* TextArea.h
*
* Created on: Nov 27, 2016
* Author: tibi
*/
#ifndef GUI_PRIMITIVES_TEXTAREA_H_
#define GUI_PRIMITIVES_TEXTAREA_H_
#include <gui/primitives/UIElement.h>
#include <vector>
namespace farmlands {
namespace gui {
namespace primitives {
enum class TextAlign
{
TopLeft,
TopCenter,
TopRight,
MiddleLeft,
MiddleCenter,
MiddleRight,
BottomLeft,
BottomCenter,
BottomRight,
};
enum class TextHorizontalWrapping
{
Overflow,
Wrap,
Trim,
Ellipsis
};
enum class TextVerticalWrapping
{
Overflow,
Trim
};
class TextArea: public UIElement
{
public:
TextArea();
virtual ~TextArea();
virtual void render(RenderContext& context) override;
// Getters and setters
inline std::string text() const { return m_text; }
inline int font() const { return m_fontId; }
inline int textSize() const { return m_fontSize; }
inline SDL_Color color() const { return m_color; }
inline TextAlign alignment() const { return m_align; }
inline TextHorizontalWrapping horizontalWrap() const { return m_horWrap; }
inline TextVerticalWrapping verticalWrap() const { return m_verWrap; }
void setText(const std::string& text);
void setFont(int fontId);
void setTextSize(int size);
void setColor(SDL_Color color);
void setColor(float r, float g, float b, float a = 1.0f);
void setAlignment(TextAlign align);
void setHorizontalWrap(TextHorizontalWrapping wrap);
void setVerticalWrap(TextVerticalWrapping wrap);
private:
void wrapText(TTF_Font* font);
std::string m_text;
std::string m_wrappedText;
bool m_textChanged;
int m_fontId;
int m_fontSize;
SDL_Color m_color;
TextAlign m_align;
TextHorizontalWrapping m_horWrap;
TextVerticalWrapping m_verWrap;
};
} /* namespace primitives */
} /* namespace gui */
} /* namespace farmlands */
#endif /* GUI_PRIMITIVES_TEXTAREA_H_ */

View File

@ -0,0 +1,76 @@
/*
* UIElement.cpp
*
* Created on: Nov 27, 2016
* Author: tibi
*/
#include <gui/primitives/UIElement.h>
namespace farmlands
{
namespace gui
{
namespace primitives
{
UIElement::UIElement()
: m_x(0), m_y(0),
m_w(0), m_h(0)
{
}
UIElement::~UIElement()
{
}
void UIElement::render(RenderContext& context)
{
if (m_backColor.a > 0)
{
SDL_Rect rect =
{
(int) m_x,
(int) m_y,
(int) m_w,
(int) m_h
};
SDL_SetRenderDrawColor(context.sdlRenderer->internalRenderer(), m_backColor.r, m_backColor.g, m_backColor.b, m_backColor.a);
SDL_RenderFillRect(context.sdlRenderer->internalRenderer(), &rect);
}
}
bool UIElement::handleEvent(SDL_Event& event)
{
return false;
}
void UIElement::setPosition(float x, float y)
{
m_x = x;
m_y = y;
}
void UIElement::setSize(float w, float h)
{
m_w = w;
m_h = h;
}
void UIElement::setBackColor(SDL_Color backColor)
{
m_backColor = backColor;
}
void UIElement::setBackColor(float r, float g, float b, float a)
{
m_backColor.r = r * 255;
m_backColor.g = g * 255;
m_backColor.b = b * 255;
m_backColor.a = a * 255;
}
} /* namespace primitives */
} /* namespace gui */
} /* namespace farmlands */

View File

@ -0,0 +1,50 @@
/*
* UIElement.h
*
* Created on: Nov 27, 2016
* Author: tibi
*/
#ifndef GUI_PRIMITIVES_UIELEMENT_H_
#define GUI_PRIMITIVES_UIELEMENT_H_
#include <SDL2/SDL.h>
#include <gui/primitives/RenderContext.h>
namespace farmlands {
namespace gui {
namespace primitives {
class UIElement
{
public:
UIElement();
virtual ~UIElement();
virtual void render(RenderContext& context);
virtual bool handleEvent(SDL_Event& event);
// Getters & setters
inline float x() const { return m_x; }
inline float y() const { return m_y; }
inline float width() const { return m_w; }
inline float height() const { return m_h; }
inline SDL_Color backColor() const { return m_backColor; }
virtual void setPosition(float x, float y);
virtual void setSize(float w, float h);
virtual void setBackColor(SDL_Color backColor);
virtual void setBackColor(float r, float g, float b, float a = 1.0f);
private:
float m_x, m_y;
float m_w, m_h;
SDL_Color m_backColor;
};
} /* namespace primitives */
} /* namespace gui */
} /* namespace farmlands */
#endif /* GUI_PRIMITIVES_UIELEMENT_H_ */

View File

@ -0,0 +1,68 @@
/*
* UILayout.cpp
*
* Created on: Nov 27, 2016
* Author: tibi
*/
#include <gui/primitives/UILayout.h>
#include <cassert>
namespace farmlands
{
namespace gui
{
namespace primitives
{
UILayout::UILayout()
{
}
UILayout::~UILayout()
{
for (auto child : m_children)
delete child;
}
void UILayout::addChild(UIElement* element)
{
assert(element != NULL);
m_children.push_back(element);
}
size_t UILayout::childrenCount() const
{
return m_children.size();
}
UIElement* UILayout::child(size_t index)
{
return m_children.at(index);
}
void UILayout::removeChild(size_t index)
{
// Unallocate item
delete m_children.at(index);
// Remove from children list
m_children.erase(m_children.begin() + index);
}
UIElement* UILayout::unparentChild(size_t index)
{
// Save item
UIElement* elem = m_children.at(index);
// Remove from children list
m_children.erase(m_children.begin() + index);
return elem;
}
} /* namespace primitives */
} /* namespace gui */
} /* namespace farmlands */

View File

@ -0,0 +1,39 @@
/*
* UILayout.h
*
* Created on: Nov 27, 2016
* Author: tibi
*/
#ifndef GUI_PRIMITIVES_UILAYOUT_H_
#define GUI_PRIMITIVES_UILAYOUT_H_
#include <gui/primitives/UIElement.h>
#include <vector>
namespace farmlands {
namespace gui {
namespace primitives {
class UILayout: public UIElement
{
public:
UILayout();
virtual ~UILayout();
void addChild(UIElement* element);
size_t childrenCount() const;
UIElement* child(size_t index);
void removeChild(size_t index);
UIElement* unparentChild(size_t index);
protected:
std::vector<UIElement*> m_children;
};
} /* namespace primitives */
} /* namespace gui */
} /* namespace farmlands */
#endif /* GUI_PRIMITIVES_UILAYOUT_H_ */

40
src/model/Configuration.h Normal file
View File

@ -0,0 +1,40 @@
/*
* Settings.h
*
* Created on: Nov 13, 2016
* Author: tibi
*/
#include <SDL2/SDL.h>
namespace farmlands {
namespace model {
struct KeyConfiguration
{
SDL_Scancode Right = SDL_SCANCODE_RIGHT;
SDL_Scancode Up = SDL_SCANCODE_UP;
SDL_Scancode Left = SDL_SCANCODE_LEFT;
SDL_Scancode Down = SDL_SCANCODE_DOWN;
SDL_Scancode AltRight = SDL_SCANCODE_D;
SDL_Scancode AltUp = SDL_SCANCODE_W;
SDL_Scancode AltLeft = SDL_SCANCODE_A;
SDL_Scancode AltDown = SDL_SCANCODE_S;
SDL_Scancode Action = SDL_SCANCODE_SPACE;
SDL_Scancode Action2 = SDL_SCANCODE_LCTRL;
SDL_Scancode Run = SDL_SCANCODE_LSHIFT;
SDL_Scancode Debug_ZoomIn = SDL_SCANCODE_KP_PLUS;
SDL_Scancode Debug_ZoomOut = SDL_SCANCODE_KP_MINUS;
};
struct Configuration
{
KeyConfiguration keys;
};
}
}

31
src/model/Direction.h Normal file
View File

@ -0,0 +1,31 @@
/*
* Direction.h
*
* Created on: Nov 27, 2016
* Author: tibi
*/
#ifndef MODEL_DIRECTION_H_
#define MODEL_DIRECTION_H_
namespace farmlands {
namespace model {
enum Direction
{
East = 0,
NorthEast = 1,
North = 2,
NorthWest = 3,
West = 4,
SouthWest = 5,
South = 6,
SouthEast = 7
};
}
}
#endif /* MODEL_DIRECTION_H_ */

View File

@ -1,18 +0,0 @@
/*
* Settings.h
*
* Created on: Nov 13, 2016
* Author: tibi
*/
namespace farmlands {
namespace model {
struct GameConfig
{
float graphics_Scale;
float graphics_UiScale;
};
}
}

View File

@ -16,6 +16,8 @@ namespace model {
struct Player
{
float posX, posY;
int inventorySelection = -1;
int inventory[PLAYER_INVENTORY_SIZE];
};