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
|
|
|
|
|
|
|
FlexboxComponent::FlexboxComponent(Window* window,
|
|
|
|
std::vector<std::pair<std::string, ImageComponent>>& images)
|
|
|
|
: GuiComponent{window}
|
|
|
|
, mImages(images)
|
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}}
|
|
|
|
, 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"))
|
|
|
|
Renderer::drawRect(0.0f, 0.0f, mSize.x, mSize.y, 0xFF000033, 0xFF000033);
|
|
|
|
|
|
|
|
for (auto& image : mImages) {
|
|
|
|
if (mOpacity == 255) {
|
|
|
|
image.second.render(trans);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
image.second.setOpacity(mOpacity);
|
|
|
|
image.second.render(trans);
|
|
|
|
image.second.setOpacity(255);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-10-11 19:28:37 +00:00
|
|
|
float anchorX{0.0f};
|
|
|
|
float anchorY{0.0f};
|
2021-09-13 23:01:46 +00:00
|
|
|
|
|
|
|
// Translation directions when placing items.
|
2021-10-12 20:53:02 +00:00
|
|
|
glm::vec2 directionLine{1, 0};
|
|
|
|
glm::vec2 directionRow{0, 1};
|
2021-09-13 23:01:46 +00:00
|
|
|
|
|
|
|
// Change direction.
|
2021-10-12 20:53:02 +00:00
|
|
|
if (mDirection == "column") {
|
2021-09-13 23:01:46 +00:00
|
|
|
directionLine = {0, 1};
|
|
|
|
directionRow = {1, 0};
|
|
|
|
}
|
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-11 19:28:37 +00:00
|
|
|
// Compute maximum image dimensions.
|
2021-10-09 15:04:04 +00:00
|
|
|
glm::vec2 grid;
|
2021-10-12 20:53:02 +00:00
|
|
|
if (mDirection == "row")
|
2021-10-09 15:04:04 +00:00
|
|
|
grid = {mItemsPerLine, mLines};
|
|
|
|
else
|
|
|
|
grid = {mLines, mItemsPerLine};
|
2021-10-13 16:18:23 +00:00
|
|
|
|
2021-10-11 19:28:37 +00:00
|
|
|
glm::vec2 maxItemSize{(mSize + mItemMargin - grid * mItemMargin) / grid};
|
2021-10-13 16:18:23 +00:00
|
|
|
maxItemSize.x = floorf(maxItemSize.x);
|
|
|
|
maxItemSize.y = floorf(maxItemSize.y);
|
2021-10-09 15:04:04 +00:00
|
|
|
|
2021-10-12 20:53:02 +00:00
|
|
|
if (grid.x * grid.y < static_cast<float>(mImages.size())) {
|
|
|
|
LOG(LogWarning) << "FlexboxComponent: Invalid theme configuration, the number of badges "
|
|
|
|
"exceeds the product of <lines> times <itemsPerLine>";
|
|
|
|
}
|
|
|
|
|
2021-10-13 16:18:23 +00:00
|
|
|
glm::vec2 sizeChange{0.0f, 0.0f};
|
|
|
|
|
2021-10-11 19:28:37 +00:00
|
|
|
// Set final image dimensions.
|
|
|
|
for (auto& image : mImages) {
|
|
|
|
if (!image.second.isVisible())
|
|
|
|
continue;
|
|
|
|
auto oldSize{image.second.getSize()};
|
2021-09-13 23:01:46 +00:00
|
|
|
if (oldSize.x == 0)
|
2021-10-09 15:04:04 +00:00
|
|
|
oldSize.x = maxItemSize.x;
|
2021-10-11 19:28:37 +00:00
|
|
|
glm::vec2 sizeMaxX{maxItemSize.x, oldSize.y * (maxItemSize.x / oldSize.x)};
|
|
|
|
glm::vec2 sizeMaxY{oldSize.x * (maxItemSize.y / oldSize.y), maxItemSize.y};
|
2021-10-10 11:29:26 +00:00
|
|
|
glm::vec2 newSize;
|
|
|
|
if (sizeMaxX.y > maxItemSize.y)
|
|
|
|
newSize = sizeMaxY;
|
|
|
|
else if (sizeMaxY.x > maxItemSize.x)
|
|
|
|
newSize = sizeMaxX;
|
|
|
|
else
|
|
|
|
newSize = sizeMaxX.x * sizeMaxX.y >= sizeMaxY.x * sizeMaxY.y ? sizeMaxX : sizeMaxY;
|
2021-10-13 16:18:23 +00:00
|
|
|
|
|
|
|
if (image.second.getSize() != newSize)
|
2021-10-17 16:45:21 +00:00
|
|
|
image.second.setResize(std::round(newSize.x), std::round(newSize.y));
|
2021-10-13 16:18:23 +00:00
|
|
|
|
|
|
|
// In case maxItemSize needs to be updated.
|
|
|
|
if (newSize.x != sizeChange.x)
|
|
|
|
sizeChange.x = newSize.x;
|
|
|
|
if (newSize.y != sizeChange.y)
|
|
|
|
sizeChange.y = newSize.y;
|
2021-09-13 23:01:46 +00:00
|
|
|
}
|
2021-09-07 15:21:54 +00:00
|
|
|
|
2021-10-13 16:18:23 +00:00
|
|
|
if (maxItemSize.x != sizeChange.x)
|
2021-10-17 16:45:21 +00:00
|
|
|
maxItemSize.x = std::round(sizeChange.x);
|
2021-10-13 16:18:23 +00:00
|
|
|
if (maxItemSize.y != sizeChange.y)
|
2021-10-17 16:45:21 +00:00
|
|
|
maxItemSize.y = std::round(sizeChange.y);
|
2021-10-13 16:18:23 +00:00
|
|
|
|
2021-09-23 22:26:41 +00:00
|
|
|
// Pre-compute layout parameters.
|
2021-10-11 19:28:37 +00:00
|
|
|
float anchorXStart{anchorX};
|
|
|
|
float anchorYStart{anchorY};
|
|
|
|
|
|
|
|
int i = 0;
|
2021-09-13 23:01:46 +00:00
|
|
|
|
2021-10-11 19:28:37 +00:00
|
|
|
// Iterate through the images.
|
|
|
|
for (auto& image : mImages) {
|
|
|
|
if (!image.second.isVisible())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
auto size{image.second.getSize()};
|
2021-09-13 23:01:46 +00:00
|
|
|
|
|
|
|
// Top-left anchor position.
|
2021-10-12 20:53:02 +00:00
|
|
|
float x{anchorX};
|
|
|
|
float y{anchorY};
|
2021-09-13 23:01:46 +00:00
|
|
|
|
2021-10-12 20:53:02 +00:00
|
|
|
// Apply alignment.
|
|
|
|
if (mItemPlacement == "end") {
|
2021-09-13 23:01:46 +00:00
|
|
|
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
|
|
|
}
|
2021-10-12 20:53:02 +00:00
|
|
|
else if (mItemPlacement == "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
|
|
|
}
|
2021-10-12 20:53:02 +00:00
|
|
|
else if (mItemPlacement == "stretch" && mDirection == "row") {
|
2021-10-11 19:28:37 +00:00
|
|
|
image.second.setSize(image.second.getSize().x, maxItemSize.y);
|
2021-09-07 15:21:54 +00:00
|
|
|
}
|
|
|
|
|
2021-10-12 20:53:02 +00:00
|
|
|
// Apply overall container alignment.
|
|
|
|
if (mAlignment == "right")
|
2021-10-15 19:28:12 +00:00
|
|
|
x += (mSize.x - maxItemSize.x * grid.x - (grid.x - 1) * mItemMargin.x);
|
2021-09-23 22:26:41 +00:00
|
|
|
|
2021-09-13 23:01:46 +00:00
|
|
|
// Store final item position.
|
2021-10-12 20:53:02 +00:00
|
|
|
image.second.setPosition(x, y);
|
2021-09-07 15:21:54 +00:00
|
|
|
|
2021-09-13 23:01:46 +00:00
|
|
|
// Translate anchor.
|
2021-10-11 19:28:37 +00:00
|
|
|
if ((i++ + 1) % std::max(1, static_cast<int>(mItemsPerLine)) != 0) {
|
2021-09-13 23:01:46 +00:00
|
|
|
// Translate on same line.
|
2021-10-10 11:29:26 +00:00
|
|
|
anchorX += (size.x + mItemMargin.x) * static_cast<float>(directionLine.x);
|
|
|
|
anchorY += (size.y + mItemMargin.y) * static_cast<float>(directionLine.y);
|
2021-09-13 23:01:46 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Translate to first position of next line.
|
|
|
|
if (directionRow.x == 0) {
|
2021-10-12 20:53:02 +00:00
|
|
|
anchorY += size.y + mItemMargin.y;
|
2021-09-13 23:01:46 +00:00
|
|
|
anchorX = anchorXStart;
|
2021-09-27 19:06:07 +00:00
|
|
|
}
|
|
|
|
else {
|
2021-10-12 20:53:02 +00:00
|
|
|
anchorX += size.x + mItemMargin.x;
|
2021-09-13 23:01:46 +00:00
|
|
|
anchorY = anchorYStart;
|
2021-09-07 15:21:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-09-23 22:05:32 +00:00
|
|
|
|
2021-10-17 16:45:21 +00:00
|
|
|
// Apply right-align to the images on the last row, if needed.
|
2021-10-15 19:28:12 +00:00
|
|
|
if (mAlignment == "right") {
|
2021-10-17 16:45:21 +00:00
|
|
|
std::vector<ImageComponent*> imagesToAlign;
|
2021-10-15 19:28:12 +00:00
|
|
|
for (auto& image : mImages) {
|
|
|
|
if (!image.second.isVisible())
|
|
|
|
continue;
|
2021-10-17 16:45:21 +00:00
|
|
|
// Only include images on the last row.
|
|
|
|
if (image.second.getPosition().y == anchorY)
|
|
|
|
imagesToAlign.push_back(&image.second);
|
|
|
|
}
|
|
|
|
for (auto& moveImage : imagesToAlign) {
|
|
|
|
float offset = (maxItemSize.x + mItemMargin.x) * (grid.x - imagesToAlign.size());
|
|
|
|
moveImage->setPosition(moveImage->getPosition().x + offset, moveImage->getPosition().y);
|
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
|
|
|
}
|