ObjectivelyMVC
Object oriented MVC framework for SDL3 and GNU C
Loading...
Searching...
No Matches
CollectionView.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#include <math.h>
26
27#include "CollectionView.h"
28
29const EnumName CollectionViewAxisNames[] = MakeEnumNames(
30 MakeEnumAlias(CollectionViewAxisHorizontal, horizontal),
31 MakeEnumAlias(CollectionViewAxisVertical, vertical)
32);
33
34#define _Class _CollectionView
35
36#pragma mark - Object
37
41static void dealloc(Object *self) {
42
43 CollectionView *this = (CollectionView *) self;
44
45 memset(&this->delegate, 0, sizeof(this->delegate));
46
47 release(this->contentView);
48 release(this->items);
49 release(this->scrollView);
50
51 super(Object, self, dealloc);
52}
53
54#pragma mark - View
55
59static void applyStyle(View *self, const Style *style) {
60
61 super(View, self, applyStyle, style);
62
63 CollectionView *this = (CollectionView *) self;
64
65 const Inlet inlets[] = MakeInlets(
66 MakeInlet("axis", InletTypeEnum, &this->axis, (ident) CollectionViewAxisNames),
67 MakeInlet("item-size", InletTypeSize, &this->itemSize, NULL),
68 MakeInlet("item-spacing", InletTypeSize, &this->itemSpacing, NULL)
69 );
70
71 $(self, bind, inlets, (Dictionary *) style->attributes);
72}
73
77static View *init(View *self) {
78 return (View *) $((CollectionView *) self, initWithFrame, NULL);
79}
80
84static void layoutSubviews(View *self) {
85
86 CollectionView *this = (CollectionView *) self;
87
88 super(View, self, layoutSubviews);
89
90 const SDL_Rect bounds = $((View *) this->scrollView, bounds);
91
92 int x = bounds.x, y = bounds.y;
93
94 const Array *items = (Array *) this->items;
95 for (size_t i = 0; i < items->count; i++) {
96
97 CollectionItemView *item = (CollectionItemView *) $(items, objectAtIndex, i);
98
99 $((View *) item, resize, &this->itemSize);
100 $((View *) item, layoutIfNeeded);
101
102 item->view.frame.x = x;
103 item->view.frame.y = y;
104
105 switch (this->axis) {
107 x += this->itemSize.w + this->itemSpacing.w;
108 if (x + this->itemSize.w > bounds.w) {
109 y += this->itemSize.h + this->itemSpacing.h;
110 x = bounds.x;
111 }
112 break;
114 y += this->itemSize.h + this->itemSpacing.h;
115 if (y + this->itemSize.h > bounds.h) {
116 x += this->itemSize.w + this->itemSpacing.w;
117 y = bounds.y;
118 }
119 break;
120 }
121 }
122}
123
124#pragma mark - Control
125
129static bool captureEvent(Control *self, const SDL_Event *event) {
130
131 if (event->type == SDL_EVENT_MOUSE_BUTTON_UP) {
132
133 CollectionView *this = (CollectionView *) self;
134
135 if ($((Control *) this->scrollView, isHighlighted) == false) {
136 if ($((View *) this->contentView, didReceiveEvent, event)) {
137
138 const SDL_Point point = {
139 .x = event->button.x,
140 .y = event->button.y
141 };
142
143 IndexPath *indexPath = $(this, indexPathForItemAtPoint, &point);
144
145 const CollectionItemView *item = $(this, itemAtIndexPath, indexPath);
146 if (item) {
147
148 switch (self->selection) {
150 break;
152 if (item->isSelected == false) {
153 $(this, deselectAll);
154 $(this, selectItemAtIndexPath, indexPath);
155 }
156 break;
158 if (SDL_GetModState() & (SDL_KMOD_CTRL | SDL_KMOD_GUI)) {
159 if (item->isSelected) {
160 $(this, deselectItemAtIndexPath, indexPath);
161 } else {
162 $(this, selectItemAtIndexPath, indexPath);
163 }
164 } else {
165 $(this, deselectAll);
166 $(this, selectItemAtIndexPath, indexPath);
167 }
168 break;
169 }
170
171 if (this->delegate.didModifySelection) {
173
174 this->delegate.didModifySelection(this, selectionIndexPaths);
175
176 release(selectionIndexPaths);
177 }
178 }
179
180 release(indexPath);
181 return true;
182 }
183 }
184 }
185
186 return super(Control, self, captureEvent, event);
187}
188
189#pragma mark - CollectionView
190
194static void deselectAll_enumerate(const Array *array, ident obj, ident data) {
195 $((CollectionItemView *) obj, setSelected, false);
196}
197
202static void deselectAll(CollectionView *self) {
203 $((Array *) self->items, enumerate, deselectAll_enumerate, NULL);
204}
205
210static void deselectItemAtIndexPath(CollectionView *self, const IndexPath *indexPath) {
211
212 if (indexPath) {
213 CollectionItemView *item = $(self, itemAtIndexPath, indexPath);
214 if (item) {
215 $(item, setSelected, false);
217 }
218 }
219}
220
224static void deselectItemsAtIndexPaths_enumerate(const Array *array, ident obj, ident data) {
225 $((CollectionItemView *) obj, setSelected, false);
226}
227
232static void deselectItemsAtIndexPaths(CollectionView *self, const Array *indexPaths) {
233
234 if (indexPaths) {
236 }
237}
238
243static IndexPath *indexPathForItemAtPoint(const CollectionView *self, const SDL_Point *point) {
244
245 if (self->itemSize.w && self->itemSize.h) {
246
247 const SDL_Rect frame = $(self->contentView, renderFrame);
248
249 const int itemWidth = self->itemSize.w + self->itemSpacing.w;
250 const int itemHeight = self->itemSize.h + self->itemSpacing.h;
251
252 const int rows = max(1, frame.h / itemHeight);
253 const int cols = max(1, frame.w / itemWidth);
254
255 const int x = point->x - frame.x;
256 const int y = point->y - frame.y;
257
258 const int row = y / itemHeight;
259 const int col = x / itemWidth;
260
261 int index;
262 switch (self->axis) {
264 index = row * cols + col;
265 break;
267 index = col * rows + row;
268 break;
269 }
270
271 if (index < (int) self->items->count) {
272 return $(alloc(IndexPath), initWithIndex, index);
273 }
274 }
275
276 return NULL;
277}
278
283static IndexPath *indexPathForItem(const CollectionView *self, const CollectionItemView *item) {
284
285 const ssize_t index = $((Array *) self->items, indexOfObject, (ident) item);
286 if (index > -1) {
287 return $(alloc(IndexPath), initWithIndex, index);
288 }
289
290 return NULL;
291}
292
297static CollectionView *initWithFrame(CollectionView *self, const SDL_Rect *frame) {
298
299 self = (CollectionView *) super(Control, self, initWithFrame, frame);
300 if (self) {
301 self->items = $$(Array, array);
302 assert(self->items);
303
304 self->contentView = $(alloc(View), initWithFrame, NULL);
305 assert(self->contentView);
306
307 $(self->contentView, addClassName, "contentView");
308 $(self->contentView, addClassName, "container");
309
310 self->scrollView = $(alloc(ScrollView), initWithFrame, NULL);
311 assert(self->scrollView);
312
313 $(self->scrollView, setContentView, self->contentView);
314 $((View *) self, addSubview, (View *) self->scrollView);
315 }
316
317 return self;
318}
319
324static CollectionItemView *itemAtIndexPath(const CollectionView *self, const IndexPath *indexPath) {
325
326 if (indexPath) {
327 const Array *items = (Array *) self->items;
328 const size_t index = $(indexPath, indexAtPosition, 0);
329
330 if (index < items->count) {
331 return $(items, objectAtIndex, index);
332 }
333 }
334
335 return NULL;
336}
337
342static SDL_Size naturalSize(const CollectionView *self) {
343
344 ViewPadding padding = MakePadding(0, 0, 0, 0);
345 padding = AddPadding(padding, ((View *) self)->padding);
346 padding = AddPadding(padding, ((View *) self->scrollView)->padding);
347 padding = AddPadding(padding, ((View *) self->contentView)->padding);
348
349 SDL_Size size = MakeSize(padding.left + padding.right, padding.top + padding.bottom);
350
351 View *scrollView = (View *) self->scrollView;
352 SDL_Size scrollViewSize;
353 if (scrollView->autoresizingMask & ViewAutoresizingContain) {
354 scrollViewSize = $(scrollView, sizeThatContains);
355 } else if (scrollView->autoresizingMask & ViewAutoresizingFit) {
356 scrollViewSize = $(scrollView, sizeThatFits);
357 } else {
358 scrollViewSize = $(scrollView, size);
359 }
360
361 switch (self->axis) {
363 int itemsPerRow = 1;
364 int w = scrollViewSize.w;
365 while (w > 0) {
366 w -= self->itemSize.w;
367 itemsPerRow++;
368 if (w - self->itemSpacing.w < 0) {
369 break;
370 }
371 w -= self->itemSpacing.w;
372 }
373 const int rows = ceilf(self->items->count / (float) itemsPerRow);
374 size.w += max(self->itemSize.w, scrollViewSize.w);
375 size.h += rows * (self->itemSize.h + self->itemSpacing.h);
376 }
377 break;
379 int itemsPerCol = 1;
380 int h = scrollViewSize.h;
381 while (h > 0) {
382 h -= self->itemSize.h;
383 itemsPerCol++;
384 if (h - self->itemSpacing.h < 0) {
385 break;
386 }
387 h -= self->itemSpacing.h;
388 }
389 const int cols = ceilf(self->items->count / (float) itemsPerCol);
390 size.w += cols * (self->itemSize.w + self->itemSpacing.w);
391 size.h += max(self->itemSize.h, scrollViewSize.h);
392 }
393 break;
394 }
395
396 return size;
397}
398
402static void reloadData_removeItems(const Array *array, ident obj, ident data) {
403 $((View *) data, removeSubview, (View *) obj);
404}
405
410static void reloadData(CollectionView *self) {
411
412 assert(self->dataSource.numberOfItems);
414
415 $((Array *) self->items, enumerate, reloadData_removeItems, self->contentView);
416 $(self->items, removeAllObjects);
417
418 const size_t numberOfItems = self->dataSource.numberOfItems(self);
419 for (size_t i = 0; i < numberOfItems; i++) {
420
421 IndexPath *indexPath = $(alloc(IndexPath), initWithIndex, i);
422
423 CollectionItemView *item = self->delegate.itemForObjectAtIndexPath(self, indexPath);
424 assert(item);
425
426 $(self->items, addObject, item);
427 $(self->contentView, addSubview, (View *) item);
428
429 release(item);
430 release(indexPath);
431 }
432
433 ((View *) self)->needsLayout = true;
434}
435
439static void selectAll_enumerate(const Array *array, ident obj, ident data) {
440 $((CollectionItemView *) obj, setSelected, true);
441}
442
447static void selectAll(CollectionView *self) {
448 $((Array *) self->items, enumerate, selectAll_enumerate, NULL);
449}
450
455static Array *selectionIndexPaths(const CollectionView *self) {
456
457 Array *array = $$(Array, array);
458
459 const Array *items = (Array *) self->items;
460 for (size_t i = 0; i < items->count; i++) {
461
462 const CollectionItemView *item = $(items, objectAtIndex, i);
463 if (item->isSelected) {
464
465 IndexPath *indexPath = $(self, indexPathForItem, item);
466 $(array, addObject, indexPath);
467
468 release(indexPath);
469 }
470 }
471
472 return (Array *) array;
473}
474
479static void selectItemAtIndexPath(CollectionView *self, const IndexPath *indexPath) {
480
481 if (indexPath) {
482 CollectionItemView *item = $(self, itemAtIndexPath, indexPath);
483 if (item) {
484 $(item, setSelected, true);
486 }
487 }
488}
489
493static void selectItemsAtIndexPaths_enumerate(const Array *array, ident obj, ident data) {
494 $((CollectionView *) data, selectItemAtIndexPath, (IndexPath *) obj);
495}
496
501static void selectItemsAtIndexPaths(CollectionView *self, const Array *indexPaths) {
502
503 if (indexPaths) {
504 $(indexPaths, enumerate, selectItemsAtIndexPaths_enumerate, self);
505 }
506}
507
508#pragma mark - Class lifecycle
509
513static void initialize(Class *clazz) {
514
515 ((ObjectInterface *) clazz->interface)->dealloc = dealloc;
516
517 ((ViewInterface *) clazz->interface)->applyStyle = applyStyle;
518 ((ViewInterface *) clazz->interface)->init = init;
519 ((ViewInterface *) clazz->interface)->layoutSubviews = layoutSubviews;
520
521 ((ControlInterface *) clazz->interface)->captureEvent = captureEvent;
522
523 ((CollectionViewInterface *) clazz->interface)->deselectAll = deselectAll;
524 ((CollectionViewInterface *) clazz->interface)->deselectItemAtIndexPath = deselectItemAtIndexPath;
525 ((CollectionViewInterface *) clazz->interface)->deselectItemsAtIndexPaths = deselectItemsAtIndexPaths;
526 ((CollectionViewInterface *) clazz->interface)->indexPathForItem = indexPathForItem;
527 ((CollectionViewInterface *) clazz->interface)->indexPathForItemAtPoint = indexPathForItemAtPoint;
528 ((CollectionViewInterface *) clazz->interface)->initWithFrame = initWithFrame;
529 ((CollectionViewInterface *) clazz->interface)->itemAtIndexPath = itemAtIndexPath;
530 ((CollectionViewInterface *) clazz->interface)->naturalSize = naturalSize;
531 ((CollectionViewInterface *) clazz->interface)->reloadData = reloadData;
532 ((CollectionViewInterface *) clazz->interface)->selectAll = selectAll;
533 ((CollectionViewInterface *) clazz->interface)->selectionIndexPaths = selectionIndexPaths;
534 ((CollectionViewInterface *) clazz->interface)->selectItemAtIndexPath = selectItemAtIndexPath;
535 ((CollectionViewInterface *) clazz->interface)->selectItemsAtIndexPaths = selectItemsAtIndexPaths;
536}
537
542Class *_CollectionView(void) {
543 static Class *clazz;
544 static Once once;
545
546 do_once(&once, {
547 clazz = _initialize(&(const ClassDef) {
548 .name = "CollectionView",
549 .superclass = _Control(),
550 .instanceSize = sizeof(CollectionView),
551 .interfaceOffset = offsetof(CollectionView, interface),
552 .interfaceSize = sizeof(CollectionViewInterface),
554 });
555 });
556
557 return clazz;
558}
559
560#undef _Class
static void setSelected(CollectionItemView *self, bool isSelected)
static void selectItemsAtIndexPaths_enumerate(const Array *array, ident obj, ident data)
ArrayEnumerator for item selection.
static void selectAll(CollectionView *self)
static void deselectItemAtIndexPath(CollectionView *self, const IndexPath *indexPath)
static IndexPath * indexPathForItem(const CollectionView *self, const CollectionItemView *item)
static View * init(View *self)
static CollectionItemView * itemAtIndexPath(const CollectionView *self, const IndexPath *indexPath)
static CollectionView * initWithFrame(CollectionView *self, const SDL_Rect *frame)
static void applyStyle(View *self, const Style *style)
Class * _CollectionView(void)
static bool captureEvent(Control *self, const SDL_Event *event)
const EnumName CollectionViewAxisNames[]
static void deselectAll_enumerate(const Array *array, ident obj, ident data)
ArrayEnumerator for all item deselection.
static void deselectItemsAtIndexPaths(CollectionView *self, const Array *indexPaths)
static Array * selectionIndexPaths(const CollectionView *self)
static IndexPath * indexPathForItemAtPoint(const CollectionView *self, const SDL_Point *point)
static void selectItemsAtIndexPaths(CollectionView *self, const Array *indexPaths)
static void dealloc(Object *self)
static SDL_Size naturalSize(const CollectionView *self)
static void selectAll_enumerate(const Array *array, ident obj, ident data)
ArrayEnumerator for all item selection.
static void initialize(Class *clazz)
static void deselectItemsAtIndexPaths_enumerate(const Array *array, ident obj, ident data)
ArrayEnumerator for item deselection.
static void layoutSubviews(View *self)
static void selectItemAtIndexPath(CollectionView *self, const IndexPath *indexPath)
static void deselectAll(CollectionView *self)
static void reloadData(CollectionView *self)
static void reloadData_removeItems(const Array *array, ident obj, ident data)
ArrayEnumerator to remove CollectionItemViews from the collection's contentView.
The CollectionView type.
Class * _Control(void)
Definition Control.c:391
static bool isHighlighted(const Control *self)
Definition Control.c:327
@ ControlSelectionMultiple
Definition Control.h:57
@ ControlSelectionSingle
Definition Control.h:56
@ ControlSelectionNone
Definition Control.h:55
static SDL_Size size(const Image *self)
Definition Image.c:181
static void removeSubview(View *self, View *subview)
Definition PageView.c:72
static void addSubview(View *self, View *subview)
Definition PageView.c:49
static void setContentView(ScrollView *self, View *contentView)
Definition ScrollView.c:148
static SDL_Size sizeThatFits(const View *self)
Definition Select.c:92
@ InletTypeEnum
Definition View+JSON.h:75
@ InletTypeSize
Definition View+JSON.h:110
#define MakeInlets(...)
Creates a null-termianted array of Inlets.
Definition View+JSON.h:221
#define MakeInlet(name, type, dest, data)
Creates an Inlet with the specified parameters.
Definition View+JSON.h:216
static void addClassName(View *self, const char *className)
Definition View.c:159
static void resize(View *self, const SDL_Size *size)
Definition View.c:1527
static void layoutIfNeeded(View *self)
Definition View.c:1115
static bool didReceiveEvent(const View *self, const SDL_Event *event)
Definition View.c:668
static void invalidateStyle(View *self)
Definition View.c:1033
static SDL_Size sizeThatContains(const View *self)
Definition View.c:1659
static SDL_Rect renderFrame(const View *self)
Definition View.c:1455
static void enumerate(View *self, ViewEnumerator enumerator, ident data)
Definition View.c:749
static SDL_Rect bounds(const View *self)
Definition View.c:492
#define MakePadding(top, right, bottom, left)
Creates a ViewPadding with the given dimensions.
Definition View.h:107
#define AddPadding(a, b)
Definition View.h:113
@ ViewAutoresizingContain
Definition View.h:92
@ ViewAutoresizingFit
Definition View.h:91
@ CollectionViewAxisHorizontal
@ CollectionViewAxisVertical
CollectionViewItems are a visual representation of an item within a CollectionView.
bool isSelected
True when this item is selected, false otherwise.
View view
The superclass.
size_t(* numberOfItems)(const CollectionView *collectionView)
CollectionItemView *(* itemForObjectAtIndexPath)(const CollectionView *collectionView, const IndexPath *indexPath)
Called by the CollectionView to instantiate items.
CollectionViews display items in a grid.
SDL_Size itemSize
The item size.
CollectionViewAxis axis
The layout axis.
CollectionItemView * itemAtIndexPath(const CollectionView *self, const IndexPath *indexPath)
CollectionViewDelegate delegate
The delegate.
Array * items
The items.
CollectionViewDataSource dataSource
The data source.
ScrollView * scrollView
The scroll view.
SDL_Size itemSpacing
The item spacing.
View * contentView
The content view.
Controls are Views which capture and respond to events.
Definition Control.h:83
ControlSelection selection
The ControlSelection.
Definition Control.h:109
Inlets enable inbound data binding of View attributes through JSON.
Definition View+JSON.h:155
ScrollViews allow users to pan their internal contents.
Definition ScrollView.h:62
The Style type.
Definition Style.h:43
Dictionary * attributes
Definition Style.h:59
Views are the fundamental building blocks of ObjectivelyMVC user interfaces.
Definition View.h:134
int autoresizingMask
The ViewAutoresizing bitmask.
Definition View.h:155
layoutSubviews(View *self)
Performs layout for this View's immediate subviews.
Definition Box.c:74
SDL_Rect frame
The frame, relative to the superview.
Definition View.h:191
Spacing applied to the inside of a View's frame.
Definition View.h:100
int top
Definition View.h:101
int bottom
Definition View.h:101
int right
Definition View.h:101
int left
Definition View.h:101