From aaf5d0209bf920be2da4894a749e0335f082796f Mon Sep 17 00:00:00 2001 From: Sophia Hadash Date: Tue, 14 Sep 2021 01:01:46 +0200 Subject: [PATCH] Implement flexbox and badges. --- es-core/src/ThemeData.cpp | 7 +- es-core/src/components/BadgesComponent.cpp | 24 +- es-core/src/components/BadgesComponent.h | 1 + es-core/src/components/FlexboxComponent.cpp | 313 ++++++++------------ es-core/src/components/FlexboxComponent.h | 65 ++-- themes/rbsimple-DE/theme.xml | 16 +- 6 files changed, 161 insertions(+), 265 deletions(-) diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 37c3fae73..8dd5ae90c 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -148,13 +148,12 @@ std::map> The {"zIndex", FLOAT}}}, {"badges", {{"pos", NORMALIZED_PAIR}, - {"size", NORMALIZED_PAIR}, {"origin", NORMALIZED_PAIR}, {"direction", STRING}, - {"wrap", STRING}, - {"justifyContent", STRING}, {"align", STRING}, - {"margin", NORMALIZED_PAIR}, + {"itemsPerLine", FLOAT}, + {"itemMargin", NORMALIZED_PAIR}, + {"itemWidth", FLOAT}, {"slots", STRING}, {"customBadgeIcon", PATH}, {"visible", BOOLEAN}, diff --git a/es-core/src/components/BadgesComponent.cpp b/es-core/src/components/BadgesComponent.cpp index b65b0c5d5..51fb09056 100644 --- a/es-core/src/components/BadgesComponent.cpp +++ b/es-core/src/components/BadgesComponent.cpp @@ -14,10 +14,10 @@ #include "resources/TextureResource.h" BadgesComponent::BadgesComponent(Window* window) - : FlexboxComponent(window, NUM_SLOTS) + : FlexboxComponent(window) { // Define the slots. - setSlots({SLOT_FAVORITE, SLOT_COMPLETED, SLOT_KIDS, SLOT_BROKEN}); + mSlots = {SLOT_FAVORITE, SLOT_COMPLETED, SLOT_KIDS, SLOT_BROKEN}; mBadgeIcons = std::map(); mBadgeIcons[SLOT_FAVORITE] = ":/graphics/badge_favorite.png"; @@ -39,21 +39,12 @@ BadgesComponent::BadgesComponent(Window* window) 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}; - - // Trigger initial layout computation. - onSizeChanged(); } void BadgesComponent::setValue(const std::string& value) { - std::vector slots = {}; - + mChildren.clear(); 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); while (std::getline(ss, temp, ' ')) { @@ -61,19 +52,17 @@ void BadgesComponent::setValue(const std::string& value) temp == SLOT_BROKEN)) LOG(LogError) << "Badge slot '" << temp << "' is invalid."; else - slots.push_back(temp); + mChildren.push_back(&mImageComponents.find(temp)->second); } } - setSlots(slots); onSizeChanged(); } std::string BadgesComponent::getValue() const { - const std::vector slots = getSlots(); std::stringstream ss; - for (auto& slot : slots) + for (auto& slot : mSlots) ss << slot << ' '; std::string r = ss.str(); r.pop_back(); @@ -92,8 +81,7 @@ void BadgesComponent::applyTheme(const std::shared_ptr& theme, return; bool imgChanged = false; - const std::vector slots = getSlots(); - for (auto& slot : slots) { + for (auto& slot : mSlots) { if (properties & PATH && elem->has(slot)) { mBadgeIcons[slot] = elem->get(slot); mImageComponents.find(slot)->second.setImage(mBadgeIcons[slot]); diff --git a/es-core/src/components/BadgesComponent.h b/es-core/src/components/BadgesComponent.h index 715714057..badaa57c7 100644 --- a/es-core/src/components/BadgesComponent.h +++ b/es-core/src/components/BadgesComponent.h @@ -40,6 +40,7 @@ public: virtual std::vector getHelpPrompts() override; private: + std::vector mSlots; std::map mBadgeIcons; std::map mImageComponents; }; diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index 210b2d2f2..2daa686df 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -14,213 +14,136 @@ #include "ThemeData.h" #include "resources/TextureResource.h" -FlexboxComponent::FlexboxComponent(Window* window, unsigned int assumeChildren) +FlexboxComponent::FlexboxComponent(Window* window) : GuiComponent(window) , mDirection(DEFAULT_DIRECTION) - , mWrap(DEFAULT_WRAP) - , mJustifyContent(DEFAULT_JUSTIFY_CONTENT) , mAlign(DEFAULT_ALIGN) - , mAssumeChildren(assumeChildren) + , mItemsPerLine(DEFAULT_ITEMS_PER_LINE) + , mItemWidth(DEFAULT_ITEM_SIZE_X) { - - // Initialize contents of the flexbox. - mSlots = std::vector(); - mComponents = std::map(); - - // Initialize flexbox layout. - mVertices = std::map(); - - // 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}; + // Initialize item margins. + mItemMargin = glm::vec2{DEFAULT_MARGIN_X, DEFAULT_MARGIN_Y}; // Calculate flexbox layout. - updateVertices(); + computeLayout(); } -void FlexboxComponent::onSizeChanged() +// Getters/Setters for rendering options. +void FlexboxComponent::setDirection(std::string value) { - // 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(); + mDirection = value; + computeLayout(); } - -void FlexboxComponent::updateVertices() +std::string FlexboxComponent::getDirection() { return mDirection; } +void FlexboxComponent::setAlign(std::string value) { - // The maximum number of components to be displayed. - const float numSlots = mAssumeChildren; + mAlign = value; + computeLayout(); +} +std::string FlexboxComponent::getAlign() { return mAlign; } +void FlexboxComponent::setItemsPerLine(unsigned int value) +{ + mItemsPerLine = value; + computeLayout(); +} +unsigned int FlexboxComponent::getItemsPerLine() { return mItemsPerLine; } +void FlexboxComponent::setItemMargin(glm::vec2 value) +{ + mItemMargin = value; + computeLayout(); +} +glm::vec2 FlexboxComponent::getItemMargin() { return mItemMargin; } +void FlexboxComponent::setItemWidth(float value) +{ + mItemWidth = value; + computeLayout(); +} +float FlexboxComponent::getItemWidth() { return mItemWidth; } - // The available size to draw in. - const auto size = getSize(); +void FlexboxComponent::onSizeChanged() { computeLayout(); } - // 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 areas; - for (int i = 1; i < 10; i++) { +void FlexboxComponent::computeLayout() +{ + // Start placing items in the top-left; + float anchorX = 0; + float anchorY = 0; + float anchorOriginX = 0; + float anchorOriginY = 0; - float area = size.x * size.y; + // Translation directions when placing items. + glm::vec2 directionLine = {1, 0}; + glm::vec2 directionRow = {0, 1}; - // Number of vertical gaps. - int verticalGaps = i - 1; + // Change direction. + if (mDirection == DIRECTION_COLUMN) { + directionLine = {0, 1}; + directionRow = {1, 0}; + } - // Area of vertical gaps. - area -= verticalGaps * mMargin.y * size.x; + // 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)}; + } - // Height per item. - float iHeight = (size.y - verticalGaps * mMargin.y) / i; + // Pre-compute layout parameters; + int n = mChildren.size(); + int nLines = std::max(1, (int)std::ceil(n / std::max(1, (int)mItemsPerLine))); + float lineWidth = + (mDirection == "row" ? (maxItemSize.y + mItemMargin.y) : (maxItemSize.x + mItemMargin.x)); + float anchorXStart = anchorX; + float anchorYStart = anchorY; - // 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; + // Iterate through the children. + for (int i = 0; i < n; i++) { + GuiComponent* child = mChildren[i]; + auto size = child->getSize(); - // Average area available per badge - float avgArea = iHeight * iWidth; + // Top-left anchor position. + float x = anchorX - anchorOriginX * size.x; + float y = anchorY - anchorOriginY * size.y; - // Push to the areas array. - areas.push_back(avgArea); - } + // Apply item margin. + x += mItemMargin.x * (directionLine.x >= 0.0f ? 1.0f : -1.0f); + y += mItemMargin.y * (directionLine.y >= 0.0f ? 1.0f : -1.0f); - // 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; + // 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; + } + else if (mAlign == ITEM_ALIGN_CENTER) { + x += directionLine.x == 0 ? (maxItemSize.x - size.x) / 2 : 0; + y += directionLine.y == 0 ? (maxItemSize.y - size.y) / 2 : 0; + } + else if (mAlign == ITEM_ALIGN_STRETCH && mDirection == "row") { + child->setSize(child->getSize().x, maxItemSize.y); + } - // Obtain final item dimensions. - itemHeight = (size.y - (rows - 1) * mMargin.y) / rows; - itemWidth = areas[rows - 1] / itemHeight; + // Store final item position. + child->setPosition(getPosition().x + x, getPosition().y + y); - // Compute number of columns. - if (rows == 1) - columns = mAssumeChildren; - else - columns = std::round((size.x + mMargin.x) / (itemWidth + mMargin.x)); + // Translate anchor. + if ((i + 1) % std::max(1, (int)mItemsPerLine) != 0) { + // Translate on same line. + anchorX += (size.x + mItemMargin.x) * directionLine.x; + anchorY += (size.y + mItemMargin.y) * directionLine.y; } 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 widths; - std::vector 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++; + // Translate to first position of next line. + if (directionRow.x == 0) { + anchorY += lineWidth * directionRow.y; + anchorX = anchorXStart; } - - // 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++; + else { + anchorX += lineWidth * directionRow.x; + anchorY = anchorYStart; } - - // Iterate the row. - mWrap == WRAP_REVERSE ? row-- : row++; } } } @@ -230,17 +153,6 @@ 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); } @@ -251,6 +163,10 @@ void FlexboxComponent::applyTheme(const std::shared_ptr& theme, { using namespace ThemeFlags; + glm::vec2 scale{getParent() ? getParent()->getSize() : + glm::vec2{static_cast(Renderer::getScreenWidth()), + static_cast(Renderer::getScreenHeight())}}; + // TODO: How to do this without explicit 'badges' property? const ThemeData::ThemeElement* elem = theme->getElement(view, element, "badges"); if (!elem) @@ -259,15 +175,18 @@ void FlexboxComponent::applyTheme(const std::shared_ptr& theme, if (properties & DIRECTION && elem->has("direction")) mDirection = elem->get("direction"); - if (elem->has("wrap")) - mWrap = elem->get("wrap"); - - if (elem->has("justifyContent")) - mJustifyContent = elem->get("justifyContent"); - if (elem->has("align")) mAlign = elem->get("align"); + if (elem->has("itemsPerLine")) + mItemsPerLine = elem->get("itemsPerLine"); + + if (elem->has("itemMargin")) + mItemMargin = elem->get("itemMargin"); + + if (elem->has("itemWidth")) + mItemWidth = elem->get("itemWidth") * scale.x; + GuiComponent::applyTheme(theme, view, element, properties); // Trigger layout computation. diff --git a/es-core/src/components/FlexboxComponent.h b/es-core/src/components/FlexboxComponent.h index 0ae106bd8..3be634e00 100644 --- a/es-core/src/components/FlexboxComponent.h +++ b/es-core/src/components/FlexboxComponent.h @@ -13,73 +13,60 @@ #include "GuiComponent.h" #include "renderers/Renderer.h" +// Definitions for the option values. #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" + +// Default values. #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 +#define DEFAULT_ITEMS_PER_LINE 4 +#define DEFAULT_MARGIN_X 10.0f +#define DEFAULT_MARGIN_Y 10.0f +#define DEFAULT_ITEM_SIZE_X 64.0f +#define DEFAULT_ITEM_SIZE_Y 64.0f class TextureResource; class FlexboxComponent : public GuiComponent { public: - FlexboxComponent(Window* window, unsigned int assumeChildren = 0); + FlexboxComponent(Window* window); - void render(const glm::mat4& parentTrans) override; + // Getters/Setters for rendering options. + void setDirection(std::string value); + std::string getDirection(); + void setAlign(std::string value); + std::string getAlign(); + void setItemsPerLine(unsigned int value); + unsigned int getItemsPerLine(); + void setItemMargin(glm::vec2 value); + glm::vec2 getItemMargin(); + void setItemWidth(float value); + float getItemWidth(); void onSizeChanged() override; - - void setDirection(int direction); - - int getDirection(); - - void setSlots(std::vector); - - std::vector getSlots() const; - + void render(const glm::mat4& parentTrans) override; virtual void applyTheme(const std::shared_ptr& theme, const std::string& view, const std::string& element, unsigned int properties) override; - virtual std::vector getHelpPrompts() override; private: // Calculate flexbox layout. - void updateVertices(); - - // Storage for the flexbox components positions and sizes. - std::map mVertices; - - // The components of the flexbox. - std::map mComponents; - - // Named map of the components of the flexbox. - std::vector mSlots; + void computeLayout(); + // Rendering options. std::string mDirection; - std::string mWrap; - std::string mJustifyContent; std::string mAlign; - glm::vec2 mMargin; - unsigned int mAssumeChildren; + unsigned int mItemsPerLine; + glm::vec2 mItemMargin; + float mItemWidth; }; #endif // ES_APP_COMPONENTS_FLEXBOX_COMPONENT_H diff --git a/themes/rbsimple-DE/theme.xml b/themes/rbsimple-DE/theme.xml index dd6027f9a..15e10323d 100644 --- a/themes/rbsimple-DE/theme.xml +++ b/themes/rbsimple-DE/theme.xml @@ -237,15 +237,17 @@ based on: 'recalbox-multi' by the Recalbox community right - 0.873 0.212 - 0.1 0.3 + 0.8125 0.65 0 0 + + row - wrap - start - - start - 20 10 + start + 2 + 10 5 + .05 + + favorite completed kidgame broken :/graphics/badge_favorite.png :/graphics/badge_completed.png