Velvet 0.1a
A beginner-friendly C++ GUI framework built on SFML.
Loading...
Searching...
No Matches
Stack.hpp
Go to the documentation of this file.
1#pragma once
3#include <algorithm>
4#include <cctype>
5#include <iostream>
6#include <string>
7#include <vector>
8
12enum class StackJustify { Start, Center, End };
14enum class StackAlign { Start, Center, End };
15
27class Stack : public Widget {
28private:
29 float xPos = 0;
30 float yPos = 0;
31
32 float cachedWidth = 0;
33 float cachedHeight = 0;
34 float explicitWidth = 0;
35 float explicitHeight = 0;
36
37 std::vector<Widget*> children;
38
39 StackDirection direction;
42
43 float gap;
44 float paddingLeft = 8.0f;
45 float paddingRight = 8.0f;
46 float paddingTop = 8.0f;
47 float paddingBottom = 8.0f;
48
49 sf::Color backgroundColor = sf::Color(0, 0, 0, 0);
50 sf::RectangleShape backgroundShape;
51
52 bool dirty = false;
53
54 static std::string normalize(std::string value) {
55 std::transform(value.begin(), value.end(), value.begin(),
56 [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
57 return value;
58 }
59
60 static StackJustify parseJustifyOrFallback(const std::string& value) {
61 const std::string normalized = normalize(value);
62
63 if (normalized == "center") return StackJustify::Center;
64 if (normalized == "start") return StackJustify::Start;
65 if (normalized == "end") return StackJustify::End;
66
67 std::cerr << "[Velvet][Stack] Unsupported justify value: '" << value
68 << "'. Falling back to 'start'.\n";
70 }
71
72 static StackAlign parseAlignOrFallback(const std::string& value) {
73 const std::string normalized = normalize(value);
74
75 if (normalized == "center") return StackAlign::Center;
76 if (normalized == "start") return StackAlign::Start;
77 if (normalized == "end") return StackAlign::End;
78
79 std::cerr << "\033[33mWarning: Invalid style property: '" << value << "'\033[0m" << std::endl;
80 return StackAlign::Start;
81 }
82
83 void draw(sf::RenderWindow& window) override {
84 if (backgroundColor.a > 0) {
85 window.draw(backgroundShape);
86 }
87 for (Widget* child : children) {
88 child->render(window);
89 }
90 }
91
92 void update(sf::RenderWindow& window) override {
93 if (dirty) {
94 recalculateChildPositions();
95 dirty = false;
96 }
97
98 }
99
100 float getContentMainSize() {
101 if (children.empty()) return 0;
102
103 float total = 0;
104 for (Widget* child : children) {
105 const sf::Vector2f dims = child->getDimensions();
106 total += (direction == StackDirection::Horizontal) ? dims.x : dims.y;
107 }
108
109 total += gap * static_cast<float>(children.size() - 1);
110 return total;
111 }
112
113 void recalculateCachedDimensions() {
114 cachedWidth = 0;
115 cachedHeight = 0;
116
117 for (Widget* child : children) {
118 const sf::Vector2f dims = child->getDimensions();
119 if (direction == StackDirection::Horizontal) {
120 cachedWidth += dims.x;
121 cachedHeight = std::max(cachedHeight, dims.y);
122 } else {
123 cachedHeight += dims.y;
124 cachedWidth = std::max(cachedWidth, dims.x);
125 }
126 }
127
128 if (!children.empty()) {
129 if (direction == StackDirection::Horizontal)
130 cachedWidth += gap * static_cast<float>(children.size() - 1);
131 else
132 cachedHeight += gap * static_cast<float>(children.size() - 1);
133 }
134 }
135
136 float calculateStartOffset(float available, float content) const {
137 if (available <= content) return 0;
138
139 const float remaining = available - content;
140 if (justify == StackJustify::Center) return remaining * 0.5f;
141 if (justify == StackJustify::End) return remaining;
142
143 return 0;
144 }
145
146 float calculateCrossOffset(float available, float content) const {
147 if (available <= content) return 0;
148
149 const float remaining = available - content;
150 if (align == StackAlign::Center) return remaining * 0.5f;
151 if (align == StackAlign::End) return remaining;
152
153 return 0;
154 }
155
156 void recalculateChildPositions() {
157 recalculateCachedDimensions();
158
159 const float contentMain = getContentMainSize();
160 const float availableMain =
161 (direction == StackDirection::Horizontal) ? explicitWidth : explicitHeight;
162 const float startMain = calculateStartOffset(availableMain, contentMain);
163
164 float pos = (direction == StackDirection::Horizontal)
165 ? xPos + paddingLeft + startMain
166 : yPos + paddingTop + startMain;
167
168 for (Widget* child : children) {
169 const sf::Vector2f dims = child->getDimensions();
170
171 if (direction == StackDirection::Horizontal) {
172 const float crossOffset = calculateCrossOffset(explicitHeight, dims.y);
173 child->setPosition(pos, yPos + paddingTop + crossOffset);
174 pos += dims.x + gap;
175 } else {
176 const float crossOffset = calculateCrossOffset(explicitWidth, dims.x);
177 child->setPosition(xPos + paddingLeft + crossOffset, pos);
178 pos += dims.y + gap;
179 }
180 }
181
182 const float totalWidth = std::max(cachedWidth, explicitWidth) + paddingLeft + paddingRight;
183 const float totalHeight = std::max(cachedHeight, explicitHeight) + paddingTop + paddingBottom;
184 backgroundShape.setPosition(xPos, yPos);
185 backgroundShape.setSize(sf::Vector2f(totalWidth, totalHeight));
186 backgroundShape.setFillColor(backgroundColor);
187 }
188
189public:
193 Stack(StackDirection dir, float gap = 0) : direction(dir), gap(gap) {}
194
198 void render(sf::RenderWindow& window) override {
199 update(window);
200 draw(window);
201 }
202
208 void add(Widget* widget) {
209 children.push_back(widget);
210 dirty = true;
211 }
212
216 template<typename... Ts>
217 void add(Ts&&... widgets) {
218 (add_widget(std::forward<Ts>(widgets)), ...);
219 }
220
221private:
222 void add_widget(Widget* w) { add(w); }
223 void add_widget(Widget& w) { add(&w); }
224
225public:
229 void setPosition(float x, float y) override {
230 if (xPos != x || yPos != y) {
231 xPos = x;
232 yPos = y;
233 dirty = true;
234 }
235 }
236
240 sf::Vector2f getDimensions() override {
241 return sf::Vector2f(
242 std::max(cachedWidth, explicitWidth) + paddingLeft + paddingRight,
243 std::max(cachedHeight, explicitHeight) + paddingTop + paddingBottom
244 );
245 }
246
250 void handleEvent(const sf::Event& event, sf::RenderWindow &window) override {
251 for (Widget* child : children) {
252 child->handleEvent(event, window);
253 }
254 }
255
259 void setGap(float g) {
260 if (gap != g) {
261 gap = g;
262 dirty = true;
263 }
264 }
265
269 float getGap() const { return gap; }
270
275 if (justify != value) {
276 justify = value;
277 dirty = true;
278 }
279 }
280
284 void setJustifyContent(const std::string& value) {
285 setJustifyContent(parseJustifyOrFallback(value));
286 }
287
292 if (align != value) {
293 align = value;
294 dirty = true;
295 }
296 }
297
301 void setAlignItems(const std::string& value) {
302 setAlignItems(parseAlignOrFallback(value));
303 }
304
308 void setSize(float width, float height) {
309 if (explicitWidth != width || explicitHeight != height) {
310 explicitWidth = width;
311 explicitHeight = height;
312 dirty = true;
313 }
314 }
315
319 void setWidth(float width) {
320 if (explicitWidth != width) {
321 explicitWidth = width;
322 dirty = true;
323 }
324 }
325
329 void setHeight(float height) {
330 if (explicitHeight != height) {
331 explicitHeight = height;
332 dirty = true;
333 }
334 }
335
339 void setPadding(float p) {
340 paddingLeft = p;
341 paddingRight = p;
342 paddingTop = p;
343 paddingBottom = p;
344
345 dirty = true;
346 }
347
351 void setPadding(float horizontal, float vertical) {
352 paddingLeft = horizontal;
353 paddingRight = horizontal;
354
355 paddingTop = vertical;
356 paddingBottom = vertical;
357
358 dirty = true;
359 }
360
368 void setPadding(float left, float right, float top, float bottom) {
369 paddingLeft = left;
370 paddingRight = right;
371 paddingTop = top;
372 paddingBottom = bottom;
373
374 dirty = true;
375 }
376
380 void setBackgroundColor(sf::Color color) {
381 if (backgroundColor.r != color.r || backgroundColor.g != color.g ||
382 backgroundColor.b != color.b || backgroundColor.a != color.a) {
383 backgroundColor = color;
384 backgroundShape.setFillColor(backgroundColor);
385 dirty = true;
386 }
387 }
388};
389
390// preserve HStack and VStack class names for backwards compatibility
391// (to be fair its like 4 commits ago but still cmon)
392
394class HStack : public Stack {
395public:
396 HStack(float gap = 0) : Stack(StackDirection::Horizontal, gap) {}
397};
398
400class VStack : public Stack {
401public:
402 VStack(float gap = 0) : Stack(StackDirection::Vertical, gap) {}
403};
StackAlign
Cross-axis item alignment for Stack.
Definition Stack.hpp:14
StackJustify
Main-axis content alignment for Stack.
Definition Stack.hpp:12
StackDirection
Layout direction for Stack.
Definition Stack.hpp:10
Horizontal stack helper (StackDirection::Horizontal).
Definition Stack.hpp:394
HStack(float gap=0)
Definition Stack.hpp:396
Container widget that arranges children in a row or column.
Definition Stack.hpp:27
void render(sf::RenderWindow &window) override
Render stack background and child widgets.
Definition Stack.hpp:198
void setSize(float width, float height)
Set explicit content size used for alignment calculations.
Definition Stack.hpp:308
Stack(StackDirection dir, float gap=0)
Construct a stack with direction and optional gap.
Definition Stack.hpp:193
void setPadding(float p)
Set uniform padding on all sides.
Definition Stack.hpp:339
void setAlignItems(StackAlign value)
Set cross-axis child alignment.
Definition Stack.hpp:291
sf::Vector2f getDimensions() override
Get total stack dimensions including padding.
Definition Stack.hpp:240
void add(Widget *widget)
Add a child widget pointer.
Definition Stack.hpp:208
void setAlignItems(const std::string &value)
Set cross-axis alignment from string (start|center|end).
Definition Stack.hpp:301
void setPosition(float x, float y) override
Set stack position (top-left).
Definition Stack.hpp:229
void setBackgroundColor(sf::Color color)
Set container background color.
Definition Stack.hpp:380
void setGap(float g)
Set spacing between children.
Definition Stack.hpp:259
void setPadding(float horizontal, float vertical)
Set horizontal and vertical padding.
Definition Stack.hpp:351
void setHeight(float height)
Set explicit content height.
Definition Stack.hpp:329
void setJustifyContent(StackJustify value)
Set main-axis content alignment.
Definition Stack.hpp:274
void add(Ts &&... widgets)
Add one or more child widgets (pointer or reference forms).
Definition Stack.hpp:217
float getGap() const
Get current child gap.
Definition Stack.hpp:269
void setWidth(float width)
Set explicit content width.
Definition Stack.hpp:319
void setPadding(float left, float right, float top, float bottom)
Set side-specific padding.
Definition Stack.hpp:368
void handleEvent(const sf::Event &event, sf::RenderWindow &window) override
Forward input events to all children.
Definition Stack.hpp:250
void setJustifyContent(const std::string &value)
Set main-axis alignment from string (start|center|end).
Definition Stack.hpp:284
Vertical stack helper (StackDirection::Vertical).
Definition Stack.hpp:400
VStack(float gap=0)
Definition Stack.hpp:402
Base class for all Velvet UI elements.
Definition Widget.hpp:16
float y
Definition Widget.hpp:31
float x
Definition Widget.hpp:31