mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2025-01-30 03:55:40 +00:00
Separate flexbox functionality in it's own component.
This commit is contained in:
parent
fe413bb68f
commit
efe928852f
|
@ -41,6 +41,7 @@ set(CORE_HEADERS
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentList.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeEditComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/FlexboxComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GridTileComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/HelpComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/IList.h
|
||||
|
@ -116,6 +117,7 @@ set(CORE_SOURCES
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentList.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeComponent.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeEditComponent.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/FlexboxComponent.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GridTileComponent.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/HelpComponent.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageComponent.cpp
|
||||
|
|
|
@ -8,24 +8,16 @@
|
|||
//
|
||||
|
||||
#include "components/BadgesComponent.h"
|
||||
#include <numeric>
|
||||
|
||||
#include "Settings.h"
|
||||
#include "ThemeData.h"
|
||||
#include "resources/TextureResource.h"
|
||||
|
||||
BadgesComponent::BadgesComponent(Window* window)
|
||||
: GuiComponent(window)
|
||||
, mDirection(DEFAULT_DIRECTION)
|
||||
, mWrap(DEFAULT_WRAP)
|
||||
, mJustifyContent(DEFAULT_JUSTIFY_CONTENT)
|
||||
, mAlign(DEFAULT_ALIGN)
|
||||
: FlexboxComponent(window, NUM_SLOTS)
|
||||
{
|
||||
mSlots = std::vector<std::string>();
|
||||
mSlots.push_back(SLOT_FAVORITE);
|
||||
mSlots.push_back(SLOT_COMPLETED);
|
||||
mSlots.push_back(SLOT_KIDS);
|
||||
mSlots.push_back(SLOT_BROKEN);
|
||||
// Define the slots.
|
||||
setSlots({SLOT_FAVORITE, SLOT_COMPLETED, SLOT_KIDS, SLOT_BROKEN});
|
||||
|
||||
mBadgeIcons = std::map<std::string, std::string>();
|
||||
mBadgeIcons[SLOT_FAVORITE] = ":/graphics/badge_favorite.png";
|
||||
|
@ -33,33 +25,34 @@ BadgesComponent::BadgesComponent(Window* window)
|
|||
mBadgeIcons[SLOT_KIDS] = ":/graphics/badge_kidgame.png";
|
||||
mBadgeIcons[SLOT_BROKEN] = ":/graphics/badge_broken.png";
|
||||
|
||||
mTextures = std::map<std::string, std::shared_ptr<TextureResource>>();
|
||||
mTextures[SLOT_FAVORITE] = TextureResource::get(mBadgeIcons[SLOT_FAVORITE], true);
|
||||
mTextures[SLOT_COMPLETED] = TextureResource::get(mBadgeIcons[SLOT_COMPLETED], true);
|
||||
mTextures[SLOT_KIDS] = TextureResource::get(mBadgeIcons[SLOT_KIDS], true);
|
||||
mTextures[SLOT_BROKEN] = TextureResource::get(mBadgeIcons[SLOT_BROKEN], true);
|
||||
|
||||
mVertices = std::map<std::string, Renderer::Vertex[4]>();
|
||||
// Create the child ImageComponent for every badge.
|
||||
mImageComponents = std::map<std::string, ImageComponent>();
|
||||
ImageComponent mImageFavorite = ImageComponent(window);
|
||||
mImageFavorite.setImage(mBadgeIcons[SLOT_FAVORITE], false, false);
|
||||
mImageComponents.insert({SLOT_FAVORITE, mImageFavorite});
|
||||
ImageComponent mImageCompleted = ImageComponent(window);
|
||||
mImageCompleted.setImage(mBadgeIcons[SLOT_COMPLETED], false, false);
|
||||
mImageComponents.insert({SLOT_COMPLETED, mImageCompleted});
|
||||
ImageComponent mImageKids = ImageComponent(window);
|
||||
mImageKids.setImage(mBadgeIcons[SLOT_KIDS], false, false);
|
||||
mImageComponents.insert({SLOT_KIDS, mImageKids});
|
||||
ImageComponent mImageBroken = ImageComponent(window);
|
||||
mImageBroken.setImage(mBadgeIcons[SLOT_BROKEN], false, false);
|
||||
mImageComponents.insert({SLOT_BROKEN, mImageBroken});
|
||||
|
||||
// TODO: Should be dependent on the direction property.
|
||||
mSize = glm::vec2{64.0f * NUM_SLOTS, 64.0f};
|
||||
|
||||
// TODO: Add definition for default value.
|
||||
mMargin = glm::vec2{10.0f, 10.0f};
|
||||
|
||||
updateVertices();
|
||||
// Trigger initial layout computation.
|
||||
onSizeChanged();
|
||||
}
|
||||
|
||||
void BadgesComponent::setValue(const std::string& value)
|
||||
{
|
||||
if (value.empty()) {
|
||||
mSlots.clear();
|
||||
}
|
||||
else {
|
||||
// Start by clearing the slots.
|
||||
mSlots.clear();
|
||||
std::vector<std::string> slots = {};
|
||||
|
||||
// Interpret the value and iteratively fill mSlots. The value is a space separated list of
|
||||
if (!value.empty()) {
|
||||
// Interpret the value and iteratively fill slots. The value is a space separated list of
|
||||
// strings.
|
||||
std::string temp;
|
||||
std::istringstream ss(value);
|
||||
|
@ -68,271 +61,25 @@ void BadgesComponent::setValue(const std::string& value)
|
|||
temp == SLOT_BROKEN))
|
||||
LOG(LogError) << "Badge slot '" << temp << "' is invalid.";
|
||||
else
|
||||
mSlots.push_back(temp);
|
||||
slots.push_back(temp);
|
||||
}
|
||||
}
|
||||
|
||||
updateVertices();
|
||||
setSlots(slots);
|
||||
onSizeChanged();
|
||||
}
|
||||
|
||||
std::string BadgesComponent::getValue() const
|
||||
{
|
||||
const std::vector<std::string> slots = getSlots();
|
||||
std::stringstream ss;
|
||||
for (auto& slot : mSlots)
|
||||
for (auto& slot : slots)
|
||||
ss << slot << ' ';
|
||||
std::string r = ss.str();
|
||||
r.pop_back();
|
||||
return r;
|
||||
}
|
||||
|
||||
void BadgesComponent::onSizeChanged()
|
||||
{
|
||||
// TODO: Should be dependent on the direction property.
|
||||
if (mSize.y == 0.0f)
|
||||
mSize.y = mSize.x / NUM_SLOTS;
|
||||
else if (mSize.x == 0.0f)
|
||||
mSize.x = mSize.y * NUM_SLOTS;
|
||||
|
||||
if (mSize.y > 0.0f) {
|
||||
size_t heightPx = static_cast<size_t>(std::round(mSize.y));
|
||||
for (auto const& tex : mTextures)
|
||||
tex.second->rasterizeAt(heightPx, heightPx);
|
||||
}
|
||||
|
||||
updateVertices();
|
||||
}
|
||||
|
||||
void BadgesComponent::updateVertices()
|
||||
{
|
||||
mVertices.clear();
|
||||
|
||||
/*const float numSlots = mSlots.size();
|
||||
float s;
|
||||
if (mDirection == DIRECTION_ROW)
|
||||
s = std::min( getSize().x / numSlots, getSize().y );
|
||||
else
|
||||
s = std::min( getSize().y / numSlots, getSize().x );
|
||||
const long color = 4278190080;
|
||||
|
||||
int i = 0;
|
||||
for (auto & slot : mSlots)
|
||||
{
|
||||
// clang-format off
|
||||
mVertices[slot][0] = {{0.0f, 0.0f}, {0.0f, 1.0f}, color};
|
||||
mVertices[slot][1] = {{0.0f, s}, {0.0f, 0.0f}, color};
|
||||
mVertices[slot][2] = {{s , 0.0f}, {1.0f, 1.0f}, color};
|
||||
mVertices[slot][3] = {{s , s}, {1.0f, 0.0f}, color};
|
||||
// clang-format on
|
||||
i++;
|
||||
}*/
|
||||
|
||||
// The maximum number of badges to be displayed.
|
||||
const float numSlots = NUM_SLOTS;
|
||||
|
||||
// The available size to draw in.
|
||||
const auto size = getSize();
|
||||
|
||||
// Compute the number of rows and columns and the item max dimensions.
|
||||
int rows;
|
||||
int columns;
|
||||
float itemWidth;
|
||||
float itemHeight;
|
||||
|
||||
if (mDirection == DIRECTION_ROW) {
|
||||
if (mWrap != WRAP_NOWRAP) {
|
||||
// Suppose we have i rows, what would be the average area of an icon? Compute for a
|
||||
// small number of rows.
|
||||
std::vector<float> areas;
|
||||
for (int i = 1; i < 10; i++) {
|
||||
|
||||
float area = size.x * size.y;
|
||||
|
||||
// Number of vertical gaps.
|
||||
int verticalGaps = i - 1;
|
||||
|
||||
// Area of vertical gaps.
|
||||
area -= verticalGaps * mMargin.y * size.x;
|
||||
|
||||
// Height per item.
|
||||
float iHeight = (size.y - verticalGaps * mMargin.y) / i;
|
||||
|
||||
// Width per item. (Approximation)
|
||||
// TODO: this is an approximation!
|
||||
// Solve: area - (iHeight * (iWidth + mMargin.x) * numSlots) + mMargin.x * iHeight =
|
||||
// 0;
|
||||
float iWidth = ((area + mMargin.x * iHeight) / (iHeight * numSlots)) - mMargin.x;
|
||||
|
||||
// Average area available per badge
|
||||
float avgArea = iHeight * iWidth;
|
||||
|
||||
// Push to the areas array.
|
||||
areas.push_back(avgArea);
|
||||
}
|
||||
|
||||
// Determine the number of rows based on what results in the largest area per badge
|
||||
// based on available space.
|
||||
rows = std::max_element(areas.begin(), areas.end()) - areas.begin() + 1;
|
||||
|
||||
// Obtain final item dimensions.
|
||||
itemHeight = (size.y - (rows - 1) * mMargin.y) / rows;
|
||||
itemWidth = areas[rows - 1] / itemHeight;
|
||||
|
||||
// Compute number of columns.
|
||||
if (rows == 1)
|
||||
columns = NUM_SLOTS;
|
||||
else
|
||||
columns = std::round((size.x + mMargin.x) / (itemWidth + mMargin.x));
|
||||
}
|
||||
else {
|
||||
rows = 1;
|
||||
columns = NUM_SLOTS;
|
||||
itemHeight = size.y;
|
||||
itemWidth = size.x / (NUM_SLOTS + (NUM_SLOTS - 1) * mMargin.x);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// TODO: Add computation for column direction.
|
||||
}
|
||||
|
||||
const long color = 4278190080;
|
||||
if (mDirection == DIRECTION_ROW) {
|
||||
|
||||
// Start row.
|
||||
int row = mWrap == WRAP_REVERSE ? rows : 1;
|
||||
int item = 0;
|
||||
|
||||
// Iterate through all the rows.
|
||||
for (int c = 0; c < rows && item < mSlots.size(); c++) {
|
||||
|
||||
// Pre-compute dimensions of all items in this row.
|
||||
std::vector<float> widths;
|
||||
std::vector<float> heights;
|
||||
int itemTemp = item;
|
||||
for (int column = 0; column < columns && itemTemp < mSlots.size(); column++) {
|
||||
glm::vec texSize = mTextures[mSlots[itemTemp]]->getSize();
|
||||
float aspectRatioTexture = texSize.x / texSize.y;
|
||||
float aspectRatioItemSpace = itemWidth / itemHeight;
|
||||
if (aspectRatioTexture > aspectRatioItemSpace) {
|
||||
widths.push_back(itemWidth);
|
||||
heights.push_back(itemWidth / aspectRatioTexture);
|
||||
}
|
||||
else {
|
||||
widths.push_back(itemHeight * aspectRatioTexture);
|
||||
heights.push_back(itemHeight);
|
||||
}
|
||||
itemTemp++;
|
||||
}
|
||||
|
||||
// Iterate through the columns.
|
||||
float xpos = 0;
|
||||
for (int column = 0; column < columns && item < mSlots.size(); column++) {
|
||||
|
||||
// We always go from left to right.
|
||||
// Here we compute the coordinates of the items.
|
||||
|
||||
// Compute final badge x position.
|
||||
float x;
|
||||
float totalWidth =
|
||||
std::accumulate(widths.begin(), widths.end(), decltype(widths)::value_type(0)) +
|
||||
(widths.size() - 1) * mMargin.x;
|
||||
if (mJustifyContent == "start") {
|
||||
x = xpos;
|
||||
xpos += widths[column] + mMargin.x;
|
||||
}
|
||||
else if (mJustifyContent == "end") {
|
||||
if (column == 0)
|
||||
xpos += size.x - totalWidth;
|
||||
x = xpos;
|
||||
xpos += widths[column] + mMargin.x;
|
||||
}
|
||||
else if (mJustifyContent == "center") {
|
||||
if (column == 0)
|
||||
xpos += (size.x - totalWidth) / 2;
|
||||
x = xpos;
|
||||
xpos += widths[column] + mMargin.x;
|
||||
}
|
||||
else if (mJustifyContent == "space-between") {
|
||||
float gapSize = (size.x - totalWidth) / (widths.size() - 1);
|
||||
x = xpos;
|
||||
xpos += widths[column] + gapSize;
|
||||
}
|
||||
else if (mJustifyContent == "space-around") {
|
||||
float gapSize = (size.x - totalWidth) / (widths.size() - 1);
|
||||
xpos += gapSize / 2;
|
||||
x = xpos;
|
||||
xpos += widths[column] + gapSize / 2;
|
||||
}
|
||||
else if (mJustifyContent == "space-evenly") {
|
||||
float gapSize = (size.x - totalWidth) / (widths.size() + 1);
|
||||
xpos += gapSize;
|
||||
x = xpos;
|
||||
}
|
||||
|
||||
// Compute final badge y position.
|
||||
float y = row * itemHeight;
|
||||
if (mAlign == "end") {
|
||||
y += itemHeight - heights[column];
|
||||
}
|
||||
else if (mAlign == "center") {
|
||||
y += (itemHeight - heights[column]) / 2;
|
||||
}
|
||||
if (mAlign == "stretch") {
|
||||
heights[column] = itemHeight;
|
||||
}
|
||||
|
||||
LOG(LogError) << "Computed Final Item Position. Row: " << row
|
||||
<< ", Column: " << column << ", Item: " << item << ", pos: (" << x
|
||||
<< ", " << y << "), size: (" << widths[column] << ", "
|
||||
<< heights[column] << ")";
|
||||
|
||||
// Store the item's vertices and apply texture mapping.
|
||||
// clang-format off
|
||||
mVertices[mSlots[item]][0] = {{x, y}, {0.0f, 1.0f}, color};
|
||||
mVertices[mSlots[item]][1] = {{x, y+heights[column]}, {0.0f, 0.0f}, color};
|
||||
mVertices[mSlots[item]][2] = {{x+widths[column] , y}, {1.0f, 1.0f}, color};
|
||||
mVertices[mSlots[item]][3] = {{x+widths[column] , y+heights[column]}, {1.0f, 0.0f}, color};
|
||||
// clang-format on
|
||||
|
||||
// Increment item;
|
||||
item++;
|
||||
}
|
||||
|
||||
// Iterate the row.
|
||||
mWrap == WRAP_REVERSE ? row-- : row++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BadgesComponent::render(const glm::mat4& parentTrans)
|
||||
{
|
||||
if (!isVisible())
|
||||
return;
|
||||
|
||||
glm::mat4 trans{parentTrans * getTransform()};
|
||||
|
||||
Renderer::setMatrix(trans);
|
||||
|
||||
if (mOpacity > 0) {
|
||||
if (Settings::getInstance()->getBool("DebugImage"))
|
||||
Renderer::drawRect(0.0f, 0.0f, mSize.x, mSize.y, 0xFF000033, 0xFF000033);
|
||||
|
||||
for (auto& slot : mSlots) {
|
||||
if (mTextures[slot] == nullptr)
|
||||
continue;
|
||||
|
||||
if (mTextures[slot]->bind()) {
|
||||
Renderer::drawTriangleStrips(mVertices[slot], 4);
|
||||
Renderer::bindTexture(0);
|
||||
}
|
||||
|
||||
// TODO: update render matrix to position of next slot
|
||||
// trans = glm::translate(trans, {0.0f, 0.0f, 1.0f});
|
||||
}
|
||||
}
|
||||
|
||||
renderChildren(trans);
|
||||
}
|
||||
|
||||
void BadgesComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
|
||||
const std::string& view,
|
||||
const std::string& element,
|
||||
|
@ -345,30 +92,20 @@ void BadgesComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
|
|||
return;
|
||||
|
||||
bool imgChanged = false;
|
||||
for (auto& slot : mSlots) {
|
||||
const std::vector<std::string> slots = getSlots();
|
||||
for (auto& slot : slots) {
|
||||
if (properties & PATH && elem->has(slot)) {
|
||||
mBadgeIcons[slot] = elem->get<std::string>(slot);
|
||||
mTextures[slot] = TextureResource::get(mBadgeIcons[slot], true);
|
||||
mImageComponents.find(slot)->second.setImage(mBadgeIcons[slot]);
|
||||
imgChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (properties & DIRECTION && elem->has("direction"))
|
||||
mDirection = elem->get<std::string>("direction");
|
||||
|
||||
if (elem->has("wrap"))
|
||||
mWrap = elem->get<std::string>("wrap");
|
||||
|
||||
if (elem->has("justifyContent"))
|
||||
mJustifyContent = elem->get<std::string>("justifyContent");
|
||||
|
||||
if (elem->has("align"))
|
||||
mAlign = elem->get<std::string>("align");
|
||||
|
||||
if (elem->has("slots"))
|
||||
setValue(elem->get<std::string>("slots"));
|
||||
|
||||
GuiComponent::applyTheme(theme, view, element, properties);
|
||||
// Apply theme on the flexbox component parent.
|
||||
FlexboxComponent::applyTheme(theme, view, element, properties);
|
||||
|
||||
if (imgChanged)
|
||||
onSizeChanged();
|
||||
|
|
|
@ -10,39 +10,20 @@
|
|||
#ifndef ES_APP_COMPONENTS_BADGES_COMPONENT_H
|
||||
#define ES_APP_COMPONENTS_BADGES_COMPONENT_H
|
||||
|
||||
#include "FlexboxComponent.h"
|
||||
#include "GuiComponent.h"
|
||||
#include "ImageComponent.h"
|
||||
#include "renderers/Renderer.h"
|
||||
|
||||
#define DIRECTION_ROW "row"
|
||||
#define DIRECTION_COLUMN "column"
|
||||
#define WRAP_WRAP "wrap"
|
||||
#define WRAP_NOWRAP "nowrap"
|
||||
#define WRAP_REVERSE "wrap-reverse"
|
||||
#define JUSTIFY_CONTENT_START "start"
|
||||
#define JUSTIFY_CONTENT_END "end"
|
||||
#define JUSTIFY_CONTENT_CENTER "center"
|
||||
#define JUSTIFY_CONTENT_SPACE_BETWEEN "space-between"
|
||||
#define JUSTIFY_CONTENT_SPACE_AROUND "space-around"
|
||||
#define JUSTIFY_CONTENT_SPACE_EVENLY "space-evenly"
|
||||
#define ITEM_ALIGN_START "start"
|
||||
#define ITEM_ALIGN_END "end"
|
||||
#define ITEM_ALIGN_CENTER "center"
|
||||
#define ITEM_ALIGN_STRETCH "stretch"
|
||||
#define NUM_SLOTS 4
|
||||
#define SLOT_FAVORITE "favorite"
|
||||
#define SLOT_COMPLETED "completed"
|
||||
#define SLOT_KIDS "kidgame"
|
||||
#define SLOT_BROKEN "broken"
|
||||
#define DEFAULT_DIRECTION DIRECTION_ROW
|
||||
#define DEFAULT_WRAP WRAP_WRAP
|
||||
#define DEFAULT_JUSTIFY_CONTENT JUSTIFY_CONTENT_START
|
||||
#define DEFAULT_ALIGN ITEM_ALIGN_CENTER
|
||||
#define DEFAULT_MARGIN_X = 10.0f
|
||||
#define DEFAULT_MARGIN_Y = 10.0f
|
||||
|
||||
class TextureResource;
|
||||
|
||||
class BadgesComponent : public GuiComponent
|
||||
class BadgesComponent : public FlexboxComponent
|
||||
{
|
||||
public:
|
||||
BadgesComponent(Window* window);
|
||||
|
@ -51,18 +32,6 @@ public:
|
|||
// Should be a list of strings.
|
||||
void setValue(const std::string& value) override;
|
||||
|
||||
void render(const glm::mat4& parentTrans) override;
|
||||
|
||||
void onSizeChanged() override;
|
||||
|
||||
void setDirection(int direction);
|
||||
|
||||
int getDirection();
|
||||
|
||||
void setSlots(std::vector<std::string>);
|
||||
|
||||
std::vector<std::string> getSlots();
|
||||
|
||||
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme,
|
||||
const std::string& view,
|
||||
const std::string& element,
|
||||
|
@ -71,18 +40,8 @@ public:
|
|||
virtual std::vector<HelpPrompt> getHelpPrompts() override;
|
||||
|
||||
private:
|
||||
void updateVertices();
|
||||
std::map<std::string, Renderer::Vertex[4]> mVertices;
|
||||
|
||||
std::map<std::string, std::string> mBadgeIcons;
|
||||
std::map<std::string, std::shared_ptr<TextureResource>> mTextures;
|
||||
|
||||
std::string mDirection;
|
||||
std::string mWrap;
|
||||
std::string mJustifyContent;
|
||||
std::string mAlign;
|
||||
glm::vec2 mMargin;
|
||||
std::vector<std::string> mSlots;
|
||||
std::map<std::string, ImageComponent> mImageComponents;
|
||||
};
|
||||
|
||||
#endif // ES_APP_COMPONENTS_BADGES_COMPONENT_H
|
||||
|
|
281
es-core/src/components/FlexboxComponent.cpp
Normal file
281
es-core/src/components/FlexboxComponent.cpp
Normal file
|
@ -0,0 +1,281 @@
|
|||
// 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"
|
||||
|
||||
FlexboxComponent::FlexboxComponent(Window* window, unsigned int assumeChildren)
|
||||
: GuiComponent(window)
|
||||
, mDirection(DEFAULT_DIRECTION)
|
||||
, mWrap(DEFAULT_WRAP)
|
||||
, mJustifyContent(DEFAULT_JUSTIFY_CONTENT)
|
||||
, mAlign(DEFAULT_ALIGN)
|
||||
, mAssumeChildren(assumeChildren)
|
||||
{
|
||||
|
||||
// Initialize contents of the flexbox.
|
||||
mSlots = std::vector<std::string>();
|
||||
mComponents = std::map<std::string, GuiComponent>();
|
||||
|
||||
// Initialize flexbox layout.
|
||||
mVertices = std::map<std::string, glm::vec4>();
|
||||
|
||||
// TODO: Should be dependent on the direction property.
|
||||
mSize = glm::vec2{64.0f * mAssumeChildren, 64.0f};
|
||||
|
||||
// TODO: Add definition for default value.
|
||||
mMargin = glm::vec2{10.0f, 10.0f};
|
||||
|
||||
// Calculate flexbox layout.
|
||||
updateVertices();
|
||||
}
|
||||
|
||||
void FlexboxComponent::onSizeChanged()
|
||||
{
|
||||
// TODO: Should be dependent on the direction property.
|
||||
if (mSize.y == 0.0f)
|
||||
mSize.y = mSize.x / mAssumeChildren;
|
||||
else if (mSize.x == 0.0f)
|
||||
mSize.x = mSize.y * mAssumeChildren;
|
||||
|
||||
updateVertices();
|
||||
}
|
||||
|
||||
void FlexboxComponent::updateVertices()
|
||||
{
|
||||
// The maximum number of components to be displayed.
|
||||
const float numSlots = mAssumeChildren;
|
||||
|
||||
// The available size to draw in.
|
||||
const auto size = getSize();
|
||||
|
||||
// Compute the number of rows and columns and the item max dimensions.
|
||||
int rows;
|
||||
int columns;
|
||||
float itemWidth;
|
||||
float itemHeight;
|
||||
if (mDirection == DIRECTION_ROW) {
|
||||
if (mWrap != WRAP_NOWRAP) {
|
||||
// Suppose we have i rows, what would be the average area of an icon? Compute for a
|
||||
// small number of rows.
|
||||
std::vector<float> areas;
|
||||
for (int i = 1; i < 10; i++) {
|
||||
|
||||
float area = size.x * size.y;
|
||||
|
||||
// Number of vertical gaps.
|
||||
int verticalGaps = i - 1;
|
||||
|
||||
// Area of vertical gaps.
|
||||
area -= verticalGaps * mMargin.y * size.x;
|
||||
|
||||
// Height per item.
|
||||
float iHeight = (size.y - verticalGaps * mMargin.y) / i;
|
||||
|
||||
// Width per item. (Approximation)
|
||||
// TODO: this is an approximation!
|
||||
// Solve: area - (iHeight * (iWidth + mMargin.x) * numSlots) + mMargin.x * iHeight =
|
||||
// 0;
|
||||
float iWidth = ((area + mMargin.x * iHeight) / (iHeight * numSlots)) - mMargin.x;
|
||||
|
||||
// Average area available per badge
|
||||
float avgArea = iHeight * iWidth;
|
||||
|
||||
// Push to the areas array.
|
||||
areas.push_back(avgArea);
|
||||
}
|
||||
|
||||
// Determine the number of rows based on what results in the largest area per badge
|
||||
// based on available space.
|
||||
rows = std::max_element(areas.begin(), areas.end()) - areas.begin() + 1;
|
||||
|
||||
// Obtain final item dimensions.
|
||||
itemHeight = (size.y - (rows - 1) * mMargin.y) / rows;
|
||||
itemWidth = areas[rows - 1] / itemHeight;
|
||||
|
||||
// Compute number of columns.
|
||||
if (rows == 1)
|
||||
columns = mAssumeChildren;
|
||||
else
|
||||
columns = std::round((size.x + mMargin.x) / (itemWidth + mMargin.x));
|
||||
}
|
||||
else {
|
||||
rows = 1;
|
||||
columns = mAssumeChildren;
|
||||
itemHeight = size.y;
|
||||
itemWidth = size.x / (mAssumeChildren + (mAssumeChildren - 1) * mMargin.x);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// TODO: Add computation for column direction.
|
||||
}
|
||||
|
||||
// Compute the exact positions and sizes of the components.
|
||||
mVertices.clear();
|
||||
if (mDirection == DIRECTION_ROW) {
|
||||
|
||||
// Start row.
|
||||
int row = mWrap == WRAP_REVERSE ? rows : 1;
|
||||
int item = 0;
|
||||
|
||||
// Iterate through all the rows.
|
||||
for (int c = 0; c < rows && item < mSlots.size(); c++) {
|
||||
|
||||
// Pre-compute dimensions of all items in this row.
|
||||
std::vector<float> widths;
|
||||
std::vector<float> heights;
|
||||
int itemTemp = item;
|
||||
for (int column = 0; column < columns && itemTemp < mSlots.size(); column++) {
|
||||
glm::vec componentSize = mComponents.find(mSlots[itemTemp])->second.getSize();
|
||||
float aspectRatioTexture = componentSize.x / componentSize.y;
|
||||
float aspectRatioItemSpace = itemWidth / itemHeight;
|
||||
if (aspectRatioTexture > aspectRatioItemSpace) {
|
||||
widths.push_back(itemWidth);
|
||||
heights.push_back(itemWidth / aspectRatioTexture);
|
||||
}
|
||||
else {
|
||||
widths.push_back(itemHeight * aspectRatioTexture);
|
||||
heights.push_back(itemHeight);
|
||||
}
|
||||
itemTemp++;
|
||||
}
|
||||
|
||||
// Iterate through the columns.
|
||||
float xpos = 0;
|
||||
for (int column = 0; column < columns && item < mSlots.size(); column++) {
|
||||
|
||||
// We always go from left to right.
|
||||
// Here we compute the coordinates of the items.
|
||||
|
||||
// Compute final badge x position.
|
||||
float x;
|
||||
float totalWidth =
|
||||
std::accumulate(widths.begin(), widths.end(), decltype(widths)::value_type(0)) +
|
||||
(widths.size() - 1) * mMargin.x;
|
||||
if (mJustifyContent == "start") {
|
||||
x = xpos;
|
||||
xpos += widths[column] + mMargin.x;
|
||||
}
|
||||
else if (mJustifyContent == "end") {
|
||||
if (column == 0)
|
||||
xpos += size.x - totalWidth;
|
||||
x = xpos;
|
||||
xpos += widths[column] + mMargin.x;
|
||||
}
|
||||
else if (mJustifyContent == "center") {
|
||||
if (column == 0)
|
||||
xpos += (size.x - totalWidth) / 2;
|
||||
x = xpos;
|
||||
xpos += widths[column] + mMargin.x;
|
||||
}
|
||||
else if (mJustifyContent == "space-between") {
|
||||
float gapSize = (size.x - totalWidth) / (widths.size() - 1);
|
||||
x = xpos;
|
||||
xpos += widths[column] + gapSize;
|
||||
}
|
||||
else if (mJustifyContent == "space-around") {
|
||||
float gapSize = (size.x - totalWidth) / (widths.size() - 1);
|
||||
xpos += gapSize / 2;
|
||||
x = xpos;
|
||||
xpos += widths[column] + gapSize / 2;
|
||||
}
|
||||
else if (mJustifyContent == "space-evenly") {
|
||||
float gapSize = (size.x - totalWidth) / (widths.size() + 1);
|
||||
xpos += gapSize;
|
||||
x = xpos;
|
||||
}
|
||||
|
||||
// Compute final badge y position.
|
||||
float y = row * itemHeight;
|
||||
if (mAlign == "end") {
|
||||
y += itemHeight - heights[column];
|
||||
}
|
||||
else if (mAlign == "center") {
|
||||
y += (itemHeight - heights[column]) / 2;
|
||||
}
|
||||
if (mAlign == "stretch") {
|
||||
heights[column] = itemHeight;
|
||||
}
|
||||
|
||||
LOG(LogError) << "Computed Final Item Position. Row: " << row
|
||||
<< ", Column: " << column << ", Item: " << item << ", pos: (" << x
|
||||
<< ", " << y << "), size: (" << widths[column] << ", "
|
||||
<< heights[column] << ")";
|
||||
|
||||
// Store the item's layout.
|
||||
mVertices[mSlots[item]] = {x, y, widths[column], heights[column]};
|
||||
|
||||
// Increment item;
|
||||
item++;
|
||||
}
|
||||
|
||||
// Iterate the row.
|
||||
mWrap == WRAP_REVERSE ? row-- : row++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FlexboxComponent::render(const glm::mat4& parentTrans)
|
||||
{
|
||||
if (!isVisible())
|
||||
return;
|
||||
|
||||
// Render all the child components.
|
||||
for (unsigned int i = 0; i < mSlots.size(); i++) {
|
||||
glm::vec4 v = mVertices[mSlots[i]];
|
||||
auto c = mComponents.find(mSlots[i])->second;
|
||||
glm::vec2 oldSize = c.getSize();
|
||||
c.setPosition(v.x, v.y);
|
||||
c.setSize(v.z, v.w);
|
||||
c.render(parentTrans);
|
||||
c.setSize(oldSize);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// 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("wrap"))
|
||||
mWrap = elem->get<std::string>("wrap");
|
||||
|
||||
if (elem->has("justifyContent"))
|
||||
mJustifyContent = elem->get<std::string>("justifyContent");
|
||||
|
||||
if (elem->has("align"))
|
||||
mAlign = elem->get<std::string>("align");
|
||||
|
||||
GuiComponent::applyTheme(theme, view, element, properties);
|
||||
|
||||
// Trigger layout computation.
|
||||
onSizeChanged();
|
||||
}
|
||||
|
||||
std::vector<HelpPrompt> FlexboxComponent::getHelpPrompts()
|
||||
{
|
||||
std::vector<HelpPrompt> prompts;
|
||||
return prompts;
|
||||
}
|
85
es-core/src/components/FlexboxComponent.h
Normal file
85
es-core/src/components/FlexboxComponent.h
Normal file
|
@ -0,0 +1,85 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// EmulationStation Desktop Edition
|
||||
// FlexboxComponent.h
|
||||
//
|
||||
// Flexbox layout component.
|
||||
// Used by gamelist views.
|
||||
//
|
||||
|
||||
#ifndef ES_APP_COMPONENTS_FLEXBOX_COMPONENT_H
|
||||
#define ES_APP_COMPONENTS_FLEXBOX_COMPONENT_H
|
||||
|
||||
#include "GuiComponent.h"
|
||||
#include "renderers/Renderer.h"
|
||||
|
||||
#define DIRECTION_ROW "row"
|
||||
#define DIRECTION_COLUMN "column"
|
||||
#define WRAP_WRAP "wrap"
|
||||
#define WRAP_NOWRAP "nowrap"
|
||||
#define WRAP_REVERSE "wrap-reverse"
|
||||
#define JUSTIFY_CONTENT_START "start"
|
||||
#define JUSTIFY_CONTENT_END "end"
|
||||
#define JUSTIFY_CONTENT_CENTER "center"
|
||||
#define JUSTIFY_CONTENT_SPACE_BETWEEN "space-between"
|
||||
#define JUSTIFY_CONTENT_SPACE_AROUND "space-around"
|
||||
#define JUSTIFY_CONTENT_SPACE_EVENLY "space-evenly"
|
||||
#define ITEM_ALIGN_START "start"
|
||||
#define ITEM_ALIGN_END "end"
|
||||
#define ITEM_ALIGN_CENTER "center"
|
||||
#define ITEM_ALIGN_STRETCH "stretch"
|
||||
#define DEFAULT_DIRECTION DIRECTION_ROW
|
||||
#define DEFAULT_WRAP WRAP_WRAP
|
||||
#define DEFAULT_JUSTIFY_CONTENT JUSTIFY_CONTENT_START
|
||||
#define DEFAULT_ALIGN ITEM_ALIGN_CENTER
|
||||
#define DEFAULT_MARGIN_X = 10.0f
|
||||
#define DEFAULT_MARGIN_Y = 10.0f
|
||||
|
||||
class TextureResource;
|
||||
|
||||
class FlexboxComponent : public GuiComponent
|
||||
{
|
||||
public:
|
||||
FlexboxComponent(Window* window, unsigned int assumeChildren = 0);
|
||||
|
||||
void render(const glm::mat4& parentTrans) override;
|
||||
|
||||
void onSizeChanged() override;
|
||||
|
||||
void setDirection(int direction);
|
||||
|
||||
int getDirection();
|
||||
|
||||
void setSlots(std::vector<std::string>);
|
||||
|
||||
std::vector<std::string> getSlots() const;
|
||||
|
||||
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme,
|
||||
const std::string& view,
|
||||
const std::string& element,
|
||||
unsigned int properties) override;
|
||||
|
||||
virtual std::vector<HelpPrompt> getHelpPrompts() override;
|
||||
|
||||
private:
|
||||
// Calculate flexbox layout.
|
||||
void updateVertices();
|
||||
|
||||
// Storage for the flexbox components positions and sizes.
|
||||
std::map<std::string, glm::vec4> mVertices;
|
||||
|
||||
// The components of the flexbox.
|
||||
std::map<std::string, GuiComponent> mComponents;
|
||||
|
||||
// Named map of the components of the flexbox.
|
||||
std::vector<std::string> mSlots;
|
||||
|
||||
std::string mDirection;
|
||||
std::string mWrap;
|
||||
std::string mJustifyContent;
|
||||
std::string mAlign;
|
||||
glm::vec2 mMargin;
|
||||
unsigned int mAssumeChildren;
|
||||
};
|
||||
|
||||
#endif // ES_APP_COMPONENTS_FLEXBOX_COMPONENT_H
|
Loading…
Reference in a new issue