ObjectivelyMVC
Object oriented MVC framework for SDL3 and GNU C
Loading...
Searching...
No Matches
StackView.c
Go to the documentation of this file.
1/*
2 * ObjectivelyMVC: Object oriented MVC framework for SDL3 and C.
3 * Copyright (C) 2014 Jay Dolan <jay@jaydolan.com>
4 *
5 * This software is provided 'as-is', without any express or implied
6 * warranty. In no event will the authors be held liable for any damages
7 * arising from the use of this software.
8 *
9 * Permission is granted to anyone to use this software for any purpose,
10 * including commercial applications, and to alter it and redistribute it
11 * freely, subject to the following restrictions:
12 *
13 * 1. The origin of this software must not be misrepresented; you must not
14 * claim that you wrote the original software. If you use this software
15 * in a product, an acknowledgment in the product documentation would be
16 * appreciated but is not required.
17 *
18 * 2. Altered source versions must be plainly marked as such, and must not be
19 * misrepresented as being the original software.
20 *
21 * 3. This notice may not be removed or altered from any source distribution.
22 */
23
24#include <assert.h>
25
26#include "StackView.h"
27
28const EnumName StackViewAxisNames[] = MakeEnumNames(
29 MakeEnumAlias(StackViewAxisVertical, vertical),
30 MakeEnumAlias(StackViewAxisHorizontal, horizontal)
31);
32
33const EnumName StackViewDistributionNames[] = MakeEnumNames(
34 MakeEnumAlias(StackViewDistributionDefault, default),
35 MakeEnumAlias(StackViewDistributionFill, fill),
36 MakeEnumAlias(StackViewDistributionFillEqually, fill-equally)
37);
38
39#define _Class _StackView
40
41#pragma mark - View
42
46static void applyStyle(View *self, const Style *style) {
47
48 super(View, self, applyStyle, style);
49
50 StackView *this = (StackView *) self;
51
52 const Inlet inlets[] = MakeInlets(
53 MakeInlet("axis", InletTypeEnum, &this->axis, (ident) StackViewAxisNames),
54 MakeInlet("reversed", InletTypeBool, &this->reversed, NULL),
55 MakeInlet("distribution", InletTypeEnum, &this->distribution, (ident) StackViewDistributionNames),
56 MakeInlet("spacing", InletTypeInteger, &this->spacing, NULL)
57 );
58
59 $(self, bind, inlets, (Dictionary *) style->attributes);
60}
61
65static View *init(View *self) {
66 return (View *) $((StackView *) self, initWithFrame, NULL);
67}
68
72static void layoutSubviews(View *self) {
73
74 Array *subviews = $(self, visibleSubviews);
75 if (subviews->count) {
76
77 const SDL_Rect bounds = $(self, bounds);
78
79 StackView *this = (StackView *) self;
80
81 int availableSize, requestedSize = 0;
82 switch (this->axis) {
84 availableSize = bounds.h;
85 break;
87 availableSize = bounds.w;
88 break;
89 }
90
91 availableSize -= this->spacing * (subviews->count - 1);
92
93 const ViewConstraint unspecified = MakeConstraint(ViewConstraintUnspecified, 0);
94
95 SDL_Size sizes[subviews->count];
96
97 for (size_t i = 0; i < subviews->count; i++) {
98
99 View *subview = $(subviews, objectAtIndex, i);
100
101 sizes[i] = $(subview, sizeThatSatisfies, unspecified, unspecified);
102
103 switch (this->axis) {
105 requestedSize += sizes[i].h;
106 break;
108 requestedSize += sizes[i].w;
109 break;
110 }
111 }
112
113 int numStretch = 0;
114 for (size_t i = 0; i < subviews->count; i++) {
115 const View *subview = $(subviews, objectAtIndex, i);
116 if (subview->stretch) {
117 numStretch++;
118 }
119 }
120
121 int remaining = this->distribution == StackViewDistributionDefault && numStretch ?
122 max(availableSize - requestedSize, 0) : 0;
123
124 int pos = 0;
125
126 const float scale = requestedSize ? availableSize / (float) requestedSize : 1.f;
127
128 for (size_t j = 0; j < subviews->count; j++) {
129
130 const size_t i = this->reversed ? subviews->count - 1 - j : j;
131
132 View *subview = $(subviews, objectAtIndex, i);
133
134 switch (this->axis) {
136 subview->frame.y = pos;
137 break;
139 subview->frame.x = pos;
140 break;
141 }
142
143 SDL_Size subviewSize = sizes[i];
144
145 switch (this->axis) {
148 subviewSize.w = bounds.w;
149 }
150 break;
153 subviewSize.h = bounds.h;
154 }
155 break;
156 }
157
158 // Distribute along primary axis
159
160 switch (this->distribution) {
162 break;
163
165 switch (this->axis) {
167 subviewSize.h *= scale;
168 break;
170 subviewSize.w *= scale;
171 break;
172 }
173 break;
174
176 switch (this->axis) {
178 subviewSize.h = max(availableSize / (float) subviews->count, sizes[i].h);
179 break;
181 subviewSize.w = max(availableSize / (float) subviews->count, sizes[i].w);
182 break;
183 }
184 break;
185 }
186
187 const bool stretch = subview->stretch && remaining;
188 if (stretch) {
189 const int share = --numStretch ? remaining / (numStretch + 1) : remaining;
190 switch (this->axis) {
192 subviewSize.h += share;
193 break;
195 subviewSize.w += share;
196 break;
197 }
198 }
199
200 $(subview, layoutWithSize, &subviewSize);
201
202 if (stretch) {
203 switch (this->axis) {
205 remaining -= max(subview->frame.h - sizes[i].h, 0);
206 subviewSize.h = subview->frame.h;
207 break;
209 remaining -= max(subview->frame.w - sizes[i].w, 0);
210 subviewSize.w = subview->frame.w;
211 break;
212 }
213 }
214
215 // Align along secondary axis
216
217 switch (this->axis) {
219 switch (subview->alignment & ViewAlignmentMaskHorizontal) {
221 subview->frame.x = 0;
222 break;
224 subview->frame.x = (bounds.w - subview->frame.w) * 0.5f;
225 break;
227 subview->frame.x = bounds.w - subview->frame.w;
228 break;
229 }
230 pos += subviewSize.h;
231 break;
233 switch (subview->alignment & ViewAlignmentMaskVertical) {
235 subview->frame.y = 0;
236 break;
238 subview->frame.y = (bounds.h - subview->frame.h) * 0.5f;
239 break;
241 subview->frame.y = bounds.h - subview->frame.h;
242 break;
243 }
244 pos += subviewSize.w;
245 break;
246 }
247
248 pos += this->spacing;
249 }
250 }
251
252 release(subviews);
253}
254
258static SDL_Size sizeThatFits(const View *self) {
259
260 const StackView *this = (StackView *) self;
261
262 SDL_Size size = MakeSize(0, 0);
263
264 switch (this->axis) {
266 size.h = self->padding.top + self->padding.bottom;
267 break;
269 size.w = self->padding.left + self->padding.right;
270 break;
271 }
272
273 Array *subviews = $(self, visibleSubviews);
274
275 const ViewConstraint unspecified = MakeConstraint(ViewConstraintUnspecified, 0);
276
277 for (size_t i = 0; i < subviews->count; i++) {
278
279 View *subview = $(subviews, objectAtIndex, i);
280
281 const SDL_Size subviewSize = $(subview, sizeThatSatisfies, unspecified, unspecified);
282
283 switch (this->axis) {
285 size.w = max(size.w, subviewSize.w);
286 size.h += subviewSize.h;
287 break;
289 size.w += subviewSize.w;
290 size.h = max(size.h, subviewSize.h);
291 break;
292 }
293 }
294
295 switch (this->axis) {
297 size.w += self->padding.left + self->padding.right;
298 break;
300 size.h += self->padding.top + self->padding.bottom;
301 break;
302 }
303
304 if (subviews->count) {
305 switch (this->axis) {
307 size.h += this->spacing * (subviews->count - 1);
308 break;
310 size.w += this->spacing * (subviews->count - 1);
311 break;
312 }
313 }
314
315 release(subviews);
316
317 size.w = ClampSize(size.w, self->minSize.w, self->maxSize.w);
318 size.h = ClampSize(size.h, self->minSize.h, self->maxSize.h);
319
320 return size;
321}
322
323#pragma mark - StackView
324
329static StackView *initWithFrame(StackView *self, const SDL_Rect *frame) {
330
331 self = (StackView *) super(View, self, initWithFrame, frame);
332 if (self) {
333 }
334
335 return self;
336}
337
338#pragma mark - Class lifecycle
339
343static void initialize(Class *clazz) {
344
345 ((ViewInterface *) clazz->interface)->applyStyle = applyStyle;
346 ((ViewInterface *) clazz->interface)->init = init;
347
348 ((ViewInterface *) clazz->interface)->layoutSubviews = layoutSubviews;
349 ((ViewInterface *) clazz->interface)->sizeThatFits = sizeThatFits;
350
351 ((StackViewInterface *) clazz->interface)->initWithFrame = initWithFrame;
352}
353
358Class *_StackView(void) {
359 static Class *clazz;
360 static Once once;
361
362 do_once(&once, {
363 clazz = _initialize(&(const ClassDef) {
364 .name = "StackView",
365 .superclass = _View(),
366 .instanceSize = sizeof(StackView),
367 .interfaceSize = sizeof(StackViewInterface),
368 .initialize = initialize,
369 });
370 });
371
372 return clazz;
373}
374
375#undef _Class
static SDL_Size size(const Image *self)
Definition Image.c:416
static Array * visibleSubviews(const View *self)
Definition PageView.c:92
const EnumName StackViewAxisNames[]
Definition StackView.c:28
static View * init(View *self)
Definition StackView.c:65
static void applyStyle(View *self, const Style *style)
Definition StackView.c:46
static SDL_Size sizeThatFits(const View *self)
Definition StackView.c:258
static StackView * initWithFrame(StackView *self, const SDL_Rect *frame)
Definition StackView.c:329
const EnumName StackViewDistributionNames[]
Definition StackView.c:33
static void initialize(Class *clazz)
Definition StackView.c:343
Class * _StackView(void)
Definition StackView.c:358
static void layoutSubviews(View *self)
Definition StackView.c:72
StackViews are containers that manage the arrangement of their subviews.
@ StackViewDistributionDefault
Definition StackView.h:53
@ StackViewDistributionFillEqually
Definition StackView.h:55
@ StackViewDistributionFill
Definition StackView.h:54
@ StackViewAxisHorizontal
Definition StackView.h:44
@ StackViewAxisVertical
Definition StackView.h:43
@ InletTypeEnum
Definition View+JSON.h:75
@ InletTypeBool
Definition View+JSON.h:46
@ InletTypeInteger
Definition View+JSON.h:89
#define MakeInlets(...)
Creates a null-termianted array of Inlets.
Definition View+JSON.h:215
#define MakeInlet(name, type, dest, data)
Creates an Inlet with the specified parameters.
Definition View+JSON.h:210
static SDL_Size sizeThatSatisfies(View *self, ViewConstraint width, ViewConstraint height)
Definition View.c:1979
Class * _View(void)
Definition View.c:2336
static void layoutWithSize(View *self, const SDL_Size *size)
Definition View.c:1267
static SDL_Rect bounds(const View *self)
Definition View.c:552
#define ViewAlignmentMaskBottom
Definition View.h:46
#define ViewAlignmentMaskTop
Definition View.h:44
#define ViewAlignmentMaskMiddle
Definition View.h:45
#define MakeConstraint(mode, value)
Definition View.h:163
#define ViewAlignmentMaskHorizontal
Definition View.h:55
#define ClampSize(size, min, max)
Clamps size to min and max, where a max of zero is unbounded.
Definition View.h:103
#define ViewAlignmentMaskVertical
Definition View.h:52
@ ViewAutoresizingWidth
Definition View.h:91
@ ViewAutoresizingHeight
Definition View.h:92
@ ViewAlignmentCenter
Definition View.h:70
@ ViewAlignmentLeft
Definition View.h:69
@ ViewAlignmentRight
Definition View.h:71
Inlets enable inbound data binding of View attributes through JSON.
Definition View+JSON.h:149
StackViews are containers that manage the arrangement of their subviews.
Definition StackView.h:68
The Style type.
Definition Style.h:43
Dictionary * attributes
Definition Style.h:59
A constraint offered by an ancestor View to a descendant during View::sizeThatSatisfies.
Definition View.h:131
Views are the fundamental building blocks of ObjectivelyMVC user interfaces.
Definition View.h:225
ViewAlignment alignment
The alignment.
Definition View.h:242
void applyStyle(View *self, const Style *style)
Applies the given Style to this View.
int autoresizingMask
The ViewAutoresizing bitmask.
Definition View.h:248
ViewPadding padding
The padding.
Definition View.h:398
SDL_Size minSize
The minimum size this View may be resized to during layout.
Definition View.h:345
layoutSubviews(View *self)
Arranges this View's immediate subviews within its (already final) frame.
Definition Box.c:74
SDL_Size maxSize
The maximum size this View may be resized to during layout.
Definition View.h:339
bool stretch
Whether this View takes a share of the space its StackView leaves along its axis.
Definition View.h:436
SDL_Rect frame
The frame, relative to the superview.
Definition View.h:301
int top
Definition View.h:170
int bottom
Definition View.h:170
int right
Definition View.h:170
int left
Definition View.h:170