2021-09-07 15:21:54 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
//
|
|
|
|
// EmulationStation Desktop Edition
|
|
|
|
// FlexboxComponent.cpp
|
|
|
|
//
|
|
|
|
// Flexbox layout component.
|
|
|
|
//
|
|
|
|
|
2021-10-12 20:53:02 +00:00
|
|
|
#define DEFAULT_DIRECTION "row"
|
|
|
|
#define DEFAULT_ALIGNMENT "left"
|
2021-10-11 19:28:37 +00:00
|
|
|
#define DEFAULT_ITEMS_PER_LINE 4
|
2021-10-12 20:53:02 +00:00
|
|
|
#define DEFAULT_LINES 2
|
|
|
|
#define DEFAULT_ITEM_PLACEMENT "center"
|
2021-10-13 16:18:23 +00:00
|
|
|
#define DEFAULT_MARGIN_X std::roundf(0.01f * Renderer::getScreenWidth())
|
|
|
|
#define DEFAULT_MARGIN_Y std::roundf(0.01f * Renderer::getScreenHeight())
|
2021-10-11 19:28:37 +00:00
|
|
|
|
2021-09-07 15:21:54 +00:00
|
|
|
#include "components/FlexboxComponent.h"
|
2021-10-11 19:28:37 +00:00
|
|
|
|
2021-10-12 20:53:02 +00:00
|
|
|
#include "Settings.h"
|
2021-09-07 15:21:54 +00:00
|
|
|
#include "ThemeData.h"
|
2021-10-11 19:28:37 +00:00
|
|
|
|
2021-10-23 17:08:32 +00:00
|
|
|
FlexboxComponent::FlexboxComponent(Window* window, std::vector<FlexboxItem>& items)
|
2021-10-11 19:28:37 +00:00
|
|
|
: GuiComponent{window}
|
2021-10-23 17:08:32 +00:00
|
|
|
, mItems(items)
|
2021-10-12 20:53:02 +00:00
|
|
|
, mDirection{DEFAULT_DIRECTION}
|
|
|
|
, mAlignment{DEFAULT_ALIGNMENT}
|
2021-10-11 19:28:37 +00:00
|
|
|
, mItemsPerLine{DEFAULT_ITEMS_PER_LINE}
|
|
|
|
, mLines{DEFAULT_LINES}
|
2021-10-12 20:53:02 +00:00
|
|
|
, mItemPlacement{DEFAULT_ITEM_PLACEMENT}
|
2021-10-11 19:28:37 +00:00
|
|
|
, mItemMargin{glm::vec2{DEFAULT_MARGIN_X, DEFAULT_MARGIN_Y}}
|
2021-10-23 17:08:32 +00:00
|
|
|
, mOverlayPosition{0.5f, 0.5f}
|
|
|
|
, mOverlaySize{0.5f}
|
2021-10-11 19:28:37 +00:00
|
|
|
, mLayoutValid{false}
|
2021-09-07 15:21:54 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2021-10-13 16:18:23 +00:00
|
|
|
void FlexboxComponent::render(const glm::mat4& parentTrans)
|
|
|
|
{
|
|
|
|
if (!isVisible())
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!mLayoutValid)
|
|
|
|
computeLayout();
|
|
|
|
|
|
|
|
glm::mat4 trans{parentTrans * getTransform()};
|
|
|
|
Renderer::setMatrix(trans);
|
|
|
|
|
|
|
|
if (Settings::getInstance()->getBool("DebugImage"))
|
2021-10-23 17:08:32 +00:00
|
|
|
Renderer::drawRect(0.0f, 0.0f, ceilf(mSize.x), ceilf(mSize.y), 0xFF000033, 0xFF000033);
|
2021-10-13 16:18:23 +00:00
|
|
|
|
2021-10-23 17:08:32 +00:00
|
|
|
for (auto& item : mItems) {
|
|
|
|
if (!item.visible)
|
|
|
|
continue;
|
2021-10-13 16:18:23 +00:00
|
|
|
if (mOpacity == 255) {
|
2021-10-23 17:08:32 +00:00
|
|
|
item.baseImage.render(trans);
|
|
|
|
if (item.overlayImage.getTexture() != nullptr)
|
|
|
|
item.overlayImage.render(trans);
|
2021-10-13 16:18:23 +00:00
|
|
|
}
|
|
|
|
else {
|
2021-10-23 17:08:32 +00:00
|
|
|
item.baseImage.setOpacity(mOpacity);
|
|
|
|
item.baseImage.render(trans);
|
|
|
|
item.baseImage.setOpacity(255);
|
|
|
|
if (item.overlayImage.getTexture() != nullptr) {
|
|
|
|
item.overlayImage.setOpacity(mOpacity);
|
|
|
|
item.overlayImage.render(trans);
|
|
|
|
item.overlayImage.setOpacity(255);
|
|
|
|
}
|
2021-10-13 16:18:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-23 17:08:32 +00:00
|
|
|
void FlexboxComponent::setItemMargin(glm::vec2 value)
|
|
|
|
{
|
|
|
|
if (value.x == -1.0f)
|
|
|
|
mItemMargin.x = std::roundf(value.y * Renderer::getScreenHeight());
|
|
|
|
else
|
|
|
|
mItemMargin.x = std::roundf(value.x * Renderer::getScreenWidth());
|
|
|
|
|
|
|
|
if (value.y == -1.0f)
|
|
|
|
mItemMargin.y = std::roundf(value.x * Renderer::getScreenWidth());
|
|
|
|
else
|
|
|
|
mItemMargin.y = std::roundf(value.y * Renderer::getScreenHeight());
|
|
|
|
|
|
|
|
mLayoutValid = false;
|
|
|
|
}
|
|
|
|
|
2021-09-13 23:01:46 +00:00
|
|
|
void FlexboxComponent::computeLayout()
|
|
|
|
{
|
2021-10-23 17:08:32 +00:00
|
|
|
// TODO: There is no right-alignment support for column mode.
|
2021-09-07 15:21:54 +00:00
|
|
|
|
2021-10-13 16:18:23 +00:00
|
|
|
// If we're not clamping itemMargin to a reasonable value, all kinds of weird rendering
|
|
|
|
// issues could occur.
|
|
|
|
mItemMargin.x = glm::clamp(mItemMargin.x, 0.0f, mSize.x / 2.0f);
|
|
|
|
mItemMargin.y = glm::clamp(mItemMargin.y, 0.0f, mSize.y / 2.0f);
|
|
|
|
|
|
|
|
// Also keep the size within reason.
|
|
|
|
mSize.x = glm::clamp(mSize.x, static_cast<float>(Renderer::getScreenWidth()) * 0.03f,
|
|
|
|
static_cast<float>(Renderer::getScreenWidth()));
|
|
|
|
mSize.y = glm::clamp(mSize.y, static_cast<float>(Renderer::getScreenHeight()) * 0.03f,
|
|
|
|
static_cast<float>(Renderer::getScreenHeight()));
|
|
|
|
|
2021-10-23 17:08:32 +00:00
|
|
|
if (mItemsPerLine * mLines < mItems.size()) {
|
|
|
|
LOG(LogWarning)
|
|
|
|
<< "FlexboxComponent: Invalid theme configuration, the number of badges"
|
|
|
|
" exceeds the product of <lines> times <itemsPerLine>, setting <itemsPerLine> to "
|
|
|
|
<< mItems.size();
|
|
|
|
mItemsPerLine = static_cast<unsigned int>(mItems.size());
|
|
|
|
}
|
2021-10-13 16:18:23 +00:00
|
|
|
|
2021-10-23 17:08:32 +00:00
|
|
|
glm::vec2 grid{mItemsPerLine, mLines};
|
2021-10-11 19:28:37 +00:00
|
|
|
glm::vec2 maxItemSize{(mSize + mItemMargin - grid * mItemMargin) / grid};
|
2021-10-09 15:04:04 +00:00
|
|
|
|
2021-10-23 17:08:32 +00:00
|
|
|
float rowHeight{0.0f};
|
|
|
|
bool firstItem{true};
|
2021-10-12 20:53:02 +00:00
|
|
|
|
2021-10-23 17:08:32 +00:00
|
|
|
// Calculate maximum item dimensions.
|
|
|
|
for (auto& item : mItems) {
|
|
|
|
if (!item.visible)
|
2021-10-11 19:28:37 +00:00
|
|
|
continue;
|
2021-09-07 15:21:54 +00:00
|
|
|
|
2021-10-23 17:08:32 +00:00
|
|
|
glm::vec2 sizeDiff{item.baseImage.getSize() / maxItemSize};
|
2021-10-13 16:18:23 +00:00
|
|
|
|
2021-10-23 17:08:32 +00:00
|
|
|
// The first item dictates the maximum width for the rest.
|
|
|
|
if (firstItem) {
|
|
|
|
maxItemSize.x = (item.baseImage.getSize() / std::max(sizeDiff.x, sizeDiff.y)).x;
|
|
|
|
sizeDiff = item.baseImage.getSize() / maxItemSize;
|
|
|
|
item.baseImage.setSize((item.baseImage.getSize() / std::max(sizeDiff.x, sizeDiff.y)));
|
|
|
|
firstItem = false;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
item.baseImage.setSize((item.baseImage.getSize() / std::max(sizeDiff.x, sizeDiff.y)));
|
|
|
|
}
|
2021-10-11 19:28:37 +00:00
|
|
|
|
2021-10-23 17:08:32 +00:00
|
|
|
if (item.baseImage.getSize().y > rowHeight)
|
|
|
|
rowHeight = item.baseImage.getSize().y;
|
|
|
|
}
|
2021-09-13 23:01:46 +00:00
|
|
|
|
2021-10-23 17:08:32 +00:00
|
|
|
// Update the maximum item height.
|
|
|
|
maxItemSize.y = 0.0f;
|
|
|
|
for (auto& item : mItems) {
|
|
|
|
if (!item.visible)
|
2021-10-11 19:28:37 +00:00
|
|
|
continue;
|
2021-10-23 17:08:32 +00:00
|
|
|
if (item.baseImage.getSize().y > maxItemSize.y)
|
|
|
|
maxItemSize.y = item.baseImage.getSize().y;
|
|
|
|
}
|
2021-10-11 19:28:37 +00:00
|
|
|
|
2021-10-23 17:08:32 +00:00
|
|
|
maxItemSize = glm::round(maxItemSize);
|
2021-09-13 23:01:46 +00:00
|
|
|
|
2021-10-23 17:08:32 +00:00
|
|
|
bool alignRight{mAlignment == "right" && mDirection == "row"};
|
|
|
|
float alignRightComp{0.0f};
|
2021-09-13 23:01:46 +00:00
|
|
|
|
2021-10-23 17:08:32 +00:00
|
|
|
// If right-aligning, move the overall container contents during grid setup.
|
|
|
|
if (alignRight)
|
|
|
|
alignRightComp =
|
|
|
|
std::round(mSize.x - ((maxItemSize.x + mItemMargin.x) * grid.x) + mItemMargin.x);
|
|
|
|
|
|
|
|
std::vector<glm::vec2> itemPositions;
|
|
|
|
|
|
|
|
// Lay out the grid.
|
|
|
|
if (mDirection == "row") {
|
|
|
|
for (int y = 0; y < grid.y; y++) {
|
|
|
|
for (int x = 0; x < grid.x; x++) {
|
|
|
|
itemPositions.push_back(
|
|
|
|
glm::vec2{(x * (maxItemSize.x + mItemMargin.x) + alignRightComp),
|
|
|
|
y * (rowHeight + mItemMargin.y)});
|
|
|
|
}
|
2021-09-27 19:06:07 +00:00
|
|
|
}
|
2021-10-23 17:08:32 +00:00
|
|
|
}
|
|
|
|
else { // Column mode.
|
|
|
|
for (int x = 0; x < grid.x; x++) {
|
|
|
|
for (int y = 0; y < grid.y; y++) {
|
|
|
|
itemPositions.push_back(
|
|
|
|
glm::vec2{(x * (maxItemSize.x + mItemMargin.x) + alignRightComp),
|
|
|
|
y * (rowHeight + mItemMargin.y)});
|
|
|
|
}
|
2021-09-07 15:21:54 +00:00
|
|
|
}
|
2021-10-23 17:08:32 +00:00
|
|
|
}
|
2021-09-07 15:21:54 +00:00
|
|
|
|
2021-10-23 17:08:32 +00:00
|
|
|
int pos{0};
|
|
|
|
float lastY{0.0f};
|
|
|
|
float itemsOnLastRow{0};
|
2021-09-23 22:26:41 +00:00
|
|
|
|
2021-10-23 17:08:32 +00:00
|
|
|
// Position items on the grid.
|
|
|
|
for (auto& item : mItems) {
|
|
|
|
if (!item.visible)
|
|
|
|
continue;
|
2021-09-07 15:21:54 +00:00
|
|
|
|
2021-10-23 17:08:32 +00:00
|
|
|
if (pos > 0) {
|
|
|
|
if (itemPositions[pos - 1].y < itemPositions[pos].y) {
|
|
|
|
lastY = itemPositions[pos].y;
|
|
|
|
itemsOnLastRow = 0;
|
|
|
|
}
|
2021-09-13 23:01:46 +00:00
|
|
|
}
|
2021-10-23 17:08:32 +00:00
|
|
|
|
|
|
|
float verticalOffset{0.0f};
|
|
|
|
|
|
|
|
// For any items that do not fill the maximum height, position these either on
|
|
|
|
// top/start (implicit), center or bottom/end.
|
|
|
|
if (item.baseImage.getSize().y < maxItemSize.y) {
|
|
|
|
if (mItemPlacement == "center") {
|
|
|
|
verticalOffset = std::floor((maxItemSize.y - item.baseImage.getSize().y) / 2.0f);
|
2021-09-27 19:06:07 +00:00
|
|
|
}
|
2021-10-23 17:08:32 +00:00
|
|
|
else if (mItemPlacement == "end") {
|
|
|
|
verticalOffset = maxItemSize.y - item.baseImage.getSize().y;
|
2021-09-07 15:21:54 +00:00
|
|
|
}
|
|
|
|
}
|
2021-10-23 17:08:32 +00:00
|
|
|
|
|
|
|
item.baseImage.setPosition(itemPositions[pos].x, itemPositions[pos].y + verticalOffset,
|
|
|
|
0.0f);
|
|
|
|
|
|
|
|
// Optional overlay image.
|
|
|
|
if (item.overlayImage.getTexture() != nullptr) {
|
|
|
|
item.overlayImage.setResize(item.baseImage.getSize().x * mOverlaySize, 0.0f);
|
|
|
|
item.overlayImage.setPosition(
|
|
|
|
item.baseImage.getPosition().x + (item.baseImage.getSize().x * mOverlayPosition.x) -
|
|
|
|
item.overlayImage.getSize().x / 2.0f,
|
|
|
|
item.baseImage.getPosition().y + (item.baseImage.getSize().y * mOverlayPosition.y) -
|
|
|
|
item.overlayImage.getSize().y / 2.0f);
|
|
|
|
}
|
|
|
|
|
|
|
|
// This rasterizes the SVG images so they look nice and smooth.
|
|
|
|
item.baseImage.setResize(item.baseImage.getSize());
|
|
|
|
|
|
|
|
itemsOnLastRow++;
|
|
|
|
pos++;
|
2021-09-07 15:21:54 +00:00
|
|
|
}
|
2021-09-23 22:05:32 +00:00
|
|
|
|
2021-10-23 17:08:32 +00:00
|
|
|
// Apply right-align to the items (only works in row mode).
|
|
|
|
if (alignRight) {
|
|
|
|
for (auto& item : mItems) {
|
|
|
|
if (!item.visible)
|
2021-10-15 19:28:12 +00:00
|
|
|
continue;
|
2021-10-23 17:08:32 +00:00
|
|
|
glm::vec3 currPos{item.baseImage.getPosition()};
|
|
|
|
if (currPos.y == lastY) {
|
|
|
|
const float offset{(grid.x - itemsOnLastRow) * (maxItemSize.x + mItemMargin.x)};
|
|
|
|
item.baseImage.setPosition(currPos.x + offset, currPos.y, currPos.z);
|
|
|
|
if (item.overlayImage.getTexture() != nullptr) {
|
|
|
|
currPos = item.overlayImage.getPosition();
|
|
|
|
item.overlayImage.setPosition(currPos.x + offset, currPos.y, currPos.z);
|
|
|
|
}
|
|
|
|
}
|
2021-10-15 19:28:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-23 22:05:32 +00:00
|
|
|
mLayoutValid = true;
|
2021-09-07 15:21:54 +00:00
|
|
|
}
|