AUI Framework  develop
Cross-platform base for C++ UI apps
Loading...
Searching...
No Matches
AWordWrappingEngineImpl.h
    1/*
    2 * AUI Framework - Declarative UI toolkit for modern C++20
    3 * Copyright (C) 2020-2025 Alex2772 and Contributors
    4 *
    5 * SPDX-License-Identifier: MPL-2.0
    6 *
    7 * This Source Code Form is subject to the terms of the Mozilla Public
    8 * License, v. 2.0. If a copy of the MPL was not distributed with this
    9 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
   10 */
   11
   12#pragma once
   13
   14#include <range/v3/all.hpp>
   15#include "AWordWrappingEngine.h"
   16#include "AUI/Util/AFraction.h"
   17
   18template<typename Container>
   19void AWordWrappingEngine<Container>::performLayout(const glm::ivec2& offset, const glm::ivec2& size) {
   20    if (mEntries.empty()) {
   21        mHeight = 0;
   22        return;
   23    }
   24
   25
   26    struct StandardEntry {
   27        aui::no_escape<Entry> entry;
   28        int occupiedHorizontalSpace;
   29    };
   30
   31    static constexpr auto UNDEFINED_POSITION_MARKER = std::numeric_limits<int>::min();
   32    struct FloatingEntry {
   33        aui::no_escape<Entry> entry;
   34        int occupiedHorizontalSpace;
   35        int remainingHeight;
   36        glm::ivec2 position;
   37
   38        void setPosition(glm::ivec2 p) {
   39            position = p;
   40            if (p.x == UNDEFINED_POSITION_MARKER || p.y == UNDEFINED_POSITION_MARKER) {
   41                return;
   42            }
   43            entry->setPosition(p);
   44        }
   45    };
   46
   47
   48
   49    AVector<AVector<StandardEntry>> inflatedEntriesByRows;
   50    typename decltype(inflatedEntriesByRows)::iterator currentRow;
   51    size_t currentRowWidth = 0;
   52    int currentRowHeight = 0;
   53    int currentY = offset.y;
   54
   55    AVector<FloatingEntry> leftFloat;
   56    AVector<FloatingEntry> rightFloat;
   57
   58    bool firstItem;
   59
   60    auto beginRow = [&] {
   61        firstItem = true;
   62        currentRowWidth = 0;
   63        for (auto& i : leftFloat) {
   64            currentRowWidth += i.occupiedHorizontalSpace;
   65        }
   66        for (auto& i : rightFloat) {
   67            currentRowWidth += i.occupiedHorizontalSpace;
   68        }
   69        inflatedEntriesByRows.push_back({});
   70        currentRow = inflatedEntriesByRows.end() - 1;
   71    };
   72
   73    auto flushRow = [&](bool last) {
   74        const int currentYWithLineHeightApplied = currentY + (mLineHeight - 1.f) / 2.f * currentRowHeight;
   75        for (AVector<FloatingEntry>* floating : {&leftFloat, &rightFloat}) {
   76            for (FloatingEntry& i: *floating | ranges::views::reverse) {
   77                if (i.position.y != UNDEFINED_POSITION_MARKER) {
   78                    break;
   79                }
   80                i.position.y = currentYWithLineHeightApplied;
   81                i.setPosition(i.position);
   82            }
   83        }
   84
   85        int currentX = 0;
   86        switch (mTextAlign) {
   87            case ATextAlign::JUSTIFY: {
   88                if (!last) {
   89                    int actualRowWidth = 0;
   90                    int leftPadding = 0;
   91                    int rightPadding = 0;
   92
   93                    for (auto& i: leftFloat) leftPadding += i.occupiedHorizontalSpace;
   94                    for (auto& i: rightFloat) rightPadding += i.occupiedHorizontalSpace;
   95
   96                    if (!currentRow->empty()) {
   97                        if (currentRow->last().entry->escapesEdges()) {
   98                            for (auto it = currentRow->begin(); it != currentRow->end() - 1; ++it) {
   99                                actualRowWidth += it->occupiedHorizontalSpace;
  100                            }
  101                        } else {
  102                            for (auto& i: *currentRow) actualRowWidth += i.occupiedHorizontalSpace;
  103                        }
  104                    }
  105
  106                    int freeSpace = size.x - leftPadding - rightPadding;
  107
  108                    AFraction spacing(freeSpace - actualRowWidth, (glm::max)(int(currentRow->size()) - 1, 1));
  109
  110                    currentX = offset.x + leftPadding;
  111                    int index = 0;
  112                    for (auto& i: *currentRow) {
  113                        i.entry->setPosition({currentX + (spacing * index).toInt(), currentYWithLineHeightApplied});
  114                        currentX += i.occupiedHorizontalSpace;
  115                        ++index;
  116                    }
  117                    break;
  118                }
  119                // fallthrough
  120            }
  121            case ATextAlign::LEFT:
  122                for (auto& i: leftFloat) {
  123                    currentX += i.occupiedHorizontalSpace;
  124                }
  125                for (auto& i: *currentRow) {
  126                    i.entry->setPosition({currentX + offset.x, currentYWithLineHeightApplied});
  127                    currentX += i.occupiedHorizontalSpace;
  128                }
  129                break;
  130
  131            case ATextAlign::CENTER: {
  132                int actualRowWidth = 0;
  133                int leftPadding = 0;
  134                int rightPadding = 0;
  135
  136                for (auto& i: leftFloat) leftPadding += i.occupiedHorizontalSpace;
  137                for (auto& i: rightFloat) rightPadding += i.occupiedHorizontalSpace;
  138                if (!currentRow->empty()) {
  139                    if (currentRow->last().entry->escapesEdges()) {
  140                        for (auto it = currentRow->begin(); it != currentRow->end() - 1; ++it) {
  141                            actualRowWidth += it->occupiedHorizontalSpace;
  142                        }
  143                    } else {
  144                        for (auto& i: *currentRow) actualRowWidth += i.occupiedHorizontalSpace;
  145                    }
  146                }
  147
  148                currentX = leftPadding + (size.x - leftPadding - rightPadding - actualRowWidth) / 2;
  149                for (auto& i: *currentRow) {
  150                    i.entry->setPosition({currentX + offset.x, currentYWithLineHeightApplied});
  151                    currentX += i.occupiedHorizontalSpace;
  152                }
  153                break;
  154            }
  155
  156            case ATextAlign::RIGHT:
  157                // calculate actual row width
  158                int actualRowWidth = 0;
  159                for (auto& i : *currentRow) actualRowWidth += i.occupiedHorizontalSpace;
  160                for (auto& i : rightFloat) actualRowWidth += i.occupiedHorizontalSpace;
  161                currentX = size.x - actualRowWidth;
  162                for (auto& i : *currentRow) {
  163                    i.entry->setPosition({currentX + offset.x, currentYWithLineHeightApplied});
  164                    currentX += i.occupiedHorizontalSpace;
  165                }
  166                break;
  167        }
  168    };
  169
  170
  171    beginRow();
  172
  173    for (auto currentItem = mEntries.begin(); currentItem != mEntries.end(); ++currentItem) {
  174        auto currentItemSize = (*currentItem)->getSize();
  175        bool forcesNextLine = (*currentItem)->forcesNextLine();
  176        bool escapesEdges = (*currentItem)->escapesEdges();
  177
  178        // check if entry fits into the row
  179        if (forcesNextLine || (currentRowWidth + currentItemSize.x > size.x && !escapesEdges)) {
  180            // if current row is empty, we must place this element (unless forcesNextLine)
  181            if (!currentRow->empty() || forcesNextLine) {
  182                // jump to the next row
  183
  184                auto removeRedundantItems = [&currentRowHeight](AVector<FloatingEntry>& fl) {
  185                    for (auto it = fl.begin(); it != fl.end();) {
  186                        it->remainingHeight -= currentRowHeight;
  187                        if (it->remainingHeight <= 0) {
  188                            it = fl.erase(it);
  189                        } else {
  190                            ++it;
  191                        }
  192                    }
  193                };
  194
  195                flushRow(false);
  196
  197                removeRedundantItems(leftFloat);
  198                removeRedundantItems(rightFloat);
  199
  200                currentY += int(float(currentRowHeight) * mLineHeight);
  201                currentRowHeight = 0;
  202                beginRow();
  203            }
  204        }
  205        if (firstItem) {
  206            if (mTextAlign == ATextAlign::JUSTIFY && (*currentItem)->escapesEdges()) {
  207                currentItemSize.x = 0;
  208            }
  209            firstItem = false;
  210        }
  211        switch ((*currentItem)->getFloat()) {
  212            case AFloat::LEFT: {
  213                int position = ranges::accumulate(leftFloat, 0, std::plus<>{}, &FloatingEntry::occupiedHorizontalSpace);
  214                leftFloat.push_back({*currentItem, currentItemSize.x, currentItemSize.y});
  215                leftFloat.last().setPosition({position, UNDEFINED_POSITION_MARKER });
  216                break;
  217            }
  218
  219            case AFloat::RIGHT: {
  220                rightFloat.push_back({*currentItem, currentItemSize.x, currentItemSize.y});
  221                int position = ranges::accumulate(rightFloat, offset.x + size.x, std::minus<>{}, &FloatingEntry::occupiedHorizontalSpace);
  222                rightFloat.last().setPosition({position, UNDEFINED_POSITION_MARKER });
  223                break;
  224            }
  225
  226            case AFloat::NONE:
  227                currentRow->push_back({*currentItem, currentItemSize.x});
  228                currentRowHeight = glm::max(currentRowHeight, currentItemSize.y);
  229                break;
  230        }
  231
  232        currentRowWidth += currentItemSize.x;
  233    }
  234    flushRow(true);
  235
  236    auto floatingMax = [&] {
  237        auto r = ranges::views::concat(leftFloat, rightFloat);
  238        if (r.empty()) {
  239            return 0;
  240        }
  241        return ranges::max(r | ranges::views::transform([](const FloatingEntry& e) { return e.position.y + e.remainingHeight; }));
  242    }();
  243
  244    mHeight = std::max(currentY + int(float(currentRowHeight) * mLineHeight), floatingMax) - offset.y;
  245}
Definition AFraction.h:20
A std::vector with AUI extensions.
Definition AVector.h:39
@ RIGHT
Definition AFloat.h:35
@ LEFT
Definition AFloat.h:29
@ NONE
Definition AFloat.h:23