2021-09-07 15:21:54 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
//
|
|
|
|
// EmulationStation Desktop Edition
|
|
|
|
// FlexboxComponent.cpp
|
|
|
|
//
|
|
|
|
// Flexbox layout component.
|
|
|
|
// Used by gamelist views.
|
|
|
|
//
|
|
|
|
|
|
|
|
#include "components/FlexboxComponent.h"
|
|
|
|
#include <numeric>
|
|
|
|
|
|
|
|
#include "Settings.h"
|
|
|
|
#include "ThemeData.h"
|
|
|
|
#include "resources/TextureResource.h"
|
|
|
|
|
2021-09-13 23:01:46 +00:00
|
|
|
FlexboxComponent::FlexboxComponent(Window* window)
|
2021-09-07 15:21:54 +00:00
|
|
|
: GuiComponent(window)
|
|
|
|
, mDirection(DEFAULT_DIRECTION)
|
|
|
|
, mAlign(DEFAULT_ALIGN)
|
2021-09-13 23:01:46 +00:00
|
|
|
, mItemsPerLine(DEFAULT_ITEMS_PER_LINE)
|
|
|
|
, mItemWidth(DEFAULT_ITEM_SIZE_X)
|
2021-09-07 15:21:54 +00:00
|
|
|
{
|
2021-09-13 23:01:46 +00:00
|
|
|
// Initialize item margins.
|
|
|
|
mItemMargin = glm::vec2{DEFAULT_MARGIN_X, DEFAULT_MARGIN_Y};
|
2021-09-07 15:21:54 +00:00
|
|
|
|
2021-09-23 22:05:32 +00:00
|
|
|
// Layout validity
|
|
|
|
mLayoutValid = false;
|
2021-09-07 15:21:54 +00:00
|
|
|
}
|
|
|
|
|
2021-09-13 23:01:46 +00:00
|
|
|
// Getters/Setters for rendering options.
|
|
|
|
void FlexboxComponent::setDirection(std::string value)
|
2021-09-07 15:21:54 +00:00
|
|
|
{
|
2021-09-13 23:01:46 +00:00
|
|
|
mDirection = value;
|
2021-09-23 22:05:32 +00:00
|
|
|
mLayoutValid = false;
|
2021-09-07 15:21:54 +00:00
|
|
|
}
|
2021-09-13 23:01:46 +00:00
|
|
|
std::string FlexboxComponent::getDirection() { return mDirection; }
|
|
|
|
void FlexboxComponent::setAlign(std::string value)
|
2021-09-07 15:21:54 +00:00
|
|
|
{
|
2021-09-13 23:01:46 +00:00
|
|
|
mAlign = value;
|
2021-09-23 22:05:32 +00:00
|
|
|
mLayoutValid = false;
|
2021-09-13 23:01:46 +00:00
|
|
|
}
|
|
|
|
std::string FlexboxComponent::getAlign() { return mAlign; }
|
|
|
|
void FlexboxComponent::setItemsPerLine(unsigned int value)
|
|
|
|
{
|
|
|
|
mItemsPerLine = value;
|
2021-09-23 22:05:32 +00:00
|
|
|
mLayoutValid = false;
|
2021-09-13 23:01:46 +00:00
|
|
|
}
|
|
|
|
unsigned int FlexboxComponent::getItemsPerLine() { return mItemsPerLine; }
|
|
|
|
void FlexboxComponent::setItemMargin(glm::vec2 value)
|
|
|
|
{
|
|
|
|
mItemMargin = value;
|
2021-09-23 22:05:32 +00:00
|
|
|
mLayoutValid = false;
|
2021-09-13 23:01:46 +00:00
|
|
|
}
|
|
|
|
glm::vec2 FlexboxComponent::getItemMargin() { return mItemMargin; }
|
|
|
|
void FlexboxComponent::setItemWidth(float value)
|
|
|
|
{
|
|
|
|
mItemWidth = value;
|
2021-09-23 22:05:32 +00:00
|
|
|
mLayoutValid = false;
|
2021-09-13 23:01:46 +00:00
|
|
|
}
|
|
|
|
float FlexboxComponent::getItemWidth() { return mItemWidth; }
|
2021-09-07 15:21:54 +00:00
|
|
|
|
2021-09-27 19:06:07 +00:00
|
|
|
void FlexboxComponent::onSizeChanged() { mLayoutValid = false; }
|
2021-09-07 15:21:54 +00:00
|
|
|
|
2021-09-13 23:01:46 +00:00
|
|
|
void FlexboxComponent::computeLayout()
|
|
|
|
{
|
2021-09-23 22:26:41 +00:00
|
|
|
// Start placing items in the top-left.
|
2021-09-13 23:01:46 +00:00
|
|
|
float anchorX = 0;
|
|
|
|
float anchorY = 0;
|
|
|
|
float anchorOriginX = 0;
|
|
|
|
float anchorOriginY = 0;
|
|
|
|
|
|
|
|
// Translation directions when placing items.
|
2021-09-27 20:18:19 +00:00
|
|
|
glm::ivec2 directionLine = {1, 0};
|
|
|
|
glm::ivec2 directionRow = {0, 1};
|
2021-09-13 23:01:46 +00:00
|
|
|
|
|
|
|
// Change direction.
|
|
|
|
if (mDirection == DIRECTION_COLUMN) {
|
|
|
|
directionLine = {0, 1};
|
|
|
|
directionRow = {1, 0};
|
|
|
|
}
|
2021-09-07 15:21:54 +00:00
|
|
|
|
2021-09-13 23:01:46 +00:00
|
|
|
// Set children sizes.
|
|
|
|
glm::vec2 maxItemSize = {0.0f, 0.0f};
|
|
|
|
for (auto i : mChildren) {
|
|
|
|
auto oldSize = i->getSize();
|
|
|
|
if (oldSize.x == 0)
|
|
|
|
oldSize.x = DEFAULT_ITEM_SIZE_X;
|
|
|
|
glm::vec2 newSize = {mItemWidth, oldSize.y * (mItemWidth / oldSize.x)};
|
|
|
|
i->setSize(newSize);
|
|
|
|
maxItemSize = {std::max(maxItemSize.x, newSize.x), std::max(maxItemSize.y, newSize.y)};
|
2021-09-27 20:18:19 +00:00
|
|
|
i->setResize(maxItemSize.x, maxItemSize.y);
|
2021-09-13 23:01:46 +00:00
|
|
|
}
|
2021-09-07 15:21:54 +00:00
|
|
|
|
2021-09-23 22:26:41 +00:00
|
|
|
// Pre-compute layout parameters.
|
2021-09-13 23:01:46 +00:00
|
|
|
int n = mChildren.size();
|
2021-09-27 19:06:07 +00:00
|
|
|
int nLines = std::max(1, (int)std::ceil(n / std::max(1, (int)mItemsPerLine)));
|
2021-09-13 23:01:46 +00:00
|
|
|
float lineWidth =
|
2021-09-27 19:06:07 +00:00
|
|
|
(mDirection == "row" ? (maxItemSize.y + mItemMargin.y) : (maxItemSize.x + mItemMargin.x));
|
2021-09-13 23:01:46 +00:00
|
|
|
float anchorXStart = anchorX;
|
|
|
|
float anchorYStart = anchorY;
|
|
|
|
|
2021-09-23 22:26:41 +00:00
|
|
|
// Compute total container size.
|
2021-09-26 00:17:07 +00:00
|
|
|
glm::vec2 totalSize = {-mItemMargin.x, -mItemMargin.y};
|
2021-09-23 22:26:41 +00:00
|
|
|
if (mDirection == "row") {
|
|
|
|
totalSize.x += (mItemMargin.x + mItemWidth) * mItemsPerLine;
|
|
|
|
totalSize.y += (mItemMargin.y + maxItemSize.y) * nLines;
|
2021-09-27 19:06:07 +00:00
|
|
|
}
|
|
|
|
else {
|
2021-09-23 22:26:41 +00:00
|
|
|
totalSize.x += (mItemMargin.x + mItemWidth) * nLines;
|
|
|
|
totalSize.y += (mItemMargin.y + maxItemSize.y) * mItemsPerLine;
|
|
|
|
}
|
|
|
|
|
2021-09-13 23:01:46 +00:00
|
|
|
// Iterate through the children.
|
|
|
|
for (int i = 0; i < n; i++) {
|
2021-09-27 19:06:07 +00:00
|
|
|
GuiComponent* child = mChildren[i];
|
2021-09-13 23:01:46 +00:00
|
|
|
auto size = child->getSize();
|
|
|
|
|
|
|
|
// Top-left anchor position.
|
|
|
|
float x = anchorX - anchorOriginX * size.x;
|
|
|
|
float y = anchorY - anchorOriginY * size.y;
|
|
|
|
|
|
|
|
// Apply item margin.
|
2021-09-26 20:59:14 +00:00
|
|
|
/*if ((mDirection == "row" && i % std::max(1, (int) mItemsPerLine) != 0) ||
|
2021-09-26 00:17:07 +00:00
|
|
|
(mDirection == "column" && i >= (int) mItemsPerLine))
|
|
|
|
x += mItemMargin.x * (directionLine.x >= 0.0f ? 1.0f : -1.0f);
|
|
|
|
if ((mDirection == "column" && i % std::max(1, (int) mItemsPerLine) != 0) ||
|
|
|
|
(mDirection == "row" && i >= (int) mItemsPerLine))
|
2021-09-26 20:59:14 +00:00
|
|
|
y += mItemMargin.y * (directionLine.y >= 0.0f ? 1.0f : -1.0f);*/
|
2021-09-13 23:01:46 +00:00
|
|
|
|
|
|
|
// Apply alignment
|
|
|
|
if (mAlign == ITEM_ALIGN_END) {
|
|
|
|
x += directionLine.x == 0 ? (maxItemSize.x - size.x) : 0;
|
|
|
|
y += directionLine.y == 0 ? (maxItemSize.y - size.y) : 0;
|
2021-09-27 19:06:07 +00:00
|
|
|
}
|
|
|
|
else if (mAlign == ITEM_ALIGN_CENTER) {
|
2021-09-13 23:01:46 +00:00
|
|
|
x += directionLine.x == 0 ? (maxItemSize.x - size.x) / 2 : 0;
|
|
|
|
y += directionLine.y == 0 ? (maxItemSize.y - size.y) / 2 : 0;
|
2021-09-27 19:06:07 +00:00
|
|
|
}
|
|
|
|
else if (mAlign == ITEM_ALIGN_STRETCH && mDirection == "row") {
|
2021-09-13 23:01:46 +00:00
|
|
|
child->setSize(child->getSize().x, maxItemSize.y);
|
2021-09-07 15:21:54 +00:00
|
|
|
}
|
|
|
|
|
2021-09-23 22:26:41 +00:00
|
|
|
// Apply origin.
|
|
|
|
if (mOrigin.x > 0 && mOrigin.x <= 1)
|
|
|
|
x -= mOrigin.x * totalSize.x;
|
|
|
|
if (mOrigin.y > 0 && mOrigin.y <= 1)
|
|
|
|
y -= mOrigin.y * totalSize.y;
|
|
|
|
|
2021-09-13 23:01:46 +00:00
|
|
|
// Store final item position.
|
|
|
|
child->setPosition(getPosition().x + x, getPosition().y + y);
|
2021-09-07 15:21:54 +00:00
|
|
|
|
2021-09-13 23:01:46 +00:00
|
|
|
// Translate anchor.
|
2021-09-27 19:06:07 +00:00
|
|
|
if ((i + 1) % std::max(1, (int)mItemsPerLine) != 0) {
|
2021-09-13 23:01:46 +00:00
|
|
|
// Translate on same line.
|
|
|
|
anchorX += (size.x + mItemMargin.x) * directionLine.x;
|
|
|
|
anchorY += (size.y + mItemMargin.y) * directionLine.y;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Translate to first position of next line.
|
|
|
|
if (directionRow.x == 0) {
|
|
|
|
anchorY += lineWidth * directionRow.y;
|
|
|
|
anchorX = anchorXStart;
|
2021-09-27 19:06:07 +00:00
|
|
|
}
|
|
|
|
else {
|
2021-09-13 23:01:46 +00:00
|
|
|
anchorX += lineWidth * directionRow.x;
|
|
|
|
anchorY = anchorYStart;
|
2021-09-07 15:21:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-09-23 22:05:32 +00:00
|
|
|
|
|
|
|
mLayoutValid = true;
|
2021-09-07 15:21:54 +00:00
|
|
|
}
|
|
|
|
|
2021-09-27 19:06:07 +00:00
|
|
|
void FlexboxComponent::render(const glm::mat4& parentTrans)
|
|
|
|
{
|
2021-09-07 15:21:54 +00:00
|
|
|
if (!isVisible())
|
|
|
|
return;
|
|
|
|
|
2021-09-23 22:05:32 +00:00
|
|
|
if (!mLayoutValid)
|
|
|
|
computeLayout();
|
|
|
|
|
2021-09-07 15:21:54 +00:00
|
|
|
renderChildren(parentTrans);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FlexboxComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
|
|
|
|
const std::string& view,
|
|
|
|
const std::string& element,
|
|
|
|
unsigned int properties)
|
|
|
|
{
|
|
|
|
using namespace ThemeFlags;
|
|
|
|
|
2021-09-13 23:01:46 +00:00
|
|
|
glm::vec2 scale{getParent() ? getParent()->getSize() :
|
|
|
|
glm::vec2{static_cast<float>(Renderer::getScreenWidth()),
|
|
|
|
static_cast<float>(Renderer::getScreenHeight())}};
|
|
|
|
|
2021-09-07 15:21:54 +00:00
|
|
|
// TODO: How to do this without explicit 'badges' property?
|
|
|
|
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "badges");
|
|
|
|
if (!elem)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (properties & DIRECTION && elem->has("direction"))
|
|
|
|
mDirection = elem->get<std::string>("direction");
|
|
|
|
|
|
|
|
if (elem->has("align"))
|
|
|
|
mAlign = elem->get<std::string>("align");
|
|
|
|
|
2021-09-13 23:01:46 +00:00
|
|
|
if (elem->has("itemsPerLine"))
|
|
|
|
mItemsPerLine = elem->get<float>("itemsPerLine");
|
|
|
|
|
|
|
|
if (elem->has("itemMargin"))
|
|
|
|
mItemMargin = elem->get<glm::vec2>("itemMargin");
|
|
|
|
|
|
|
|
if (elem->has("itemWidth"))
|
|
|
|
mItemWidth = elem->get<float>("itemWidth") * scale.x;
|
|
|
|
|
2021-09-07 15:21:54 +00:00
|
|
|
GuiComponent::applyTheme(theme, view, element, properties);
|
|
|
|
|
2021-09-23 22:05:32 +00:00
|
|
|
// Layout no longer valid.
|
|
|
|
mLayoutValid = false;
|
2021-09-07 15:21:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<HelpPrompt> FlexboxComponent::getHelpPrompts()
|
|
|
|
{
|
|
|
|
std::vector<HelpPrompt> prompts;
|
|
|
|
return prompts;
|
|
|
|
}
|