Commit 1534cec8 authored by Aloshi's avatar Aloshi

Added ComponentListComponent for laying out elements in a grid and

navigating through them.
Added SliderComponent for selecting from a range of values.
Added SwitchComponent for selecting an "ON" or "OFF" value.
parent e8465baa
......@@ -133,7 +133,10 @@ set(ES_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/src/Window.h
${CMAKE_CURRENT_SOURCE_DIR}/src/XMLReader.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/AnimationComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentListComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/SliderComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/SwitchComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextListComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ThemeComponent.h
......@@ -143,6 +146,7 @@ set(ES_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiGameList.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiInputConfig.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiMenu.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiSettingsMenu.h
${CMAKE_CURRENT_SOURCE_DIR}/src/pugiXML/pugiconfig.hpp
${CMAKE_CURRENT_SOURCE_DIR}/src/pugiXML/pugixml.hpp
${CMAKE_CURRENT_SOURCE_DIR}/data/Resources.h
......@@ -169,7 +173,10 @@ set(ES_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/Window.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/XMLReader.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/AnimationComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentListComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/SliderComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/SwitchComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ThemeComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiBox.cpp
......@@ -178,6 +185,7 @@ set(ES_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiGameList.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiInputConfig.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiMenu.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiSettingsMenu.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/pugiXML/pugixml.cpp
${CMAKE_CURRENT_SOURCE_DIR}/data/logo/ES_logo_16.cpp
${CMAKE_CURRENT_SOURCE_DIR}/data/logo/ES_logo_32.cpp
......
......@@ -13,6 +13,9 @@ GuiComponent::~GuiComponent()
if(mParent)
mParent->removeChild(this);
for(unsigned int i = 0; i < getChildCount(); i++)
getChild(i)->setParent(NULL);
}
bool GuiComponent::input(InputConfig* config, Input input)
......@@ -83,13 +86,15 @@ Vector2i GuiComponent::getOffset()
void GuiComponent::setOffset(Vector2i offset)
{
mOffset = offset;
setOffset(offset.x, offset.y);
onOffsetChanged();
}
void GuiComponent::setOffset(int x, int y)
{
mOffset.x = x;
mOffset.y = y;
onOffsetChanged();
}
Vector2u GuiComponent::getSize()
......
......@@ -29,10 +29,11 @@ public:
//Called when the Renderer deinitializes. Passes to children.
virtual void deinit();
Vector2i getGlobalOffset();
virtual Vector2i getGlobalOffset();
Vector2i getOffset();
void setOffset(Vector2i offset);
void setOffset(int x, int y);
virtual void onOffsetChanged() {};
Vector2u getSize();
......
#include "ComponentListComponent.h"
#include "../Log.h"
#include "../Renderer.h"
#define INITIAL_CELL_SIZE 12
ComponentListComponent::ComponentListComponent(Window* window, Vector2u gridDimensions) : GuiComponent(window), mGrid(NULL), mColumnWidths(NULL), mRowHeights(NULL)
{
mEntries.reserve(gridDimensions.x*gridDimensions.y);
makeCells(gridDimensions);
}
void ComponentListComponent::makeCells(Vector2u size)
{
if(mGrid)
delete[] mGrid;
if(mColumnWidths)
delete[] mColumnWidths;
if(mRowHeights)
delete[] mRowHeights;
mGridSize = size;
mGrid = new ComponentEntry*[size.x * size.y];
std::fill(mGrid, mGrid + (size.x * size.y), (ComponentEntry*)NULL);
mColumnWidths = new unsigned int[size.x];
std::fill(mColumnWidths, mColumnWidths + size.x, INITIAL_CELL_SIZE);
mRowHeights = new unsigned int[size.y];
std::fill(mRowHeights, mRowHeights + size.y, INITIAL_CELL_SIZE);
updateSize();
resetCursor();
}
void ComponentListComponent::setEntry(Vector2u pos, Vector2u size, GuiComponent* component, bool canFocus, AlignmentType align,
Vector2<bool> autoFit, UpdateBehavior updateType)
{
if(pos.x > mGridSize.x || pos.y > mGridSize.y)
{
LOG(LogError) << "Tried to set entry beyond grid size!";
return;
}
if(component == NULL)
{
LOG(LogError) << "Tried to add NULL component to ComponentList!";
return;
}
ComponentEntry entry(Rect(pos.x, pos.y, size.x, size.y), component, updateType, canFocus, align);
mEntries.push_back(entry);
for(unsigned int y = pos.y; y < pos.y + size.y; y++)
{
for(unsigned int x = pos.x; x < pos.x + size.x; x++)
{
setCell(x, y, &mEntries.back());
}
}
if(component->getParent() != NULL)
LOG(LogError) << "ComponentListComponent ruining an existing parent-child relationship! Call a social worker!";
component->setParent(this);
if(!cursorValid() && canFocus)
mCursor = (Vector2i)pos;
//update the column width and row height
if(autoFit.x && getColumnWidth(pos.x) < component->getSize().x)
setColumnWidth(pos.x, component->getSize().x);
if(autoFit.y && getRowHeight(pos.y) < component->getSize().y)
setRowHeight(pos.y, component->getSize().y);
component->setOffset(getCellOffset(pos));
}
void ComponentListComponent::setRowHeight(int row, unsigned int size)
{
mRowHeights[row] = size;
updateSize();
}
void ComponentListComponent::setColumnWidth(int col, unsigned int size)
{
mColumnWidths[col] = size;
updateSize();
}
unsigned int ComponentListComponent::getRowHeight(int row) { return mRowHeights[row]; }
unsigned int ComponentListComponent::getColumnWidth(int col) { return mColumnWidths[col]; }
Vector2i ComponentListComponent::getCellOffset(Vector2u pos)
{
Vector2i offset;
for(unsigned int y = 0; y < pos.y; y++)
offset.y += getRowHeight(y);
for(unsigned int x = 0; x < pos.x; x++)
offset.x += getColumnWidth(x);
ComponentEntry* entry = getCell(pos.x, pos.y);
Vector2u gridSize;
for(unsigned int x = pos.x; x < pos.x + entry->box.size.x; x++)
gridSize.x += getColumnWidth(x);
for(unsigned int y = pos.y; y < pos.y + entry->box.size.y; y++)
gridSize.y += getRowHeight(y);
//if AlignCenter, add half of cell width - half of control width
if(entry->alignment == AlignCenter)
offset.x += gridSize.x / 2 - entry->component->getSize().x / 2;
//if AlignRight, add cell width - control width
if(entry->alignment == AlignRight)
offset.x += gridSize.x - entry->component->getSize().x;
//always center on the Y axis
offset.y += gridSize.y / 2 - entry->component->getSize().y / 2;
return offset;
}
void ComponentListComponent::setCell(unsigned int x, unsigned int y, ComponentEntry* entry)
{
if(x < 0 || y < 0 || x >= mGridSize.x || y >= mGridSize.y)
{
LOG(LogError) << "Invalid setCell - position " << x << ", " << y << " out of bounds!";
return;
}
mGrid[y * mGridSize.x + x] = entry;
}
ComponentListComponent::ComponentEntry* ComponentListComponent::getCell(unsigned int x, unsigned int y)
{
if(x < 0 || y < 0 || x >= mGridSize.x || y >= mGridSize.y)
{
LOG(LogError) << "Invalid getCell - position " << x << ", " << y << " out of bounds!";
return NULL;
}
return mGrid[y * mGridSize.x + x];
}
void ComponentListComponent::updateSize()
{
mSize = Vector2u(0, 0);
for(unsigned int x = 0; x < mGridSize.x; x++)
mSize.x += getColumnWidth(x);
for(unsigned int y = 0; y < mGridSize.y; y++)
mSize.y += getRowHeight(y);
}
void ComponentListComponent::updateComponentOffsets()
{
for(auto iter = mEntries.begin(); iter != mEntries.end(); iter++)
{
iter->component->setOffset(getCellOffset((Vector2u)iter->box.pos));
}
}
bool ComponentListComponent::input(InputConfig* config, Input input)
{
if(cursorValid() && getCell(mCursor.x, mCursor.y)->component->input(config, input))
return true;
if(!input.value)
return false;
if(config->isMappedTo("down", input))
{
moveCursor(Vector2i(0, 1));
return true;
}
if(config->isMappedTo("up", input))
{
moveCursor(Vector2i(0, -1));
return true;
}
return false;
}
void ComponentListComponent::resetCursor()
{
if(mEntries.size() == 0)
{
mCursor = Vector2i(-1, -1);
return;
}
mCursor = mEntries.at(0).box.pos;
}
void ComponentListComponent::moveCursor(Vector2i dir)
{
if(dir.x != 0 && dir.y != 0)
{
LOG(LogError) << "Invalid cursor move dir!";
return;
}
if(!cursorValid())
{
resetCursor();
if(!cursorValid())
return;
}
Vector2i origCursor = mCursor;
Vector2i searchAxis(dir.x == 0, dir.y == 0);
while(mCursor.x >= 0 && mCursor.y >= 0 && mCursor.x < (int)mGridSize.x && mCursor.y < (int)mGridSize.y)
{
mCursor = mCursor + dir;
Vector2i curDirPos = mCursor;
//spread out on search axis+
while(mCursor.x < (int)mGridSize.x && mCursor.y < (int)mGridSize.y)
{
if(cursorValid() && getCell(mCursor.x, mCursor.y)->canFocus)
return;
mCursor += searchAxis;
}
//now again on search axis-
mCursor = curDirPos;
while(mCursor.x >= 0 && mCursor.y >= 0)
{
if(cursorValid() && getCell(mCursor.x, mCursor.y)->canFocus)
return;
mCursor -= searchAxis;
}
}
//failed to find another focusable element in this direction
mCursor = origCursor;
}
bool ComponentListComponent::cursorValid()
{
if(mCursor.x < 0 || mCursor.y < 0 || mCursor.x >= (int)mGridSize.x || mCursor.y >= (int)mGridSize.y)
return false;
return getCell(mCursor.x, mCursor.y) != NULL;
}
void ComponentListComponent::update(int deltaTime)
{
for(auto iter = mEntries.begin(); iter != mEntries.end(); iter++)
{
if(iter->updateType == UpdateAlways)
{
iter->component->update(deltaTime);
continue;
}
if(iter->updateType == UpdateFocused && cursorValid() && getCell(mCursor.x, mCursor.y)->component == iter->component)
{
iter->component->update(deltaTime);
continue;
}
}
}
void ComponentListComponent::onRender()
{
Renderer::drawRect(0, 0, getSize().x, getSize().y, 0xFFFFFFAA);
for(auto iter = mEntries.begin(); iter != mEntries.end(); iter++)
{
iter->component->render();
}
//draw cell outlines
/*Vector2i pos;
for(unsigned int x = 0; x < mGridSize.x; x++)
{
for(unsigned int y = 0; y < mGridSize.y; y++)
{
Renderer::drawRect(pos.x, pos.y, getColumnWidth(x), 2, 0x000000AA);
Renderer::drawRect(pos.x, pos.y, 2, getRowHeight(y), 0x000000AA);
Renderer::drawRect(pos.x + getColumnWidth(x), pos.y, 2, getRowHeight(y), 0x000000AA);
Renderer::drawRect(pos.x, pos.y + getRowHeight(y) - 2, getColumnWidth(x), 2, 0x000000AA);
pos.y += getRowHeight(y);
}
pos.y = 0;
pos.x += getColumnWidth(x);
}*/
//draw cursor
if(cursorValid())
{
ComponentEntry* entry = getCell(mCursor.x, mCursor.y);
Renderer::drawRect(entry->component->getOffset().x, entry->component->getOffset().y, 4, 4, 0xFF0000FF);
Renderer::drawRect(entry->component->getOffset().x, entry->component->getOffset().y, entry->component->getSize().x, entry->component->getSize().y, 0x0000AA88);
}
}
void ComponentListComponent::onOffsetChanged()
{
updateComponentOffsets();
}
#pragma once
#include "../GuiComponent.h"
class ComponentListComponent : public GuiComponent
{
public:
ComponentListComponent(Window* window, Vector2u gridDimensions);
enum UpdateBehavior
{
UpdateAlways, UpdateFocused
};
enum AlignmentType
{
AlignLeft, AlignRight, AlignCenter
};
void setEntry(Vector2u pos, Vector2u size, GuiComponent* component, bool canFocus, AlignmentType align, Vector2<bool> autoFit, UpdateBehavior updateType = UpdateAlways);
void onOffsetChanged() override;
bool input(InputConfig* config, Input input) override;
void update(int deltaTime) override;
void onRender() override;
void setColumnWidth(int col, unsigned int size);
void setRowHeight(int row, unsigned int size);
void resetCursor();
bool cursorValid();
private:
class ComponentEntry
{
public:
Rect box;
GuiComponent* component;
UpdateBehavior updateType;
AlignmentType alignment;
bool canFocus;
ComponentEntry() : component(NULL), updateType(UpdateAlways), canFocus(true), alignment(AlignCenter) {};
ComponentEntry(Rect b, GuiComponent* comp, UpdateBehavior update, bool focus, AlignmentType align) : box(b), component(comp), updateType(update), canFocus(focus), alignment(align) {};
operator bool() const
{
return component != NULL;
}
};
//Offset we render components by (for scrolling).
Vector2i mComponentOffset;
Vector2u mGridSize;
ComponentEntry** mGrid;
std::vector<ComponentEntry> mEntries;
void makeCells(Vector2u size);
void setCell(unsigned int x, unsigned int y, ComponentEntry* entry);
ComponentEntry* getCell(unsigned int x, unsigned int y);
Vector2u mSelectedCellIndex;
unsigned int getColumnWidth(int col);
unsigned int getRowHeight(int row);
unsigned int* mColumnWidths;
unsigned int* mRowHeights;
Vector2i getCellOffset(Vector2u gridPos);
void updateSize();
void moveCursor(Vector2i dir);
Vector2i mCursor;
void updateComponentOffsets();
};
//ability to define a list of components in terms of a grid
//input
//pass to selected component
// if returns true, stop
// else, process:
// if input == up/down
// scroll to prev/next selectable component in grid Y
// if input == left/right
// scroll to prev/next selectable component in grid X
// if input == accept
// call registered function?
//entry struct/class
// GuiComponent* component - component to work with
// bool canFocus - can we pass input to this? (necessary for labels to not be selectable)
// Function* selectFunc?
// UpdateBehavior update - how to handle updates (all the time or only when focused)
//update
//animate component offset to display selected component within the bounds
//pass update to all entries with appropriate update behavior
//render
//clip rect to our size
//render a "selected" effect behind component with focus somehow
// an edge filter would be cool, but we can't really do that without shader support
// a transparent rect will work for now, but it's kind of ugly...maybe a GuiBox
//glTranslatef by our render offset
// doesn't handle getGlobalOffset for our components...would need parenting for that
//methods
//List::setEntry(Vector2i gridPos, GuiComponent* component, bool canFocus, AlignmentType align,
// Function* selectFunc = NULL, UpdateBehavior updateType = UpdateAlways);
//example of setting up the SettingsMenu list:
//ComponentListComponent list;
//int row = 0;
//TextComponent* label = new TextComponent(Vector2i(0, 0), "Debug:", font, lblColor, etc);
//
//list.setEntry(Vector2i(-1, row), label, false, AlignRight);
//list.setEntry(Vector2i(0, row++), &mDebugSwitch, true, AlignLeft);
//...
//list.setEntry(Rect(-1, row, 2, 1), &mSaveButton, true, AlignCenter);
//example of setting up GameGrid list:
//ComponentListComponent list;
//for(int y = 0; y < yMax; y++)
// for(int x = 0; x < xMax; x++)
// list.setEntry(Vector2i(x, y), getGameImage(x, y), true, AlignCenter, &this->onSelectGame);
......@@ -5,6 +5,7 @@
#include "../SystemData.h"
#include "GuiGameList.h"
#include "../Settings.h"
#include "GuiSettingsMenu.h"
GuiMenu::GuiMenu(Window* window, GuiGameList* parent) : GuiComponent(window)
{
......@@ -52,6 +53,10 @@ void GuiMenu::executeCommand(std::string command)
//reload the game list
SystemData::loadConfig();
mParent->setSystemId(0);
}else if(command == "es_settings")
{
mWindow->pushGui(new GuiSettingsMenu(mWindow));
delete this;
}else{
if(system(command.c_str()) != 0)
{
......@@ -69,6 +74,9 @@ void GuiMenu::populateList()
//the method is GuiList::addObject(std::string displayString, std::string commandString, unsigned int displayHexColor);
//the list will automatically adjust as items are added to it, this should be the only area you need to change
//if you want to do something special within ES, override your command in the executeComand() method
mList->addObject("Settings", "es_settings", 0x0000FFFF);
mList->addObject("Restart", "sudo shutdown -r now", 0x0000FFFF);
mList->addObject("Shutdown", "sudo shutdown -h now", 0x0000FFFF);
......
#include "GuiSettingsMenu.h"
#include "../Renderer.h"
#include "../Settings.h"
#include "SliderComponent.h"
GuiSettingsMenu::GuiSettingsMenu(Window* window) : GuiComponent(window),
mList(window, Vector2u(2, 3)),
mDrawFramerateSwitch(window)
{
addChild(&mList);
mList.setOffset(Renderer::getScreenWidth() / 4, 0);
TextComponent* label = new TextComponent(mWindow);
label->setText("Draw Framerate: ");
label->setColor(0x0000FFFF);
mList.setEntry(Vector2u(0, 0), Vector2u(1, 1), label, false, ComponentListComponent::AlignRight, Vector2<bool>(true, true));
mLabels.push_back(label);
//drawFramerate switch
mList.setEntry(Vector2u(1, 0), Vector2u(1, 1), &mDrawFramerateSwitch, true, ComponentListComponent::AlignCenter, Vector2<bool>(true, true));
label = new TextComponent(mWindow);
label->setText("Volume: ");
label->setColor(0x0000FFFF);
mList.setEntry(Vector2u(0, 1), Vector2u(1, 1), label, false, ComponentListComponent::AlignRight, Vector2<bool>(true, true));
//volume slider
SliderComponent* slider = new SliderComponent(mWindow, 0, 1);
mList.setEntry(Vector2u(1, 1), Vector2u(1, 1), slider, true, ComponentListComponent::AlignCenter, Vector2<bool>(true, true));
label = new TextComponent(mWindow);
label->setText("B TO CLOSE");
label->setColor(0x00FF00FF);
mList.setEntry(Vector2u(0, 2), Vector2u(2, 1), label, true, ComponentListComponent::AlignCenter, Vector2<bool>(false, true));
mLabels.push_back(label);
mList.setOffset(Renderer::getScreenWidth() / 2 - mList.getSize().x / 2, 0);
loadStates();
}
GuiSettingsMenu::~GuiSettingsMenu()
{
for(auto iter = mLabels.begin(); iter != mLabels.end(); iter++)
{
delete *iter;
}
}
bool GuiSettingsMenu::input(InputConfig* config, Input input)
{
//let our children (read: list) go first
if(GuiComponent::input(config, input))
return true;
if(config->isMappedTo("b", input) && input.value)
{
delete this;
return true;
}
return false;
}
void GuiSettingsMenu::loadStates()
{
Settings* s = Settings::getInstance();
mDrawFramerateSwitch.setState(s->getBool("DRAWFRAMERATE"));
}
#ifndef _SETTINGSMENU_H_
#define _SETTINGSMENU_H_
#include "../GuiComponent.h"
#include "ComponentListComponent.h"
#include <vector>
#include "SwitchComponent.h"
#include "TextComponent.h"
class GuiSettingsMenu : public GuiComponent
{
public:
GuiSettingsMenu(Window* window);
~GuiSettingsMenu();
bool input(InputConfig* config, Input input) override;
private:
void loadStates();
void applyStates();
ComponentListComponent mList;
SwitchComponent mDrawFramerateSwitch;
std::vector<GuiComponent*> mLabels;
};
#endif
#include "SliderComponent.h"
#include <assert.h>
#include "../Renderer.h"
SliderComponent::SliderComponent(Window* window, float min, float max) : GuiComponent(window),
mMin(min), mMax(max), mMoveRate(0)
{
assert((min - max) != 0);
mValue = (max + min) / 2;
//calculate move scale
mMoveScale = (max - min) * 0.0007f;
setSize(Vector2u(128, 32));
}
bool SliderComponent::input(InputConfig* config, Input input)
{
if(config->isMappedTo("left", input))
{
if(input.value)
mMoveRate = -1;
else
mMoveRate = 0;
return true;
}
if(config->isMappedTo("right", input))
{
if(input.value)
mMoveRate = 1;
else
mMoveRate = 0;
return true;
}
return GuiComponent::input(config, input);
}
void SliderComponent::update(int deltaTime)
{
mValue += mMoveRate * deltaTime * mMoveScale;
if(mValue < mMin)
mValue = mMin;
if(mValue > mMax)
mValue = mMax;
GuiComponent::update(deltaTime);
}
void SliderComponent::onRender()
{
//render line
const int lineWidth = 2;
Renderer::drawRect(0, mSize.y / 2 - lineWidth / 2, mSize.x, lineWidth, 0x000000CC);
//render left end
const int capWidth = (int)(mSize.x * 0.03f);
Renderer::drawRect(0, 0, capWidth, mSize.y, 0x000000CC);