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, layoutWithSize, &this->itemSize);
100
101 item->view.frame.x = x;
102 item->view.frame.y = y;
103
104 switch (this->axis) {
106 x += this->itemSize.w + this->itemSpacing.w;
107 if (x + this->itemSize.w > bounds.w) {
108 y += this->itemSize.h + this->itemSpacing.h;
109 x = bounds.x;
110 }
111 break;
113 y += this->itemSize.h + this->itemSpacing.h;
114 if (y + this->itemSize.h > bounds.h) {
115 x += this->itemSize.w + this->itemSpacing.w;
116 y = bounds.y;
117 }
118 break;
119 }
120 }
121}
122
123#pragma mark - Control
124
128static bool captureEvent(Control *self, const SDL_Event *event) {
129
130 if (event->type == SDL_EVENT_MOUSE_BUTTON_UP) {
131
132 CollectionView *this = (CollectionView *) self;
133
134 if ($((Control *) this->scrollView, isHighlighted) == false) {
135 if ($((View *) this->contentView, didReceiveEvent, event)) {
136
137 const SDL_Point point = {
138 .x = event->button.x,
139 .y = event->button.y
140 };
141
142 IndexPath *indexPath = $(this, indexPathForItemAtPoint, &point);
143
144 const CollectionItemView *item = $(this, itemAtIndexPath, indexPath);
145 if (item) {
146
147 switch (self->selection) {
149 break;
151 if (item->isSelected == false) {
152 $(this, deselectAll);
153 $(this, selectItemAtIndexPath, indexPath);
154 }
155 break;
157 if (SDL_GetModState() & (SDL_KMOD_CTRL | SDL_KMOD_GUI)) {
158 if (item->isSelected) {
159 $(this, deselectItemAtIndexPath, indexPath);
160 } else {
161 $(this, selectItemAtIndexPath, indexPath);
162 }
163 } else {
164 $(this, deselectAll);
165 $(this, selectItemAtIndexPath, indexPath);
166 }
167 break;
168 }
169
170 if (this->delegate.didModifySelection) {
172
173 this->delegate.didModifySelection(this, selectionIndexPaths);
174
175 release(selectionIndexPaths);
176 }
177 }
178
179 release(indexPath);
180 return true;
181 }
182 }
183 }
184
185 return super(Control, self, captureEvent, event);
186}
187
188#pragma mark - CollectionView
189
193static void deselectAll_enumerate(const Array *array, ident obj, ident data) {
194 $((CollectionItemView *) obj, setSelected, false);
195}
196
201static void deselectAll(CollectionView *self) {
202 $((Array *) self->items, enumerate, deselectAll_enumerate, NULL);
203}
204
209static void deselectItemAtIndexPath(CollectionView *self, const IndexPath *indexPath) {
210
211 if (indexPath) {
212 CollectionItemView *item = $(self, itemAtIndexPath, indexPath);
213 if (item) {
214 $(item, setSelected, false);
216 }
217 }
218}
219
223static void deselectItemsAtIndexPaths_enumerate(const Array *array, ident obj, ident data) {
224 $((CollectionItemView *) obj, setSelected, false);
225}
226
231static void deselectItemsAtIndexPaths(CollectionView *self, const Array *indexPaths) {
232
233 if (indexPaths) {
235 }
236}
237
242static IndexPath *indexPathForItemAtPoint(const CollectionView *self, const SDL_Point *point) {
243
244 if (self->itemSize.w && self->itemSize.h) {
245
246 const SDL_Rect frame = $(self->contentView, renderFrame);
247
248 const int itemWidth = self->itemSize.w + self->itemSpacing.w;
249 const int itemHeight = self->itemSize.h + self->itemSpacing.h;
250
251 const int rows = max(1, frame.h / itemHeight);
252 const int cols = max(1, frame.w / itemWidth);
253
254 const int x = point->x - frame.x;
255 const int y = point->y - frame.y;
256
257 const int row = y / itemHeight;
258 const int col = x / itemWidth;
259
260 int index;
261 switch (self->axis) {
263 index = row * cols + col;
264 break;
266 index = col * rows + row;
267 break;
268 }
269
270 if (index < (int) self->items->count) {
271 return $(alloc(IndexPath), initWithIndex, index);
272 }
273 }
274
275 return NULL;
276}
277
282static IndexPath *indexPathForItem(const CollectionView *self, const CollectionItemView *item) {
283
284 const ssize_t index = $((Array *) self->items, indexOfObject, (ident) item);
285 if (index > -1) {
286 return $(alloc(IndexPath), initWithIndex, index);
287 }
288
289 return NULL;
290}
291
296static CollectionView *initWithFrame(CollectionView *self, const SDL_Rect *frame) {
297
298 self = (CollectionView *) super(Control, self, initWithFrame, frame);
299 if (self) {
300 self->items = $$(Array, array);
301 assert(self->items);
302
303 self->contentView = $(alloc(View), initWithFrame, NULL);
304 assert(self->contentView);
305
306 $(self->contentView, addClassName, "contentView");
307 $(self->contentView, addClassName, "container");
308
309 self->scrollView = $(alloc(ScrollView), initWithFrame, NULL);
310 assert(self->scrollView);
311
312 $(self->scrollView, setContentView, self->contentView);
313 $((View *) self, addSubview, (View *) self->scrollView);
314 }
315
316 return self;
317}
318
323static CollectionItemView *itemAtIndexPath(const CollectionView *self, const IndexPath *indexPath) {
324
325 if (indexPath) {
326 const Array *items = (Array *) self->items;
327 const size_t index = $(indexPath, indexAtPosition, 0);
328
329 if (index < items->count) {
330 return $(items, objectAtIndex, index);
331 }
332 }
333
334 return NULL;
335}
336
341static SDL_Size naturalSize(const CollectionView *self) {
342
343 ViewPadding padding = MakePadding(0, 0, 0, 0);
344 padding = AddPadding(padding, ((View *) self)->padding);
345 padding = AddPadding(padding, ((View *) self->scrollView)->padding);
346 padding = AddPadding(padding, ((View *) self->contentView)->padding);
347
348 SDL_Size size = MakeSize(padding.left + padding.right, padding.top + padding.bottom);
349
350 View *scrollView = (View *) self->scrollView;
351 SDL_Size scrollViewSize;
352 if (scrollView->autoresizingMask & ViewAutoresizingContain) {
353 scrollViewSize = $(scrollView, sizeThatContains);
354 } else if (scrollView->autoresizingMask & ViewAutoresizingFit) {
355 scrollViewSize = $(scrollView, sizeThatFits);
356 } else {
357 scrollViewSize = $(scrollView, size);
358 }
359
360 switch (self->axis) {
362 int itemsPerRow = 1;
363 int w = scrollViewSize.w;
364 while (w > 0) {
365 w -= self->itemSize.w;
366 itemsPerRow++;
367 if (w - self->itemSpacing.w < 0) {
368 break;
369 }
370 w -= self->itemSpacing.w;
371 }
372 const int rows = ceilf(self->items->count / (float) itemsPerRow);
373 size.w += max(self->itemSize.w, scrollViewSize.w);
374 size.h += rows * (self->itemSize.h + self->itemSpacing.h);
375 }
376 break;
378 int itemsPerCol = 1;
379 int h = scrollViewSize.h;
380 while (h > 0) {
381 h -= self->itemSize.h;
382 itemsPerCol++;
383 if (h - self->itemSpacing.h < 0) {
384 break;
385 }
386 h -= self->itemSpacing.h;
387 }
388 const int cols = ceilf(self->items->count / (float) itemsPerCol);
389 size.w += cols * (self->itemSize.w + self->itemSpacing.w);
390 size.h += max(self->itemSize.h, scrollViewSize.h);
391 }
392 break;
393 }
394
395 return size;
396}
397
401static void reloadData_removeItems(const Array *array, ident obj, ident data) {
402 $((View *) data, removeSubview, (View *) obj);
403}
404
409static void reloadData(CollectionView *self) {
410
411 assert(self->dataSource.numberOfItems);
413
414 $((Array *) self->items, enumerate, reloadData_removeItems, self->contentView);
415 $(self->items, removeAllObjects);
416
417 const size_t numberOfItems = self->dataSource.numberOfItems(self);
418 for (size_t i = 0; i < numberOfItems; i++) {
419
420 IndexPath *indexPath = $(alloc(IndexPath), initWithIndex, i);
421
422 CollectionItemView *item = self->delegate.itemForObjectAtIndexPath(self, indexPath);
423 assert(item);
424
425 $(self->items, addObject, item);
426 $(self->contentView, addSubview, (View *) item);
427
428 release(item);
429 release(indexPath);
430 }
431
432 $((View *) self, setNeedsLayout);
433}
434
438static void selectAll_enumerate(const Array *array, ident obj, ident data) {
439 $((CollectionItemView *) obj, setSelected, true);
440}
441
446static void selectAll(CollectionView *self) {
447 $((Array *) self->items, enumerate, selectAll_enumerate, NULL);
448}
449
454static Array *selectionIndexPaths(const CollectionView *self) {
455
456 Array *array = $$(Array, array);
457
458 const Array *items = (Array *) self->items;
459 for (size_t i = 0; i < items->count; i++) {
460
461 const CollectionItemView *item = $(items, objectAtIndex, i);
462 if (item->isSelected) {
463
464 IndexPath *indexPath = $(self, indexPathForItem, item);
465 $(array, addObject, indexPath);
466
467 release(indexPath);
468 }
469 }
470
471 return (Array *) array;
472}
473
478static void selectItemAtIndexPath(CollectionView *self, const IndexPath *indexPath) {
479
480 if (indexPath) {
481 CollectionItemView *item = $(self, itemAtIndexPath, indexPath);
482 if (item) {
483 $(item, setSelected, true);
485 }
486 }
487}
488
492static void selectItemsAtIndexPaths_enumerate(const Array *array, ident obj, ident data) {
493 $((CollectionView *) data, selectItemAtIndexPath, (IndexPath *) obj);
494}
495
500static void selectItemsAtIndexPaths(CollectionView *self, const Array *indexPaths) {
501
502 if (indexPaths) {
503 $(indexPaths, enumerate, selectItemsAtIndexPaths_enumerate, self);
504 }
505}
506
507#pragma mark - Class lifecycle
508
512static void initialize(Class *clazz) {
513
514 ((ObjectInterface *) clazz->interface)->dealloc = dealloc;
515
516 ((ViewInterface *) clazz->interface)->applyStyle = applyStyle;
517 ((ViewInterface *) clazz->interface)->init = init;
518 ((ViewInterface *) clazz->interface)->layoutSubviews = layoutSubviews;
519
520 ((ControlInterface *) clazz->interface)->captureEvent = captureEvent;
521
522 ((CollectionViewInterface *) clazz->interface)->deselectAll = deselectAll;
523 ((CollectionViewInterface *) clazz->interface)->deselectItemAtIndexPath = deselectItemAtIndexPath;
524 ((CollectionViewInterface *) clazz->interface)->deselectItemsAtIndexPaths = deselectItemsAtIndexPaths;
525 ((CollectionViewInterface *) clazz->interface)->indexPathForItem = indexPathForItem;
526 ((CollectionViewInterface *) clazz->interface)->indexPathForItemAtPoint = indexPathForItemAtPoint;
527 ((CollectionViewInterface *) clazz->interface)->initWithFrame = initWithFrame;
528 ((CollectionViewInterface *) clazz->interface)->itemAtIndexPath = itemAtIndexPath;
529 ((CollectionViewInterface *) clazz->interface)->naturalSize = naturalSize;
530 ((CollectionViewInterface *) clazz->interface)->reloadData = reloadData;
531 ((CollectionViewInterface *) clazz->interface)->selectAll = selectAll;
532 ((CollectionViewInterface *) clazz->interface)->selectionIndexPaths = selectionIndexPaths;
533 ((CollectionViewInterface *) clazz->interface)->selectItemAtIndexPath = selectItemAtIndexPath;
534 ((CollectionViewInterface *) clazz->interface)->selectItemsAtIndexPaths = selectItemsAtIndexPaths;
535}
536
541Class *_CollectionView(void) {
542 static Class *clazz;
543 static Once once;
544
545 do_once(&once, {
546 clazz = _initialize(&(const ClassDef) {
547 .name = "CollectionView",
548 .superclass = _Control(),
549 .instanceSize = sizeof(CollectionView),
550 .interfaceSize = sizeof(CollectionViewInterface),
551 .initialize = initialize,
552 });
553 });
554
555 return clazz;
556}
557
558#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:414
static bool isHighlighted(const Control *self)
Definition Control.c:350
@ 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:416
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:231
static SDL_Size sizeThatFits(const View *self)
Definition Select.c:113
@ InletTypeEnum
Definition View+JSON.h:75
@ InletTypeSize
Definition View+JSON.h:104
#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 void addClassName(View *self, const char *className)
Definition View.c:183
static bool didReceiveEvent(const View *self, const SDL_Event *event)
Definition View.c:726
static void invalidateStyle(View *self)
Definition View.c:1092
static void setNeedsLayout(View *self)
Definition View.c:1865
static SDL_Size sizeThatContains(const View *self)
Definition View.c:1886
static void layoutWithSize(View *self, const SDL_Size *size)
Definition View.c:1267
static SDL_Rect renderFrame(const View *self)
Definition View.c:1629
static void enumerate(View *self, ViewEnumerator enumerator, ident data)
Definition View.c:807
static SDL_Rect bounds(const View *self)
Definition View.c:552
#define MakePadding(top, right, bottom, left)
Creates a ViewPadding with the given dimensions.
Definition View.h:176
#define AddPadding(a, b)
Definition View.h:182
@ ViewAutoresizingContain
Definition View.h:95
@ ViewAutoresizingFit
Definition View.h:94
@ 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:123
Inlets enable inbound data binding of View attributes through JSON.
Definition View+JSON.h:149
ScrollViews allow users to pan their internal contents.
Definition ScrollView.h:61
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:225
int autoresizingMask
The ViewAutoresizing bitmask.
Definition View.h:248
layoutSubviews(View *self)
Arranges this View's immediate subviews within its (already final) frame.
Definition Box.c:74
SDL_Rect frame
The frame, relative to the superview.
Definition View.h:301
Spacing applied to the inside of a View's frame.
Definition View.h:169
int top
Definition View.h:170
int bottom
Definition View.h:170
int right
Definition View.h:170
int left
Definition View.h:170