2014-06-20 01:30:09 +00:00
|
|
|
#include "components/ComponentList.h"
|
2017-11-01 22:21:10 +00:00
|
|
|
|
|
|
|
#include "Renderer.h"
|
2014-03-01 21:02:44 +00:00
|
|
|
|
2014-03-04 22:48:33 +00:00
|
|
|
#define TOTAL_HORIZONTAL_PADDING_PX 20
|
2014-03-01 21:02:44 +00:00
|
|
|
|
|
|
|
ComponentList::ComponentList(Window* window) : IList<ComponentListRow, void*>(window, LIST_SCROLL_STYLE_SLOW, LIST_NEVER_LOOP)
|
|
|
|
{
|
|
|
|
mSelectorBarOffset = 0;
|
2014-03-02 16:41:02 +00:00
|
|
|
mCameraOffset = 0;
|
2014-03-12 03:00:08 +00:00
|
|
|
mFocused = false;
|
2014-03-01 21:02:44 +00:00
|
|
|
}
|
|
|
|
|
2014-03-06 19:45:03 +00:00
|
|
|
void ComponentList::addRow(const ComponentListRow& row, bool setCursorHere)
|
2014-03-01 21:02:44 +00:00
|
|
|
{
|
|
|
|
IList<ComponentListRow, void*>::Entry e;
|
|
|
|
e.name = "";
|
|
|
|
e.object = NULL;
|
|
|
|
e.data = row;
|
|
|
|
|
|
|
|
this->add(e);
|
|
|
|
|
2017-11-11 14:56:22 +00:00
|
|
|
for(auto it = mEntries.back().data.elements.cbegin(); it != mEntries.back().data.elements.cend(); it++)
|
2014-03-01 21:02:44 +00:00
|
|
|
addChild(it->component.get());
|
|
|
|
|
|
|
|
updateElementSize(mEntries.back().data);
|
|
|
|
updateElementPosition(mEntries.back().data);
|
2014-03-06 19:45:03 +00:00
|
|
|
|
|
|
|
if(setCursorHere)
|
|
|
|
{
|
|
|
|
mCursor = mEntries.size() - 1;
|
|
|
|
onCursorChanged(CURSOR_STOPPED);
|
|
|
|
}
|
2014-03-01 21:02:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ComponentList::onSizeChanged()
|
|
|
|
{
|
2017-11-11 14:56:22 +00:00
|
|
|
for(auto it = mEntries.cbegin(); it != mEntries.cend(); it++)
|
2014-03-01 21:02:44 +00:00
|
|
|
{
|
|
|
|
updateElementSize(it->data);
|
|
|
|
updateElementPosition(it->data);
|
|
|
|
}
|
2014-03-02 18:36:23 +00:00
|
|
|
|
2014-03-24 22:55:36 +00:00
|
|
|
updateCameraOffset();
|
2014-03-01 21:02:44 +00:00
|
|
|
}
|
|
|
|
|
2014-03-12 03:00:08 +00:00
|
|
|
void ComponentList::onFocusLost()
|
|
|
|
{
|
|
|
|
mFocused = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ComponentList::onFocusGained()
|
|
|
|
{
|
|
|
|
mFocused = true;
|
|
|
|
}
|
|
|
|
|
2014-03-01 21:02:44 +00:00
|
|
|
bool ComponentList::input(InputConfig* config, Input input)
|
|
|
|
{
|
|
|
|
if(size() == 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// give it to the current row's input handler
|
2014-03-02 18:36:23 +00:00
|
|
|
if(mEntries.at(mCursor).data.input_handler)
|
|
|
|
{
|
|
|
|
if(mEntries.at(mCursor).data.input_handler(config, input))
|
|
|
|
return true;
|
|
|
|
}else{
|
|
|
|
// no input handler assigned, do the default, which is to give it to the rightmost element in the row
|
|
|
|
auto& row = mEntries.at(mCursor).data;
|
|
|
|
if(row.elements.size())
|
|
|
|
{
|
|
|
|
if(row.elements.back().component->input(config, input))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2014-03-01 21:02:44 +00:00
|
|
|
|
|
|
|
// input handler didn't consume the input - try to scroll
|
|
|
|
if(config->isMappedTo("up", input))
|
|
|
|
{
|
|
|
|
return listInput(input.value != 0 ? -1 : 0);
|
|
|
|
}else if(config->isMappedTo("down", input))
|
|
|
|
{
|
|
|
|
return listInput(input.value != 0 ? 1 : 0);
|
2017-03-18 17:54:39 +00:00
|
|
|
|
2017-04-04 02:06:07 +00:00
|
|
|
}else if(config->isMappedTo("pageup", input))
|
2014-06-06 20:06:56 +00:00
|
|
|
{
|
2017-03-18 17:54:39 +00:00
|
|
|
return listInput(input.value != 0 ? -6 : 0);
|
2017-04-04 02:06:07 +00:00
|
|
|
}else if(config->isMappedTo("pagedown", input)){
|
2017-03-18 17:54:39 +00:00
|
|
|
return listInput(input.value != 0 ? 6 : 0);
|
2014-03-01 21:02:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ComponentList::update(int deltaTime)
|
|
|
|
{
|
|
|
|
listUpdate(deltaTime);
|
2014-03-02 18:36:23 +00:00
|
|
|
|
|
|
|
if(size())
|
|
|
|
{
|
|
|
|
// update our currently selected row
|
2017-11-11 14:56:22 +00:00
|
|
|
for(auto it = mEntries.at(mCursor).data.elements.cbegin(); it != mEntries.at(mCursor).data.elements.cend(); it++)
|
2014-03-02 18:36:23 +00:00
|
|
|
it->component->update(deltaTime);
|
|
|
|
}
|
2014-03-01 21:02:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ComponentList::onCursorChanged(const CursorState& state)
|
|
|
|
{
|
|
|
|
// update the selector bar position
|
|
|
|
// in the future this might be animated
|
|
|
|
mSelectorBarOffset = 0;
|
|
|
|
for(int i = 0; i < mCursor; i++)
|
|
|
|
{
|
|
|
|
mSelectorBarOffset += getRowHeight(mEntries.at(i).data);
|
|
|
|
}
|
2014-03-02 16:41:02 +00:00
|
|
|
|
2014-03-24 22:55:36 +00:00
|
|
|
updateCameraOffset();
|
|
|
|
|
|
|
|
// this is terribly inefficient but we don't know what we came from so...
|
|
|
|
if(size())
|
|
|
|
{
|
2017-11-11 14:56:22 +00:00
|
|
|
for(auto it = mEntries.cbegin(); it != mEntries.cend(); it++)
|
2014-03-24 22:55:36 +00:00
|
|
|
it->data.elements.back().component->onFocusLost();
|
2017-05-18 10:16:57 +00:00
|
|
|
|
2014-03-24 22:55:36 +00:00
|
|
|
mEntries.at(mCursor).data.elements.back().component->onFocusGained();
|
|
|
|
}
|
|
|
|
|
|
|
|
if(mCursorChangedCallback)
|
|
|
|
mCursorChangedCallback(state);
|
|
|
|
|
|
|
|
updateHelpPrompts();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ComponentList::updateCameraOffset()
|
|
|
|
{
|
2014-03-02 18:36:23 +00:00
|
|
|
// move the camera to scroll
|
|
|
|
const float totalHeight = getTotalRowHeight();
|
|
|
|
if(totalHeight > mSize.y())
|
|
|
|
{
|
2014-03-24 20:34:38 +00:00
|
|
|
float target = mSelectorBarOffset + getRowHeight(mEntries.at(mCursor).data)/2 - (mSize.y() / 2);
|
|
|
|
|
|
|
|
// clamp it
|
|
|
|
mCameraOffset = 0;
|
|
|
|
unsigned int i = 0;
|
|
|
|
while(mCameraOffset < target && i < mEntries.size())
|
|
|
|
{
|
|
|
|
mCameraOffset += getRowHeight(mEntries.at(i).data);
|
|
|
|
i++;
|
|
|
|
}
|
2014-03-02 16:41:02 +00:00
|
|
|
|
2014-03-02 18:36:23 +00:00
|
|
|
if(mCameraOffset < 0)
|
|
|
|
mCameraOffset = 0;
|
|
|
|
else if(mCameraOffset + mSize.y() > totalHeight)
|
|
|
|
mCameraOffset = totalHeight - mSize.y();
|
2014-03-19 00:55:37 +00:00
|
|
|
}else{
|
|
|
|
mCameraOffset = 0;
|
2014-03-02 18:36:23 +00:00
|
|
|
}
|
2014-03-01 21:02:44 +00:00
|
|
|
}
|
|
|
|
|
2017-10-28 20:24:35 +00:00
|
|
|
void ComponentList::render(const Transform4x4f& parentTrans)
|
2014-03-01 21:02:44 +00:00
|
|
|
{
|
2014-03-06 01:49:32 +00:00
|
|
|
if(!size())
|
|
|
|
return;
|
|
|
|
|
2017-10-28 20:24:35 +00:00
|
|
|
Transform4x4f trans = parentTrans * getTransform();
|
|
|
|
trans.round();
|
2014-03-19 20:03:23 +00:00
|
|
|
|
2014-03-02 16:41:02 +00:00
|
|
|
// clip everything to be inside our bounds
|
2017-10-28 20:24:35 +00:00
|
|
|
Vector3f dim(mSize.x(), mSize.y(), 0);
|
2014-03-01 21:02:44 +00:00
|
|
|
dim = trans * dim - trans.translation();
|
2017-10-28 20:24:35 +00:00
|
|
|
Renderer::pushClipRect(Vector2i((int)trans.translation().x(), (int)trans.translation().y()),
|
2017-11-13 22:16:38 +00:00
|
|
|
Vector2i((int)Math::round(dim.x()), (int)Math::round(dim.y() + 1)));
|
2014-03-01 21:02:44 +00:00
|
|
|
|
2014-03-02 16:41:02 +00:00
|
|
|
// scroll the camera
|
2017-11-13 22:16:38 +00:00
|
|
|
trans.translate(Vector3f(0, -Math::round(mCameraOffset), 0));
|
2014-03-02 16:41:02 +00:00
|
|
|
|
2014-03-01 21:02:44 +00:00
|
|
|
// draw our entries
|
2014-03-21 19:51:25 +00:00
|
|
|
std::vector<GuiComponent*> drawAfterCursor;
|
|
|
|
bool drawAll;
|
|
|
|
for(unsigned int i = 0; i < mEntries.size(); i++)
|
|
|
|
{
|
|
|
|
auto& entry = mEntries.at(i);
|
2017-10-28 20:07:31 +00:00
|
|
|
drawAll = !mFocused || i != (unsigned int)mCursor;
|
2017-11-11 14:56:22 +00:00
|
|
|
for(auto it = entry.data.elements.cbegin(); it != entry.data.elements.cend(); it++)
|
2014-03-21 19:51:25 +00:00
|
|
|
{
|
|
|
|
if(drawAll || it->invert_when_selected)
|
|
|
|
{
|
|
|
|
it->component->render(trans);
|
|
|
|
}else{
|
|
|
|
drawAfterCursor.push_back(it->component.get());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-03-01 21:02:44 +00:00
|
|
|
|
2014-03-12 03:00:08 +00:00
|
|
|
// custom rendering
|
2014-03-01 21:02:44 +00:00
|
|
|
Renderer::setMatrix(trans);
|
|
|
|
|
2014-03-12 03:00:08 +00:00
|
|
|
// draw selector bar
|
|
|
|
if(mFocused)
|
|
|
|
{
|
|
|
|
// inversion: src * (1 - dst) + dst * 0 = where src = 1
|
|
|
|
// need a function that goes roughly 0x777777 -> 0xFFFFFF
|
|
|
|
// and 0xFFFFFF -> 0x777777
|
|
|
|
// (1 - dst) + 0x77
|
2017-05-18 10:16:57 +00:00
|
|
|
|
2014-03-12 03:00:08 +00:00
|
|
|
const float selectedRowHeight = getRowHeight(mEntries.at(mCursor).data);
|
2014-03-19 20:03:23 +00:00
|
|
|
Renderer::drawRect(0.0f, mSelectorBarOffset, mSize.x(), selectedRowHeight, 0xFFFFFFFF,
|
2014-03-12 03:00:08 +00:00
|
|
|
GL_ONE_MINUS_DST_COLOR, GL_ZERO);
|
2014-03-19 20:03:23 +00:00
|
|
|
Renderer::drawRect(0.0f, mSelectorBarOffset, mSize.x(), selectedRowHeight, 0x777777FF,
|
2014-03-12 03:00:08 +00:00
|
|
|
GL_ONE, GL_ONE);
|
2017-05-18 10:16:57 +00:00
|
|
|
|
2014-03-12 03:00:08 +00:00
|
|
|
// hack to draw 2px dark on left/right of the bar
|
2014-03-19 20:03:23 +00:00
|
|
|
Renderer::drawRect(0.0f, mSelectorBarOffset, 2.0f, selectedRowHeight, 0x878787FF);
|
|
|
|
Renderer::drawRect(mSize.x() - 2.0f, mSelectorBarOffset, 2.0f, selectedRowHeight, 0x878787FF);
|
2014-03-21 19:51:25 +00:00
|
|
|
|
2017-11-11 14:56:22 +00:00
|
|
|
for(auto it = drawAfterCursor.cbegin(); it != drawAfterCursor.cend(); it++)
|
2014-03-21 19:51:25 +00:00
|
|
|
(*it)->render(trans);
|
2017-05-18 10:16:57 +00:00
|
|
|
|
2014-03-21 19:51:25 +00:00
|
|
|
// reset matrix if one of these components changed it
|
|
|
|
if(drawAfterCursor.size())
|
|
|
|
Renderer::setMatrix(trans);
|
2014-03-12 03:00:08 +00:00
|
|
|
}
|
2014-03-04 22:48:33 +00:00
|
|
|
|
2014-03-01 21:02:44 +00:00
|
|
|
// draw separators
|
|
|
|
float y = 0;
|
|
|
|
for(unsigned int i = 0; i < mEntries.size(); i++)
|
|
|
|
{
|
2014-03-19 20:03:23 +00:00
|
|
|
Renderer::drawRect(0.0f, y, mSize.x(), 1.0f, 0xC6C7C6FF);
|
2014-03-01 21:02:44 +00:00
|
|
|
y += getRowHeight(mEntries.at(i).data);
|
|
|
|
}
|
2014-03-19 20:03:23 +00:00
|
|
|
Renderer::drawRect(0.0f, y, mSize.x(), 1.0f, 0xC6C7C6FF);
|
2014-03-02 16:41:02 +00:00
|
|
|
|
|
|
|
Renderer::popClipRect();
|
2014-03-01 21:02:44 +00:00
|
|
|
}
|
|
|
|
|
2014-03-02 16:41:02 +00:00
|
|
|
float ComponentList::getRowHeight(const ComponentListRow& row) const
|
2014-03-01 21:02:44 +00:00
|
|
|
{
|
|
|
|
// returns the highest component height found in the row
|
|
|
|
float height = 0;
|
|
|
|
for(unsigned int i = 0; i < row.elements.size(); i++)
|
|
|
|
{
|
|
|
|
if(row.elements.at(i).component->getSize().y() > height)
|
|
|
|
height = row.elements.at(i).component->getSize().y();
|
|
|
|
}
|
|
|
|
|
|
|
|
return height;
|
|
|
|
}
|
|
|
|
|
2014-03-02 16:41:02 +00:00
|
|
|
float ComponentList::getTotalRowHeight() const
|
|
|
|
{
|
|
|
|
float height = 0;
|
2017-11-11 14:56:22 +00:00
|
|
|
for(auto it = mEntries.cbegin(); it != mEntries.cend(); it++)
|
2014-03-02 16:41:02 +00:00
|
|
|
{
|
|
|
|
height += getRowHeight(it->data);
|
|
|
|
}
|
|
|
|
|
|
|
|
return height;
|
|
|
|
}
|
|
|
|
|
2014-03-01 21:02:44 +00:00
|
|
|
void ComponentList::updateElementPosition(const ComponentListRow& row)
|
|
|
|
{
|
|
|
|
float yOffset = 0;
|
2017-11-11 14:56:22 +00:00
|
|
|
for(auto it = mEntries.cbegin(); it != mEntries.cend() && &it->data != &row; it++)
|
2014-03-01 21:02:44 +00:00
|
|
|
{
|
|
|
|
yOffset += getRowHeight(it->data);
|
|
|
|
}
|
|
|
|
|
|
|
|
// assumes updateElementSize has already been called
|
|
|
|
float rowHeight = getRowHeight(row);
|
|
|
|
|
|
|
|
float x = TOTAL_HORIZONTAL_PADDING_PX / 2;
|
|
|
|
for(unsigned int i = 0; i < row.elements.size(); i++)
|
|
|
|
{
|
|
|
|
const auto comp = row.elements.at(i).component;
|
|
|
|
|
|
|
|
// center vertically
|
|
|
|
comp->setPosition(x, (rowHeight - comp->getSize().y()) / 2 + yOffset);
|
|
|
|
x += comp->getSize().x();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ComponentList::updateElementSize(const ComponentListRow& row)
|
|
|
|
{
|
|
|
|
float width = mSize.x() - TOTAL_HORIZONTAL_PADDING_PX;
|
|
|
|
std::vector< std::shared_ptr<GuiComponent> > resizeVec;
|
|
|
|
|
2017-11-11 14:56:22 +00:00
|
|
|
for(auto it = row.elements.cbegin(); it != row.elements.cend(); it++)
|
2014-03-01 21:02:44 +00:00
|
|
|
{
|
|
|
|
if(it->resize_width)
|
|
|
|
resizeVec.push_back(it->component);
|
|
|
|
else
|
|
|
|
width -= it->component->getSize().x();
|
|
|
|
}
|
|
|
|
|
|
|
|
// redistribute the "unused" width equally among the components with resize_width set to true
|
|
|
|
width = width / resizeVec.size();
|
2017-11-11 14:56:22 +00:00
|
|
|
for(auto it = resizeVec.cbegin(); it != resizeVec.cend(); it++)
|
2014-03-01 21:02:44 +00:00
|
|
|
{
|
|
|
|
(*it)->setSize(width, (*it)->getSize().y());
|
|
|
|
}
|
|
|
|
}
|
2014-03-13 19:09:50 +00:00
|
|
|
|
2014-03-21 02:47:45 +00:00
|
|
|
void ComponentList::textInput(const char* text)
|
|
|
|
{
|
|
|
|
if(!size())
|
|
|
|
return;
|
|
|
|
|
|
|
|
mEntries.at(mCursor).data.elements.back().component->textInput(text);
|
|
|
|
}
|
|
|
|
|
2014-03-13 19:09:50 +00:00
|
|
|
std::vector<HelpPrompt> ComponentList::getHelpPrompts()
|
|
|
|
{
|
|
|
|
if(!size())
|
|
|
|
return std::vector<HelpPrompt>();
|
|
|
|
|
2014-03-24 01:33:27 +00:00
|
|
|
std::vector<HelpPrompt> prompts = mEntries.at(mCursor).data.elements.back().component->getHelpPrompts();
|
|
|
|
|
|
|
|
if(size() > 1)
|
|
|
|
{
|
|
|
|
bool addMovePrompt = true;
|
2017-11-11 14:56:22 +00:00
|
|
|
for(auto it = prompts.cbegin(); it != prompts.cend(); it++)
|
2014-03-24 01:33:27 +00:00
|
|
|
{
|
2017-08-22 23:21:33 +00:00
|
|
|
if(it->first == "up/down" || it->first == "up/down/left/right")
|
2014-03-24 01:33:27 +00:00
|
|
|
{
|
|
|
|
addMovePrompt = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(addMovePrompt)
|
2014-05-16 21:21:33 +00:00
|
|
|
prompts.push_back(HelpPrompt("up/down", "choose"));
|
2014-03-24 01:33:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return prompts;
|
2014-03-13 19:09:50 +00:00
|
|
|
}
|
2014-03-22 01:12:57 +00:00
|
|
|
|
|
|
|
bool ComponentList::moveCursor(int amt)
|
|
|
|
{
|
2017-05-18 10:16:57 +00:00
|
|
|
bool ret = listInput(amt);
|
|
|
|
listInput(0);
|
2014-03-22 01:12:57 +00:00
|
|
|
return ret;
|
|
|
|
}
|