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-2024 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