diff --git a/es-core/CMakeLists.txt b/es-core/CMakeLists.txt index 7dcbe1721..d5fa9dd71 100644 --- a/es-core/CMakeLists.txt +++ b/es-core/CMakeLists.txt @@ -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 diff --git a/es-core/src/components/BadgesComponent.cpp b/es-core/src/components/BadgesComponent.cpp index a8ac67636..b65b0c5d5 100644 --- a/es-core/src/components/BadgesComponent.cpp +++ b/es-core/src/components/BadgesComponent.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(); diff --git a/es-core/src/components/BadgesComponent.h b/es-core/src/components/BadgesComponent.h index 906463cca..715714057 100644 --- a/es-core/src/components/BadgesComponent.h +++ b/es-core/src/components/BadgesComponent.h @@ -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 diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp new file mode 100644 index 000000000..210b2d2f2 --- /dev/null +++ b/es-core/src/components/FlexboxComponent.cpp @@ -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; +} diff --git a/es-core/src/components/FlexboxComponent.h b/es-core/src/components/FlexboxComponent.h new file mode 100644 index 000000000..0ae106bd8 --- /dev/null +++ b/es-core/src/components/FlexboxComponent.h @@ -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