ObjectivelyMVC 0.1.0
Object oriented MVC framework for OpenGL, SDL2 and GNU C
All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
CollectionView.c
Go to the documentation of this file.
1/*
2 * ObjectivelyMVC: Object oriented MVC framework for OpenGL, SDL2 and GNU 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 "CollectionView.h"
27
28const EnumName CollectionViewAxisNames[] = MakeEnumNames(
29 MakeEnumAlias(CollectionViewAxisHorizontal, horizontal),
30 MakeEnumAlias(CollectionViewAxisVertical, vertical)
31);
32
33#define _Class _CollectionView
34
35#pragma mark - Object
36
40static void dealloc(Object *self) {
41
42 CollectionView *this = (CollectionView *) self;
43
44 release(this->contentView);
45 release(this->items);
46 release(this->scrollView);
47
48 super(Object, self, dealloc);
49}
50
51#pragma mark - View
52
56static void applyStyle(View *self, const Style *style) {
57
58 super(View, self, applyStyle, style);
59
60 CollectionView *this = (CollectionView *) self;
61
62 const Inlet inlets[] = MakeInlets(
63 MakeInlet("axis", InletTypeEnum, &this->axis, (ident) CollectionViewAxisNames),
64 MakeInlet("item-size", InletTypeSize, &this->itemSize, NULL),
65 MakeInlet("item-spacing", InletTypeSize, &this->itemSpacing, NULL)
66 );
67
68 $(self, bind, inlets, (Dictionary *) style->attributes);
69}
70
74static View *init(View *self) {
75 return (View *) $((CollectionView *) self, initWithFrame, NULL);
76}
77
81static void layoutSubviews(View *self) {
82
83 CollectionView *this = (CollectionView *) self;
84
85 super(View, self, layoutSubviews);
86
87 const SDL_Rect bounds = $((View *) this->scrollView, bounds);
88
89 int x = bounds.x, y = bounds.y;
90
91 const Array *items = (Array *) this->items;
92 for (size_t i = 0; i < items->count; i++) {
93
94 CollectionItemView *item = (CollectionItemView *) $(items, objectAtIndex, i);
95
96 item->view.frame.x = x;
97 item->view.frame.y = y;
98 item->view.frame.w = this->itemSize.w;
99 item->view.frame.h = this->itemSize.h;
100
101 switch (this->axis) {
103 x += this->itemSize.w + this->itemSpacing.w;
104 if (x + this->itemSize.w > bounds.w) {
105 y += this->itemSize.h + this->itemSpacing.h;
106 x = bounds.x;
107 }
108 break;
110 y += this->itemSize.h + this->itemSpacing.h;
111 if (y + this->itemSize.h > bounds.h) {
112 x += this->itemSize.w + this->itemSpacing.w;
113 y = bounds.y;
114 }
115 break;
116 }
117 }
118}
119
123static SDL_Size sizeThatFits(const View *self) {
124
125 const CollectionView *this = (CollectionView *) self;
126
127 return $(this, naturalSize);
128}
129
130#pragma mark - Control
131
135static _Bool captureEvent(Control *self, const SDL_Event *event) {
136
137 if (event->type == SDL_MOUSEBUTTONUP) {
138
139 CollectionView *this = (CollectionView *) self;
140
141 if ($((Control *) this->scrollView, isHighlighted) == false) {
142 if ($((View *) this->contentView, didReceiveEvent, event)) {
143
144 const SDL_Point point = {
145 .x = event->button.x,
146 .y = event->button.y
147 };
148
149 IndexPath *indexPath = $(this, indexPathForItemAtPoint, &point);
150
151 const CollectionItemView *item = $(this, itemAtIndexPath, indexPath);
152 if (item) {
153
154 switch (self->selection) {
156 break;
158 if (item->isSelected == false) {
159 $(this, deselectAll);
160 $(this, selectItemAtIndexPath, indexPath);
161 }
162 break;
164 if (SDL_GetModState() & (KMOD_CTRL | KMOD_GUI)) {
165 if (item->isSelected) {
166 $(this, deselectItemAtIndexPath, indexPath);
167 } else {
168 $(this, selectItemAtIndexPath, indexPath);
169 }
170 } else {
171 $(this, deselectAll);
172 $(this, selectItemAtIndexPath, indexPath);
173 }
174 break;
175 }
176
177 if (this->delegate.didModifySelection) {
179
180 this->delegate.didModifySelection(this, selectionIndexPaths);
181
182 release(selectionIndexPaths);
183 }
184 }
185
186 release(indexPath);
187 return true;
188 }
189 }
190 }
191
192 return super(Control, self, captureEvent, event);
193}
194
195#pragma mark - CollectionView
196
200static void deselectAll_enumerate(const Array *array, ident obj, ident data) {
201 $((CollectionItemView *) obj, setSelected, false);
202}
203
208static void deselectAll(CollectionView *self) {
209 $((Array *) self->items, enumerateObjects, deselectAll_enumerate, NULL);
210}
211
216static void deselectItemAtIndexPath(CollectionView *self, const IndexPath *indexPath) {
217
218 if (indexPath) {
219 CollectionItemView *item = $(self, itemAtIndexPath, indexPath);
220 if (item) {
221 $(item, setSelected, false);
223 }
224 }
225}
226
230static void deselectItemsAtIndexPaths_enumerate(const Array *array, ident obj, ident data) {
231 $((CollectionItemView *) obj, setSelected, false);
232}
233
238static void deselectItemsAtIndexPaths(CollectionView *self, const Array *indexPaths) {
239
240 if (indexPaths) {
241 $(indexPaths, enumerateObjects, deselectItemsAtIndexPaths_enumerate, self);
242 }
243}
244
249static IndexPath *indexPathForItemAtPoint(const CollectionView *self, const SDL_Point *point) {
250
251 if (self->itemSize.w && self->itemSize.h) {
252
253 const SDL_Rect frame = $(self->contentView, renderFrame);
254
255 const int itemWidth = self->itemSize.w + self->itemSpacing.w;
256 const int itemHeight = self->itemSize.h + self->itemSpacing.h;
257
258 const int rows = max(1, frame.h / itemHeight);
259 const int cols = max(1, frame.w / itemWidth);
260
261 const int x = point->x - frame.x;
262 const int y = point->y - frame.y;
263
264 const int row = y / itemHeight;
265 const int col = x / itemWidth;
266
267 int index;
268 switch (self->axis) {
270 index = row * cols + col;
271 break;
273 index = col * rows + row;
274 break;
275 }
276
277 if (index < (int) self->items->array.count) {
278 return $(alloc(IndexPath), initWithIndex, index);
279 }
280 }
281
282 return NULL;
283}
284
289static IndexPath *indexPathForItem(const CollectionView *self, const CollectionItemView *item) {
290
291 const ssize_t index = $((Array *) self->items, indexOfObject, (ident) item);
292 if (index > -1) {
293 return $(alloc(IndexPath), initWithIndex, index);
294 }
295
296 return NULL;
297}
298
303static CollectionView *initWithFrame(CollectionView *self, const SDL_Rect *frame) {
304
305 self = (CollectionView *) super(Control, self, initWithFrame, frame);
306 if (self) {
307
308 self->items = $$(MutableArray, array);
309
310 self->contentView = $(alloc(View), initWithFrame, NULL);
311 assert(self->contentView);
312
313 $(self->contentView, addClassName, "contentView");
314 $(self->contentView, addClassName, "container");
315
316 self->scrollView = $(alloc(ScrollView), initWithFrame, NULL);
317 assert(self->scrollView);
318
319 $(self->scrollView, setContentView, self->contentView);
320
321 $((View *) self, addSubview, (View *) self->scrollView);
322 }
323
324 return self;
325}
326
331static CollectionItemView *itemAtIndexPath(const CollectionView *self, const IndexPath *indexPath) {
332
333 if (indexPath) {
334 const Array *items = (Array *) self->items;
335 const size_t index = $(indexPath, indexAtPosition, 0);
336
337 if (index < items->count) {
338 return $(items, objectAtIndex, index);
339 }
340 }
341
342 return NULL;
343}
344
350
351 ViewPadding padding = MakePadding(0, 0, 0, 0);
352 padding = AddPadding(padding, ((View *) self)->padding);
353 padding = AddPadding(padding, ((View *) self->scrollView)->padding);
354 padding = AddPadding(padding, ((View *) self->contentView)->padding);
355
356 SDL_Size size = MakeSize(padding.left + padding.right, padding.top + padding.bottom);
357
358 const SDL_Rect bounds = $((View *) self->scrollView->contentView, bounds);
359
360 switch (self->axis) {
362 int itemsPerRow = 1;
363 int w = bounds.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->array.count / (float) itemsPerRow);
373 size.w += max(self->itemSize.w, bounds.w);
374 size.h += rows * (self->itemSize.h + self->itemSpacing.h);
375 }
376 break;
378 int itemsPerCol = 1;
379 int h = bounds.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->array.count / (float) itemsPerCol);
389 size.w += cols * (self->itemSize.w + self->itemSpacing.w);
390 size.h += max(self->itemSize.h, bounds.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, enumerateObjects, 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)->needsLayout = true;
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, enumerateObjects, selectAll_enumerate, NULL);
448}
449
454static Array *selectionIndexPaths(const CollectionView *self) {
455
456 MutableArray *array = $$(MutableArray, 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, enumerateObjects, 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 ((ViewInterface *) clazz->interface)->sizeThatFits = sizeThatFits;
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
561
static void selectItemsAtIndexPaths_enumerate(const Array *array, ident obj, ident data)
ArrayEnumerator for item selection.
const EnumName CollectionViewAxisNames[]
static void deselectAll_enumerate(const Array *array, ident obj, ident data)
ArrayEnumerator for all item deselection.
static void dealloc(Object *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 reloadData_removeItems(const Array *array, ident obj, ident data)
ArrayEnumerator to remove CollectionItemViews from the collection's contentView.
The CollectionView type.
@ ControlSelectionMultiple
Definition: Control.h:58
@ ControlSelectionSingle
Definition: Control.h:57
@ ControlSelectionNone
Definition: Control.h:56
#define MakeSize(w, h)
Creates an SDL_Size with the given dimensions.
Definition: Types.h:79
@ 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
#define MakePadding(top, right, bottom, left)
Creates a ViewPadding with the given dimensions.
Definition: View.h:106
#define AddPadding(a, b)
Definition: View.h:112
@ CollectionViewAxisHorizontal
@ CollectionViewAxisVertical
Box * initWithFrame(Box *self, const SDL_Rect *frame)
Initializes this Box with the given frame.
Definition: Box.c:92
CollectionViewItems are a visual representation of an item within a CollectionView.
void setSelected(CollectionItemView *self, _Bool isSelected)
Sets the selected state of this item.
View view
The superclass.
_Bool isSelected
True when this item is selected, false otherwise.
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.
void deselectItemsAtIndexPaths(CollectionView *self, const Array *indexPaths)
Deselects the items at the given index paths.
SDL_Size itemSize
The item size.
Array * selectionIndexPaths(const CollectionView *self)
CollectionViewAxis axis
The layout axis.
IndexPath * indexPathForItemAtPoint(const CollectionView *self, const SDL_Point *point)
CollectionItemView * itemAtIndexPath(const CollectionView *self, const IndexPath *indexPath)
CollectionViewDelegate delegate
The delegate.
IndexPath * indexPathForItem(const CollectionView *self, const CollectionItemView *item)
void deselectAll(CollectionView *self)
Deselects all items in this CollectionView.
void reloadData(CollectionView *self)
Reloads this CollectionView's visible items.
Class * _CollectionView(void)
The CollectionView archetype.
MutableArray * items
The items.
void selectItemsAtIndexPaths(CollectionView *self, const Array *indexPaths)
Selects the items at the given index paths.
CollectionViewDataSource dataSource
The data source.
void selectItemAtIndexPath(CollectionView *self, const IndexPath *indexPath)
Selects the item at the given index path.
SDL_Size naturalSize(const CollectionView *self)
void selectAll(CollectionView *self)
Selects all items in this CollectionView.
ScrollView * scrollView
The scroll view.
void deselectItemAtIndexPath(CollectionView *self, const IndexPath *indexPath)
Deselects the item at the given index path.
CollectionView * init(CollectionView *self, const SDL_Rect *frame)
Initializes this CollectionView with the specified frame and style.
SDL_Size itemSpacing
The item spacing.
View * contentView
The content view.
Controls are Views which capture events and dispatch Actions.
Definition: Control.h:83
_Bool captureEvent(Control *self, const SDL_Event *event)
Captures a given event, potentially altering the state of this Control.
Definition: Button.c:76
Class * _Control(void)
The Control archetype.
Definition: Control.c:379
_Bool isHighlighted(const Control *self)
Definition: Control.c:317
ControlSelection selection
The ControlSelection.
Definition: Control.h:115
SDL_Size size(const Image *self)
Definition: Image.c:181
Inlets enable inbound data binding of View attributes through JSON.
Definition: View+JSON.h:155
The SDL_Size type.
Definition: Types.h:62
int w
Definition: Types.h:63
int h
Definition: Types.h:63
ScrollViews allow users to pan their internal contents.
Definition: ScrollView.h:62
void setContentView(ScrollView *self, View *contentView)
Sets the content view of this ScrollView.
Definition: ScrollView.c:150
View * contentView
The content View.
Definition: ScrollView.h:83
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:133
SDL_Rect bounds(const View *self)
Definition: View.c:394
_Bool bind(View *self, const Inlet *inlets, const Dictionary *dictionary)
Performs data binding for the Inlets described in dictionary.
void invalidateStyle(View *self)
Invalidates the computed Style for this View and its descendants.
Definition: View.c:822
void addSubview(View *self, View *subview)
Adds a subview to this view, to be drawn above its siblings.
Definition: PageView.c:35
void addClassName(View *self, const char *className)
Adds the given class name to this View.
Definition: View.c:120
void applyStyle(View *self, const Style *style)
Applies the given Style to this View.
_Bool didReceiveEvent(const View *self, const SDL_Event *event)
Definition: View.c:546
void sizeThatFits(const View *self)
SDL_Rect renderFrame(const View *self)
Definition: View.c:1275
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:190
void removeSubview(View *self, View *subview)
Removes the given subview from this View.
Definition: PageView.c:58
Spacing applied to the inside of a View's frame.
Definition: View.h:99
int top
Definition: View.h:100
int bottom
Definition: View.h:100
int right
Definition: View.h:100
int left
Definition: View.h:100