mirror of
				https://github.com/RetroDECK/ES-DE.git
				synced 2025-04-10 19:15:13 +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
	
	 Sophia Hadash
						Sophia Hadash