ObjectivelyMVC
Object oriented MVC framework for SDL3 and GNU C
Loading...
Searching...
No Matches
View.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 <stdlib.h>
26#include <string.h>
27
28#include <Objectively.h>
29
30#include "Log.h"
31#include "View.h"
32#include "ViewController.h"
33#include "WindowController.h"
34#include "Window.h"
35
38
39const EnumName ViewAlignmentNames[] = MakeEnumNames(
40 MakeEnumAlias(ViewAlignmentNone, none),
41 MakeEnumAlias(ViewAlignmentTop, top),
42 MakeEnumAlias(ViewAlignmentMiddle, middle),
43 MakeEnumAlias(ViewAlignmentBottom, bottom),
44 MakeEnumAlias(ViewAlignmentLeft, left),
45 MakeEnumAlias(ViewAlignmentCenter, center),
46 MakeEnumAlias(ViewAlignmentRight, right),
47 MakeEnumAlias(ViewAlignmentTopLeft, top-left),
48 MakeEnumAlias(ViewAlignmentTopCenter, top-center),
49 MakeEnumAlias(ViewAlignmentTopRight, top-right),
50 MakeEnumAlias(ViewAlignmentMiddleLeft, middle-left),
51 MakeEnumAlias(ViewAlignmentMiddleCenter, middle-center),
52 MakeEnumAlias(ViewAlignmentMiddleRight, middle-right),
53 MakeEnumAlias(ViewAlignmentBottomLeft, bottom-left),
54 MakeEnumAlias(ViewAlignmentBottomCenter, bottom-center),
55 MakeEnumAlias(ViewAlignmentBottomRight, bottom-right),
56 MakeEnumAlias(ViewAlignmentInternal, internal)
57);
58
59const EnumName ViewAutoresizingNames[] = MakeEnumNames(
60 MakeEnumAlias(ViewAutoresizingNone, none),
61 MakeEnumAlias(ViewAutoresizingWidth, width),
62 MakeEnumAlias(ViewAutoresizingHeight, height),
63 MakeEnumAlias(ViewAutoresizingFill, fill),
64 MakeEnumAlias(ViewAutoresizingFit, fit),
65 MakeEnumAlias(ViewAutoresizingContain, contain)
66);
67
68#define _Class _View
69
70#pragma mark - Object
71
75static bool filterViewEvents(void *data, SDL_Event *event) {
76
77 if (event->type == MVC_VIEW_EVENT && event->user.data1 == data) {
78 return false;
79 }
80
81 return true;
82}
83
87static void nullifySuperview(View *subview, ident data) {
88 subview->superview = NULL;
89}
90
94static void dealloc(Object *self) {
95
96 View *this = (View *) self;
97
98 SDL_FilterEvents(filterViewEvents, this);
99
100 $(this, moveToWindow, NULL);
101
102 $(this, enumerateSubviews, nullifySuperview, NULL);
103
104 free(this->identifier);
105
106 release(this->classNames);
107 release(this->computedStyle);
108 release(this->style);
109 release(this->stylesheet);
110 release(this->subviews);
111 release(this->warnings);
112
113 this->superview = NULL;
114
115 super(Object, self, dealloc);
116}
117
121static String *description(const Object *self) {
122
123 View *this = (View *) self;
124 const SDL_Rect bounds = $(this, bounds);
125
126 String *classNames = $((Object *) this->classNames, description);
127 String *description = str("%s@%p %s [%d, %d, %d, %d]",
128 this->identifier ?: classnameof(self),
129 self,
130 classNames->chars,
131 bounds.x, bounds.y, bounds.w, bounds.h);
132
133 release(classNames);
134 return description;
135}
136
137#pragma mark - View
138
143static bool acceptsKeyResponder(const View *self) {
144 return false;
145}
146
151static bool acceptsTouchResponder(const View *self) {
152 return false;
153}
154
159static void addClassName(View *self, const char *className) {
160
161 if (className) {
162
163 String *string = $$(String, stringWithCharacters, className);
164 assert(string);
165
166 $(self->classNames, addObject, string);
167 release(string);
168
169 $(self, invalidateStyle);
170 }
171}
172
177static void addSubview(View *self, View *subview) {
178 $(self, addSubviewRelativeTo, subview, NULL, ViewPositionAfter);
179}
180
185static void addSubviewRelativeTo(View *self, View *subview, View *other, ViewPosition position) {
186
187 assert(subview);
188 assert(subview != other);
189
190 retain(subview);
191
192 $(subview, removeFromSuperview);
193
194 if (other && other->superview == self) {
195
196 const Array *subviews = (Array *) self->subviews;
197 const ssize_t index = $(subviews, indexOfObject, other);
198
199 if (position == ViewPositionAfter) {
200 if (index == (ssize_t) (subviews->count - 1)) {
201 $(self->subviews, addObject, subview);
202 } else {
203 $(self->subviews, insertObjectAtIndex, subview, index + 1);
204 }
205 } else {
206 $(self->subviews, insertObjectAtIndex, subview, index);
207 }
208 } else {
209 $(self->subviews, addObject, subview);
210 }
211
212 release(subview);
213
214 subview->superview = self;
215
216 $(subview, moveToWindow, self->window);
217
218 $(subview, invalidateStyle);
219
220 self->needsLayout = true;
221}
222
227static View *ancestorWithIdentifier(const View *self, const char *identifier) {
228
229 assert(identifier);
230
231 View *view = (View *) self;
232 while (view) {
233 if (view->identifier) {
234 if (strcmp(identifier, view->identifier) == 0) {
235 return view;
236 }
237 }
238 view = view->superview;
239 }
240
241 return NULL;
242}
243
248static void applyStyle(View *self, const Style *style) {
249
250 assert(style);
251
252 const Inlet inlets[] = MakeInlets(
253 MakeInlet("alignment", InletTypeEnum, &self->alignment, (ident) ViewAlignmentNames),
254 MakeInlet("autoresizing-mask", InletTypeEnum, &self->autoresizingMask, (ident) ViewAutoresizingNames),
255 MakeInlet("background-color", InletTypeColor, &self->backgroundColor, NULL),
256 MakeInlet("border-color", InletTypeColor, &self->borderColor, NULL),
257 MakeInlet("border-width", InletTypeInteger, &self->borderWidth, NULL),
258 MakeInlet("clips-subviews", InletTypeBool, &self->clipsSubviews, NULL),
259 MakeInlet("frame", InletTypeRectangle, &self->frame, NULL),
260 MakeInlet("hidden", InletTypeBool, &self->hidden, NULL),
261 MakeInlet("height", InletTypeInteger, &self->frame.h, NULL),
262 MakeInlet("left", InletTypeInteger, &self->frame.x, NULL),
263 MakeInlet("max-height", InletTypeInteger, &self->maxSize.h, NULL),
264 MakeInlet("max-size", InletTypeSize, &self->maxSize, NULL),
265 MakeInlet("max-width", InletTypeInteger, &self->maxSize.w, NULL),
266 MakeInlet("min-height", InletTypeInteger, &self->minSize.h, NULL),
267 MakeInlet("min-size", InletTypeSize, &self->minSize, NULL),
268 MakeInlet("min-width", InletTypeInteger, &self->minSize.w, NULL),
269 MakeInlet("padding", InletTypeRectangle, &self->padding, NULL),
270 MakeInlet("padding-top", InletTypeInteger, &self->padding.top, NULL),
271 MakeInlet("padding-right", InletTypeInteger, &self->padding.right, NULL),
272 MakeInlet("padding-bottom", InletTypeInteger, &self->padding.bottom, NULL),
273 MakeInlet("padding-left", InletTypeInteger, &self->padding.left, NULL),
274 MakeInlet("top", InletTypeInteger, &self->frame.y, NULL),
275 MakeInlet("width", InletTypeInteger, &self->frame.w, NULL)
276 );
277
278 $(self, bind, inlets, style->attributes);
279}
280
285static void applyTheme(View *self, const Theme *theme) {
286
287 assert(theme);
288
289 Style *computedStyle = $(theme, computeStyle, self);
290 assert(computedStyle);
291
292 if (!$(self->computedStyle, isComputedEqual, computedStyle)) {
293
294 release(self->computedStyle);
295 self->computedStyle = retain(computedStyle);
296
298 $(self, applyStyle, self->computedStyle);
299 }
300
301 release(computedStyle);
302}
303
308static void applyThemeIfNeeded(View *self, const Theme *theme);
309
313static void _applyThemeIfNeeded(View *view, ident data) {
314 applyThemeIfNeeded(view, (const Theme *) data);
315}
316
317static void applyThemeIfNeeded(View *self, const Theme *theme) {
318
319 assert(theme);
320
322
323 if (self->needsApplyTheme) {
324
326
327 $(self, applyTheme, theme);
328
329 self->needsApplyTheme = false;
330 }
331}
332
337static void attachStylesheet(View *self, SDL_Window *window) {
338
339 assert(window);
340
341 if (self->stylesheet) {
342 Theme *theme = $$(Theme, theme, window);
343 if (theme) {
344 $(theme, addStylesheet, self->stylesheet);
345 }
346 }
347}
348
353static void awakeWithCharacters(View *self, const char *chars) {
354
355 Data *data = $$(Data, dataWithConstMemory, (uint8_t *) chars, strlen(chars));
356
357 $(self, awakeWithData, data);
358
359 release(data);
360}
361
366static void awakeWithData(View *self, const Data *data) {
367
368 JSONContext *ctx = $(alloc(JSONContext), init);
369 Dictionary *dictionary = $(ctx, objectFromData, data, 0);
370
371 if (dictionary) {
372 $(self, awakeWithDictionary, dictionary);
373 release(dictionary);
374 } else {
375 MVC_LogError("Failed to parse JSON for %s\n", classnameof(self));
376 }
377
378 release(ctx);
379}
380
385static void awakeWithDictionary(View *self, const Dictionary *dictionary) {
386
387 assert(dictionary);
388
389 const Inlet inlets[] = MakeInlets(
390 MakeInlet("identifier", InletTypeCharacters, &self->identifier, NULL),
391 MakeInlet("classNames", InletTypeClassNames, &self, NULL),
392 MakeInlet("style", InletTypeStyle, &self, NULL),
393 MakeInlet("subviews", InletTypeSubviews, &self, NULL)
394 );
395
396 $(self, bind, inlets, dictionary);
397}
398
403static void awakeWithResource(View *self, const Resource *resource) {
404
405 assert(resource);
406
407 $(self, awakeWithData, resource->data);
408}
409
414static void awakeWithResourceName(View *self, const char *name) {
415
416 Resource *resource = $$(Resource, resourceWithName, name);
417
418 $(self, awakeWithResource, resource);
419
420 release(resource);
421}
422
427static void becomeKeyResponder(View *self) {
428
429 assert(self->window);
430
431 if (!$(self, isKeyResponder)) {
432 SDL_PropertiesID props = SDL_GetWindowProperties(self->window);
433
434 View *keyResponder = SDL_GetPointerProperty(props, "keyResponder", NULL);
435 if (keyResponder) {
437 }
438
439 String *path = $(self, path);
440 MVC_LogDebug("%s\n", path->chars);
441 release(path);
442
443 SDL_SetPointerProperty(props, "keyResponder", self);
444 }
445}
446
451static void becomeTouchResponder(View *self) {
452
453 assert(self->window);
454
455 if (!$(self, isTouchResponder)) {
456 SDL_PropertiesID props = SDL_GetWindowProperties(self->window);
457
458 View *touchResponder = SDL_GetPointerProperty(props, "touchResponder", NULL);
459 if (touchResponder) {
461 }
462
463 String *path = $(self, path);
464 MVC_LogDebug("%s\n", path->chars);
465 release(path);
466
467 SDL_SetPointerProperty(props, "touchResponder", self);
468 }
469}
470
475static bool _bind(View *self, const Inlet *inlets, const Dictionary *dictionary) {
476
477 if (inlets) {
478 if (bindInlets(inlets, dictionary)) {
479 self->needsApplyTheme = true;
480 self->needsLayout = true;
481 return true;
482 }
483 }
484
485 return false;
486}
487
492static SDL_Rect bounds(const View *self) {
493
494 const SDL_Size size = $(self, size);
495
496 const SDL_Rect bounds = {
497 .x = self->padding.left,
498 .y = self->padding.top,
499 .w = max(0, size.w - (self->padding.left + self->padding.right)),
500 .h = max(0, size.h - (self->padding.top + self->padding.bottom)),
501 };
502
503 return bounds;
504}
505
510static void bringSubviewToFront(View *self, View *subview) {
511
512 assert(subview);
513
514 if (subview->superview == self) {
515
516 View *last = $((Array *) self->subviews, lastObject);
517 if (last != subview) {
518 $(self, addSubviewRelativeTo, subview, last, ViewPositionAfter);
519 }
520 }
521}
522
526static bool clearWarnings_predicate(const ident obj, ident data) {
527
528 const Warning *warning = obj;
529 const WarningType type = *(WarningType *) data;
530
531 return (warning->type & type) == 0;
532}
533
538static void clearWarnings(const View *self, WarningType type) {
539 $(self->warnings, filter, clearWarnings_predicate, &type);
540}
541
546static SDL_Rect clippingFrame(const View *self) {
547
548 SDL_Rect frame = $(self, renderFrame);
549
550 if (self->borderWidth && self->borderColor.a) {
551 for (int i = 0; i < self->borderWidth; i++) {
552 frame.x -= 1;
553 frame.y -= 1;
554 frame.w += 2;
555 frame.h += 2;
556 }
557 }
558
559 const View *superview = self->superview;
560 while (superview) {
561 if (superview->clipsSubviews) {
562 const SDL_Rect clippingFrame = $(superview, clippingFrame);
563 if (SDL_GetRectIntersection(&clippingFrame, &frame, &frame) == false) {
564 frame.w = frame.h = 0;
565 break;
566 }
567 }
568 superview = superview->superview;
569 }
570
571 return frame;
572}
573
578static bool containsPoint(const View *self, const SDL_Point *point) {
579
580 const SDL_Rect frame = $(self, clippingFrame);
581
582 return (bool) SDL_PointInRect(point, &frame);
583}
584
589static int depth(const View *self) {
590 return (self->superview ? $(self->superview, depth) + 1 : 0);
591}
592
597static View *descendantWithIdentifier(const View *self, const char *identifier) {
598
599 assert(identifier);
600
601 if (self->identifier) {
602 if (strcmp(identifier, self->identifier) == 0) {
603 return (View *) self;
604 }
605 }
606
607 const Array *subviews = (Array *) self->subviews;
608 for (size_t i = 0; i < subviews->count; i++) {
609
610 const View *subview = subviews->elements[i];
611 View *descendant = $(subview, descendantWithIdentifier, identifier);
612 if (descendant) {
613 return descendant;
614 }
615 }
616
617 return NULL;
618}
619
624static void detachStylesheet(View *self, SDL_Window *window) {
625
626 assert(window);
627
628 if (self->stylesheet) {
629 Theme *theme = $$(Theme, theme, window);
630 if (theme) {
632 }
633 }
634}
635
640static void didMoveToWindow(View *self, SDL_Window *window) {
641
642 if (window) {
643 $(self, attachStylesheet, window);
644
645 if (self->superview == NULL) {
646 $(self, sizeToFill);
647 }
648
649 self->needsLayout = true;
650 }
651}
652
657static bool didReceiveEvent(const View *self, const SDL_Event *event) {
658
659 if ($(self, isKeyResponder)) {
660 switch (event->type) {
661 case SDL_EVENT_KEY_DOWN:
662 case SDL_EVENT_KEY_UP:
663 case SDL_EVENT_TEXT_INPUT:
664 return true;
665 }
666 }
667
668 if ($(self, isVisible)) {
669
670 SDL_Point point;
671
672 switch (event->type) {
673 case SDL_EVENT_MOUSE_BUTTON_DOWN:
674 case SDL_EVENT_MOUSE_BUTTON_UP:
675 point = MakePoint(event->button.x, event->button.y);
676 break;
677 case SDL_EVENT_MOUSE_MOTION:
678 point = MakePoint(event->motion.x, event->motion.y);
679 break;
680 case SDL_EVENT_MOUSE_WHEEL: {
681 float mx, my;
682 SDL_GetMouseState(&mx, &my);
683 point = MakePoint(mx, my);
684 break;
685 }
686 default:
687 return false;
688 }
689
690 return $(self, containsPoint, &point);
691 }
692
693 return false;
694}
695
700static void draw(View *self, Renderer *renderer);
701
705static void _draw(View *view, ident data) {
706 draw(view, (Renderer *) data);
707}
708
709static void draw(View *self, Renderer *renderer) {
710
711 assert(self->window);
712
713 if (self->hidden == false) {
714
715 $(renderer, drawView, self);
716
717 $(self, enumerateSubviews, _draw, renderer);
718 }
719}
720
725static void emitViewEvent(View *self, ViewEvent code, ident data) {
726 SDL_PushEvent((SDL_Event *) &(const SDL_UserEvent) {
727 .type = MVC_VIEW_EVENT,
728 .code = code,
729 .data1 = self,
730 .data2 = data
731 });
732}
733
738static void enumerate(View *self, ViewEnumerator enumerator, ident data) {
739
740 assert(enumerator);
741
742 enumerator(self, data);
743
744 $(self, enumerateDescendants, enumerator, data);
745}
746
751static void enumerateAdjacent(const View *self, ViewEnumerator enumerator, ident data) {
752
753 assert(enumerator);
754
755 if (self->superview) {
756 const Array *siblings = (Array *) self->superview->subviews;
757 const ssize_t index = $(siblings, indexOfObject, (const ident) self);
758 if (index > 0) {
759 enumerator($(siblings, objectAtIndex, index - 1), data);
760 }
761 if (index < (ssize_t) (siblings->count - 1)) {
762 enumerator($(siblings, objectAtIndex, index + 1), data);
763 }
764 }
765}
766
771static void enumerateAncestors(const View *self, ViewEnumerator enumerator, ident data) {
772
773 assert(enumerator);
774
775 for (View *view = self->superview; view; view = view->superview) {
776 enumerator(view, data);
777 }
778}
779
784static void enumerateSelection(View *self, const char *rule, ViewEnumerator enumerator, ident data) {
785
786 Selector *selector = $(alloc(Selector), initWithRule, rule);
787 assert(selector);
788
789 $(selector, enumerateSelection, self, enumerator, data);
790 release(selector);
791}
792
797static void enumerateDescendants(const View *self, ViewEnumerator enumerator, ident data) {
798
799 assert(enumerator);
800
801 const Array *subviews = (Array *) self->subviews;
802 for (size_t i = 0; i < subviews->count; i++) {
803
804 View *subview = subviews->elements[i];
805 enumerator(subview, data);
806
807 $(subview, enumerateDescendants, enumerator, data);
808 }
809}
810
815static void enumerateSiblings(const View *self, ViewEnumerator enumerator, ident data) {
816
817 assert(enumerator);
818
819 if (self->superview) {
820
821 const Array *siblings = (Array *) self->superview->subviews;
822 for (size_t i = 0; i < siblings->count; i++) {
823 View *sibling = siblings->elements[i];
824 if (sibling != self) {
825 enumerator(sibling, data);
826 }
827 }
828 }
829}
830
835static void enumerateSubviews(const View *self, ViewEnumerator enumerator, ident data) {
836
837 assert(enumerator);
838
839 const Array *subviews = (Array *) self->subviews;
840 for (size_t i = 0; i < subviews->count; i++) {
841 enumerator((View *) subviews->elements[i], data);
842 }
843}
844
849static void enumerateSuperview(const View *self, ViewEnumerator enumerator, ident data) {
850
851 assert(enumerator);
852
853 if (self->superview) {
854 enumerator(self->superview, data);
855 }
856}
857
862static void enumerateVisible(View *self, ViewEnumerator enumerator, ident data) {
863
864 if ($(self, isVisible) == false) {
865 return;
866 }
867
868 enumerator(self, data);
869
870 const Array *subviews = (Array *) self->subviews;
871 for (size_t i = 0; i < subviews->count; i++) {
872
873 View *subview = subviews->elements[i];
874 $(subview, enumerateVisible, enumerator, data);
875 }
876}
877
881static bool hasClassName_predicate(const ident obj, ident data) {
882 return strcmp(((String *) obj)->chars, (const char *) data) == 0;
883}
884
889static bool hasClassName(const View *self, const char *className) {
890
891 if (className) {
892 return $((Set *) self->classNames, containsObjectMatching, hasClassName_predicate, (ident) className);
893 }
894
895 return false;
896}
897
898typedef struct {
899 SDL_Rect bounds;
901} Overflow;
902
906static void hasOverflow_enumerate(View *view, ident data) {
907
908 Overflow *overflow = data;
909
910 if (!view->hidden) {
911
912 const SDL_Rect bounds = $(view, bounds);
913
914 if (bounds.x + bounds.w > overflow->bounds.w || bounds.y + bounds.h > overflow->bounds.h) {
915
916 overflow->hasOverflow = true;
917
918 $(view, warn, WarningTypeLayout, "Exceeds superview bounds [%d %d %d %d]",
919 overflow->bounds.x, overflow->bounds.y, overflow->bounds.w, overflow->bounds.h);
920 }
921 }
922}
923
928static bool hasOverflow(const View *self) {
929
930 Overflow overflow = {
931 .bounds = $(self, bounds)
932 };
933
934 $(self, enumerateSubviews, hasOverflow_enumerate, (ident) &overflow);
935
936 return overflow.hasOverflow;
937}
938
943static View *hitTest(const View *self, const SDL_Point *point) {
944
945 if (self->hidden == false) {
946
947 if ($(self, containsPoint, point)) {
948
949 const Array *subviews = (Array *) self->subviews;
950 for (size_t i = subviews->count; i; i--) {
951
952 const View *subview = subviews->elements[i - 1];
953 const View *view = $(subview, hitTest, point);
954 if (view) {
955 return (View *) view;
956 }
957 }
958
959 return (View *) self;
960 }
961 }
962
963 return NULL;
964}
965
970static View *init(View *self) {
971 return $(self, initWithFrame, NULL);
972}
973
978static View *initWithFrame(View *self, const SDL_Rect *frame) {
979
980 self = (View *) super(Object, self, init);
981 if (self) {
982
983 if (frame) {
984 self->frame = *frame;
985 }
986
987 self->classNames = $$(Set, setWithCapacity, 0);
988 assert(self->classNames);
989
990 self->computedStyle = $(alloc(Style), initWithAttributes, NULL);
991 assert(self->computedStyle);
992
993 self->subviews = $$(Array, arrayWithCapacity, 0);
994 assert(self->subviews);
995
996 self->style = $(alloc(Style), initWithAttributes, NULL);
997 assert(self->style);
998
999 self->warnings = $$(Array, arrayWithCapacity, 0);
1000 assert(self->warnings);
1001
1002 self->maxSize = MakeSize(INT32_MAX, INT32_MAX);
1003
1004 self->needsApplyTheme = true;
1005 self->needsLayout = true;
1006 }
1007
1008 return self;
1009}
1010
1014static void invalidateStyle_enumerate(View *view, ident data) {
1015 view->needsApplyTheme = true;
1016}
1017
1022static void invalidateStyle(View *self) {
1023 $(self, enumerate, invalidateStyle_enumerate, NULL);
1024}
1025
1030static bool isContainer(const View *self) {
1032}
1033
1038static bool isDescendantOfView(const View *self, const View *view) {
1039
1040 assert(view);
1041
1042 while (self) {
1043 if (self == view) {
1044 return true;
1045 }
1046 self = self->superview;
1047 }
1048
1049 return false;
1050}
1051
1056static bool isKeyResponder(const View *self) {
1057
1058 if (self->window) {
1059 return SDL_GetPointerProperty(SDL_GetWindowProperties(self->window), "keyResponder", NULL) == self;
1060 } else {
1061 return false;
1062 }
1063}
1064
1069static bool isTouchResponder(const View *self) {
1070
1071 if (self->window) {
1072 return SDL_GetPointerProperty(SDL_GetWindowProperties(self->window), "touchResponder", NULL) == self;
1073 } else {
1074 return false;
1075 }
1076}
1077
1082static bool isVisible(const View *self) {
1083
1084 for (const View *view = self; view; view = view->superview) {
1085 if (view->hidden) {
1086 return false;
1087 }
1088 }
1089
1090 return true;
1091}
1092
1096static void layoutIfNeeded_enumerate(View *subview, ident data) {
1097 $(subview, layoutIfNeeded);
1098}
1099
1104static void layoutIfNeeded(View *self) {
1105
1107
1108 if (self->needsLayout) {
1109
1111
1112 $(self, layoutSubviews);
1113
1114 self->needsLayout = false;
1115 }
1116}
1117
1122static void layoutSubviews(View *self) {
1123
1125 $(self, sizeToContain);
1126 } else if (self->autoresizingMask & ViewAutoresizingFit) {
1127 $(self, sizeToFit);
1128 }
1129
1130 const SDL_Rect bounds = $(self, bounds);
1131
1132 const Array *subviews = (Array *) self->subviews;
1133 for (size_t i = 0; i < subviews->count; i++) {
1134
1135 View *subview = subviews->elements[i];
1136
1137 SDL_Size subviewSize = $(subview, size);
1138
1139 if (subview->autoresizingMask & ViewAutoresizingWidth) {
1140 subviewSize.w = bounds.w;
1141 }
1142
1144 subviewSize.h = bounds.h;
1145 }
1146
1147 $(subview, resize, &subviewSize);
1148 $(subview, layoutIfNeeded);
1149
1150 switch (subview->alignment & ViewAlignmentMaskHorizontal) {
1151 case ViewAlignmentLeft:
1152 subview->frame.x = 0;
1153 break;
1155 subview->frame.x = (bounds.w - subview->frame.w) * 0.5f;
1156 break;
1157 case ViewAlignmentRight:
1158 subview->frame.x = bounds.w - subview->frame.w;
1159 break;
1160 }
1161
1162 switch (subview->alignment & ViewAlignmentMaskVertical) {
1164 subview->frame.y = 0;
1165 break;
1167 subview->frame.y = (bounds.h - subview->frame.h) * 0.5f;
1168 break;
1170 subview->frame.y = bounds.h - subview->frame.h;
1171 break;
1172 }
1173 }
1174}
1175
1180static bool matchesSelector(const View *self, const SimpleSelector *simpleSelector) {
1181
1182 assert(simpleSelector);
1183
1184 const char *pattern = simpleSelector->pattern;
1185
1186 switch (simpleSelector->type) {
1188 return false;
1189
1191 return true;
1192
1194 const Class *clazz = classForName(pattern);
1195 if (clazz) {
1196 return $((Object *) self, isKindOfClass, clazz);
1197 }
1198 }
1199 break;
1200
1202 return $(self, hasClassName, pattern);
1203
1205 if (self->identifier) {
1206 return strcmp(self->identifier, pattern) == 0;
1207 } else {
1208 return false;
1209 }
1210
1212 if (strcmp("first-child", pattern) == 0) {
1213 if (self->superview) {
1214 return $((Array *) self->superview->subviews, firstObject) == self;
1215 }
1216 } else if (strcmp("last-child", pattern) == 0) {
1217 if (self->superview) {
1218 return $((Array *) self->superview->subviews, lastObject) == self;
1219 }
1220 } else if (strcmp("nth-child(even)", pattern) == 0) {
1221 if (self->superview) {
1222 return ($((Array *) self->superview->subviews, indexOfObject, (ident) self) & 1) == 0;
1223 }
1224 } else if (strcmp("nth-child(odd)", pattern) == 0) {
1225 if (self->superview) {
1226 return $((Array *) self->superview->subviews, indexOfObject, (ident) self) & 1;
1227 }
1228 } else if (strcmp("hover", pattern) == 0) {
1229 float mx, my;
1230 SDL_GetMouseState(&mx, &my);
1231 SDL_Point point = MakePoint(mx, my);
1232 return $(self, containsPoint, &point);
1233 }
1234 break;
1235 }
1236
1237 return false;
1238}
1239
1243static void moveToWindow_enumerate(View *subview, ident data) {
1244 $(subview, moveToWindow, data);
1245}
1246
1251static void moveToWindow(View *self, SDL_Window *window) {
1252
1253 $(self, willMoveToWindow, window);
1254
1255 self->window = window;
1256
1257 $(self, didMoveToWindow, window);
1258
1259 $(self, enumerateSubviews, moveToWindow_enumerate, window);
1260}
1261
1266static String *path(const View *self) {
1267
1268 Array *parts = $$(Array, array);
1269
1270 const View *view = self;
1271 while (view) {
1272
1273 Array *classNames = $((Set *) view->classNames, allObjects);
1274
1275 String *part;
1276 if (view->identifier) {
1277 part = str("#%s", view->identifier);
1278 } else if (classNames->count) {
1279 part = str(".%s", ((String *) $(classNames, firstObject))->chars);
1280 } else {
1281 part = str("%s", classnameof(view));
1282 }
1283
1284 release(classNames);
1285
1286 $(parts, insertObjectAtIndex, part, 0);
1287 release(part);
1288
1289 if (view->viewController) {
1290 break;
1291 }
1292
1293 view = view->superview;
1294 }
1295
1296 String *path = $((Array *) parts, componentsJoinedByCharacters, " > ");
1297
1298 release(parts);
1299
1300 return path;
1301}
1302
1307static void removeAllClassNames(View *self) {
1308
1309 $(self->classNames, removeAllObjects);
1310
1311 $(self, invalidateStyle);
1312}
1313
1317static void removeAllSubviews_enumerate(const Array *array, ident obj, ident data) {
1318 $((View *) data, removeSubview, obj);
1319}
1320
1325static void removeAllSubviews(View *self) {
1326 $(self->subviews, removeAllObjectsWithEnumerator, removeAllSubviews_enumerate, self);
1327}
1328
1333static void removeClassName(View *self, const char *className) {
1334
1335 if (className) {
1336 String *string = $$(String, stringWithCharacters, className);
1337 assert(string);
1338
1339 $(self->classNames, removeObject, string);
1340 release(string);
1341
1342 $(self, invalidateStyle);
1343 }
1344}
1345
1350static void removeFromSuperview(View *self) {
1351
1352 if (self->superview) {
1353 $(self->superview, removeSubview, self);
1354 }
1355}
1356
1361static void removeSubview(View *self, View *subview) {
1362
1363 assert(subview);
1364
1365 if (subview->superview == self) {
1366
1367 subview->superview = NULL;
1368
1369 $(subview, moveToWindow, NULL);
1370
1371 $(self->subviews, removeObject, subview);
1372
1373 self->needsLayout = true;
1374 }
1375}
1376
1381static void render(View *self, Renderer *renderer) {
1382
1383 assert(self->window);
1384
1385 if ($(self, hasClassName, "breakpoint")) {
1386 SDL_TriggerBreakpoint();
1387 }
1388
1389 if (self->backgroundColor.a) {
1390
1391 const SDL_Rect frame = $(self, renderFrame);
1392 $(renderer, drawRectFilled, &frame, &self->backgroundColor);
1393 }
1394
1395 if (self->borderWidth && self->borderColor.a) {
1396
1397 SDL_Rect frame = $(self, renderFrame);
1398 for (int i = 0; i < self->borderWidth; i++) {
1399
1400 $(renderer, drawRect, &frame, &self->borderColor);
1401
1402 frame.x -= 1;
1403 frame.y -= 1;
1404 frame.w += 2;
1405 frame.h += 2;
1406 }
1407 }
1408}
1409
1413static void renderDeviceDidReset_enumerate(View *subview, ident data) {
1414 $(subview, renderDeviceDidReset);
1415}
1416
1421static void renderDeviceDidReset(View *self) {
1423}
1424
1428static void renderDeviceWillReset_enumerate(View *subview, ident data) {
1429 $(subview, renderDeviceWillReset);
1430}
1431
1436static void renderDeviceWillReset(View *self) {
1438}
1439
1444static SDL_Rect renderFrame(const View *self) {
1445
1446 SDL_Rect frame = self->frame;
1447
1448 const View *view = self;
1449 const View *superview = view->superview;
1450 while (superview) {
1451
1452 frame.x += superview->frame.x;
1453 frame.y += superview->frame.y;
1454
1455 if (view->alignment != ViewAlignmentInternal) {
1456 frame.x += superview->padding.left;
1457 frame.y += superview->padding.top;
1458 }
1459
1460 view = superview;
1461 superview = view->superview;
1462 }
1463
1464 return frame;
1465}
1466
1471static void replaceSubview(View *self, View *subview, View *replacement) {
1472
1473 assert(subview);
1474 assert(replacement);
1475
1476 $(self, addSubviewRelativeTo, replacement, subview, ViewPositionAfter);
1477 $(self, removeSubview, subview);
1478}
1479
1484static void resignKeyResponder(View *self) {
1485
1486 if (self->window && $(self, isKeyResponder)) {
1487
1488 String *path = $(self, path);
1489 MVC_LogDebug("%s\n", path->chars);
1490 release(path);
1491
1492 SDL_SetPointerProperty(SDL_GetWindowProperties(self->window), "keyResponder", NULL);
1493 }
1494}
1495
1500static void resignTouchResponder(View *self) {
1501
1502 if (self->window && $(self, isTouchResponder)) {
1503
1504 String *path = $(self, path);
1505 MVC_LogDebug("%s\n", path->chars);
1506 release(path);
1507
1508 SDL_SetPointerProperty(SDL_GetWindowProperties(self->window), "touchResponder", NULL);
1509 }
1510}
1511
1516static void resize(View *self, const SDL_Size *size) {
1517
1518 const int w = clamp(size->w, self->minSize.w, self->maxSize.w);
1519 const int h = clamp(size->h, self->minSize.h, self->maxSize.h);
1520
1521 if (self->frame.w != w || self->frame.h != h) {
1522
1523 self->frame.w = w;
1524 self->frame.h = h;
1525
1526 self->needsLayout = true;
1527
1528 if (self->superview && $(self->superview, isContainer)) {
1529 self->superview->needsLayout = true;
1530 }
1531 }
1532}
1533
1538static void resolve(View *self, Outlet *outlets) {
1539
1540 if (outlets) {
1541 for (Outlet *outlet = outlets; outlet->identifier; outlet++) {
1542 *outlet->view = $(self, descendantWithIdentifier, outlet->identifier);
1543 if (*outlet->view == NULL) {
1544 String *desc = $((Object *) self, description);
1545 MVC_LogError("Failed to resolve outlet '%s' for %s", outlet->identifier, desc->chars);
1546 release(desc);
1547 }
1548
1549 assert(*outlet->view);
1550 }
1551 }
1552}
1553
1558static void respondToEvent(View *self, const SDL_Event *event) {
1559
1560 assert(event);
1561
1562 ViewEvent code = ViewEventNone;
1563
1564 switch (event->type) {
1565 case SDL_EVENT_MOUSE_BUTTON_DOWN:
1567 break;
1568 case SDL_EVENT_MOUSE_BUTTON_UP:
1570 $(self, resignTouchResponder);
1571 break;
1572 case SDL_EVENT_KEY_DOWN:
1573 code = ViewEventKeyDown;
1574 break;
1575 case SDL_EVENT_KEY_UP:
1576 code = ViewEventKeyUp;
1577 break;
1578 default:
1579 break;
1580 }
1581
1582 if (code != ViewEventNone) {
1583 $(self, emitViewEvent, code, NULL);
1584
1585 if (code == ViewEventMouseButtonUp) {
1586 if (event->button.clicks) {
1587 $(self, emitViewEvent, ViewEventClick, NULL);
1588 }
1589 }
1590 }
1591
1592 if (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
1593 if ($(self, acceptsKeyResponder)) {
1594 $(self, becomeKeyResponder);
1595 return;
1596 }
1597 }
1598
1599 if (self->nextResponder) {
1600 $(self->nextResponder, respondToEvent, event);
1601 } else if (self->superview) {
1602 $(self->superview, respondToEvent, event);
1603 }
1604}
1605
1610static Set *_select(View *self, const char *rule) {
1611
1612 Selector *selector = $(alloc(Selector), initWithRule, rule);
1613 assert(selector);
1614
1615 Set *selection = $(selector, select, self);
1616
1617 release(selector);
1618 return selection;
1619}
1620
1625static View *selectFirst(View *self, const char *rule) {
1626
1627 Selector *selector = $(alloc(Selector), initWithRule, rule);
1628 assert(selector);
1629
1630 View *first = $(selector, selectFirst, self);
1631
1632 release(selector);
1633 return first;
1634}
1635
1640static SDL_Size size(const View *self) {
1641 return MakeSize(self->frame.w, self->frame.h);
1642}
1643
1648static SDL_Size sizeThatContains(const View *self) {
1649
1650 const SDL_Size size = $(self, size);
1651 const SDL_Size sizeThatFits = $(self, sizeThatFits);
1652
1653 return MakeSize(max(size.w, sizeThatFits.w), max(size.h, sizeThatFits.h));
1654}
1655
1660static SDL_Size sizeThatFills(const View *self) {
1661 SDL_Size size;
1662
1663 if (self->superview == NULL) {
1664 SDL_GetWindowSize(self->window, &size.w, &size.h);
1665 } else {
1666 size = $(self->superview, size);
1667 }
1668
1669 return size;
1670}
1671
1676static SDL_Size sizeThatFits(const View *self) {
1677
1678 SDL_Size size = $(self, size);
1679
1681 size.w = 0;
1682 }
1683
1685 size.h = 0;
1686 }
1687
1688 if ($(self, isContainer)) {
1689 size = MakeSize(0, 0);
1690
1691 Array *subviews = $(self, visibleSubviews);
1692 for (size_t i = 0; i < subviews->count; i++) {
1693
1694 const View *subview = subviews->elements[i];
1695
1696 SDL_Size subviewSize;
1698 subviewSize = $(subview, sizeThatContains);
1699 } else if (subview->autoresizingMask & ViewAutoresizingFit) {
1700 subviewSize = $(subview, sizeThatFits);
1701 } else {
1702 subviewSize = $(subview, size);
1703 }
1704
1705 SDL_Point subviewOrigin = MakePoint(0, 0);
1706 switch (subview->alignment) {
1707 case ViewAlignmentNone:
1708 subviewOrigin = MakePoint(subview->frame.x, subview->frame.y);
1709 break;
1710 default:
1711 break;
1712 }
1713
1714 size.w = max(size.w, subviewOrigin.x + subviewSize.w);
1715 size.h = max(size.h, subviewOrigin.y + subviewSize.h);
1716 }
1717
1718 size.w += self->padding.left + self->padding.right;
1719 size.h += self->padding.top + self->padding.bottom;
1720
1721 release(subviews);
1722 }
1723
1724 size.w = clamp(size.w, self->minSize.w, self->maxSize.w);
1725 size.h = clamp(size.h, self->minSize.h, self->maxSize.h);
1726
1727 return size;
1728}
1729
1734static void sizeToContain(View *self) {
1735
1736 const SDL_Size size = $(self, sizeThatContains);
1737
1738 $(self, resize, &size);
1739}
1740
1745static void sizeToFill(View *self) {
1746
1747 const SDL_Size size = $(self, sizeThatFills);
1748
1749 $(self, resize, &size);
1750}
1751
1756static void sizeToFit(View *self) {
1757
1758 const SDL_Size size = $(self, sizeThatFits);
1759
1760 $(self, resize, &size);
1761}
1762
1767static View *subviewWithIdentifier(const View *self, const char *identifier) {
1768
1769 assert(identifier);
1770
1771 const Array *subviews = (Array *) self->subviews;
1772 for (size_t i = 0; i < subviews->count; i++) {
1773
1774 View *subview = subviews->elements[i];
1775 if (subview->identifier) {
1776
1777 if (strcmp(identifier, subview->identifier) == 0) {
1778 return subview;
1779 }
1780 }
1781 }
1782
1783 return NULL;
1784}
1785
1789static void updateBindings_enumerate(View *subview, ident data) {
1790 $(subview, updateBindings);
1791}
1792
1797static void updateBindings(View *self) {
1799}
1800
1805static SDL_Rect viewport(const View *self) {
1806
1807 assert(self->window);
1808
1809 const SDL_Rect frame = $(self, renderFrame);
1810
1811 return MVC_TransformToWindow(self->window, &frame);
1812}
1813
1818static View *viewWithCharacters(const char *chars, Outlet *outlets) {
1819
1820 Data *data = $$(Data, dataWithConstMemory, (ident) chars, strlen(chars));
1821
1822 View *view = $$(View, viewWithData, data, outlets);
1823
1824 release(data);
1825
1826 return view;
1827}
1828
1833static View *viewWithData(const Data *data, Outlet *outlets) {
1834
1835 JSONContext *ctx = $(alloc(JSONContext), init);
1836 Dictionary *dictionary = $(ctx, objectFromData, data, 0);
1837
1838 View *view = NULL;
1839 if (dictionary) {
1840 view = $$(View, viewWithDictionary, dictionary, outlets);
1841 release(dictionary);
1842 } else {
1843 MVC_LogError("Failed to parse JSON\n");
1844 }
1845
1846 release(ctx);
1847 return view;
1848}
1849
1854static View *viewWithDictionary(const Dictionary *dictionary, Outlet *outlets) {
1855
1856 View *view = NULL;
1857
1858 BindInlet(&MakeInlet(NULL, InletTypeView, &view, NULL), dictionary);
1859
1860 $(view, resolve, outlets);
1861
1862 return view;
1863}
1864
1869static View *viewWithResource(const Resource *resource, Outlet *outlets) {
1870
1871 assert(resource);
1872
1873 return $$(View, viewWithData, resource->data, outlets);
1874}
1875
1880static View *viewWithResourceName(const char *name, Outlet *outlets) {
1881
1882 Resource *resource = $$(Resource, resourceWithName, name);
1883
1884 View *view = $$(View, viewWithResource, resource, outlets);
1885
1886 release(resource);
1887
1888 return view;
1889}
1890
1894static bool visibleSubviews_filter(ident obj, ident data) {
1895
1896 const View *view = (View *) obj;
1897
1898 return view->hidden == false && view->alignment != ViewAlignmentInternal;
1899}
1900
1905static Array *visibleSubviews(const View *self) {
1906 return $((Array *) self->subviews, filteredArray, visibleSubviews_filter, NULL);
1907}
1908
1913static void warn(View *self, WarningType type, const char *fmt, ...) {
1914
1915 va_list args;
1916 va_start(args, fmt);
1917
1918 Warning *warning = $(alloc(Warning), initWithVaList, type, fmt, args);
1919
1920 va_end(args);
1921
1922 String *description = $((Object *) self, description);
1923 MVC_LogWarn("%s:: %s\n", description->chars, warning->message->chars);
1924 release(description);
1925
1926 $(self->warnings, addObject, warning);
1927
1928 release(warning);
1929}
1930
1935static void willMoveToWindow(View *self, SDL_Window *window) {
1936
1937 if (self->window) {
1938 $(self, resignKeyResponder);
1939 $(self, resignTouchResponder);
1940 $(self, detachStylesheet, self->window);
1941 }
1942}
1943
1944#pragma mark - View class methods
1945
1949static void initialize(Class *clazz) {
1950
1951 ((ObjectInterface *) clazz->interface)->dealloc = dealloc;
1952 ((ObjectInterface *) clazz->interface)->description = description;
1953
1954 ((ViewInterface *) clazz->interface)->acceptsKeyResponder = acceptsKeyResponder;
1955 ((ViewInterface *) clazz->interface)->acceptsTouchResponder = acceptsTouchResponder;
1956 ((ViewInterface *) clazz->interface)->addClassName = addClassName;
1957 ((ViewInterface *) clazz->interface)->addSubview = addSubview;
1958 ((ViewInterface *) clazz->interface)->addSubviewRelativeTo = addSubviewRelativeTo;
1959 ((ViewInterface *) clazz->interface)->ancestorWithIdentifier = ancestorWithIdentifier;
1960 ((ViewInterface *) clazz->interface)->applyStyle = applyStyle;
1961 ((ViewInterface *) clazz->interface)->applyTheme = applyTheme;
1962 ((ViewInterface *) clazz->interface)->applyThemeIfNeeded = applyThemeIfNeeded;
1963 ((ViewInterface *) clazz->interface)->attachStylesheet = attachStylesheet;
1964 ((ViewInterface *) clazz->interface)->awakeWithCharacters = awakeWithCharacters;
1965 ((ViewInterface *) clazz->interface)->awakeWithData = awakeWithData;
1966 ((ViewInterface *) clazz->interface)->awakeWithDictionary = awakeWithDictionary;
1967 ((ViewInterface *) clazz->interface)->awakeWithResource = awakeWithResource;
1968 ((ViewInterface *) clazz->interface)->awakeWithResourceName = awakeWithResourceName;
1969 ((ViewInterface *) clazz->interface)->becomeKeyResponder = becomeKeyResponder;
1970 ((ViewInterface *) clazz->interface)->becomeTouchResponder = becomeTouchResponder;
1971 ((ViewInterface *) clazz->interface)->bind = _bind;
1972 ((ViewInterface *) clazz->interface)->bounds = bounds;
1973 ((ViewInterface *) clazz->interface)->bringSubviewToFront = bringSubviewToFront;
1974 ((ViewInterface *) clazz->interface)->clearWarnings = clearWarnings;
1975 ((ViewInterface *) clazz->interface)->clippingFrame = clippingFrame;
1976 ((ViewInterface *) clazz->interface)->containsPoint = containsPoint;
1977 ((ViewInterface *) clazz->interface)->depth = depth;
1978 ((ViewInterface *) clazz->interface)->descendantWithIdentifier = descendantWithIdentifier;
1979 ((ViewInterface *) clazz->interface)->detachStylesheet = detachStylesheet;
1980 ((ViewInterface *) clazz->interface)->didMoveToWindow = didMoveToWindow;
1981 ((ViewInterface *) clazz->interface)->didReceiveEvent = didReceiveEvent;
1982 ((ViewInterface *) clazz->interface)->draw = draw;
1983 ((ViewInterface *) clazz->interface)->emitViewEvent = emitViewEvent;
1984 ((ViewInterface *) clazz->interface)->enumerate = enumerate;
1985 ((ViewInterface *) clazz->interface)->enumerateAdjacent = enumerateAdjacent;
1986 ((ViewInterface *) clazz->interface)->enumerateAncestors = enumerateAncestors;
1987 ((ViewInterface *) clazz->interface)->enumerateDescendants = enumerateDescendants;
1988 ((ViewInterface *) clazz->interface)->enumerateSelection = enumerateSelection;
1989 ((ViewInterface *) clazz->interface)->enumerateSiblings = enumerateSiblings;
1990 ((ViewInterface *) clazz->interface)->enumerateSubviews = enumerateSubviews;
1991 ((ViewInterface *) clazz->interface)->enumerateSuperview = enumerateSuperview;
1992 ((ViewInterface *) clazz->interface)->enumerateVisible = enumerateVisible;
1993 ((ViewInterface *) clazz->interface)->hasClassName = hasClassName;
1994 ((ViewInterface *) clazz->interface)->hasOverflow = hasOverflow;
1995 ((ViewInterface *) clazz->interface)->hitTest = hitTest;
1996 ((ViewInterface *) clazz->interface)->init = init;
1997 ((ViewInterface *) clazz->interface)->initWithFrame = initWithFrame;
1998 ((ViewInterface *) clazz->interface)->invalidateStyle = invalidateStyle;
1999 ((ViewInterface *) clazz->interface)->isContainer = isContainer;
2000 ((ViewInterface *) clazz->interface)->isDescendantOfView = isDescendantOfView;
2001 ((ViewInterface *) clazz->interface)->isKeyResponder = isKeyResponder;
2002 ((ViewInterface *) clazz->interface)->isTouchResponder = isTouchResponder;
2003 ((ViewInterface *) clazz->interface)->isVisible = isVisible;
2004 ((ViewInterface *) clazz->interface)->layoutIfNeeded = layoutIfNeeded;
2005 ((ViewInterface *) clazz->interface)->layoutSubviews = layoutSubviews;
2006 ((ViewInterface *) clazz->interface)->matchesSelector = matchesSelector;
2007 ((ViewInterface *) clazz->interface)->moveToWindow = moveToWindow;
2008 ((ViewInterface *) clazz->interface)->path = path;
2009 ((ViewInterface *) clazz->interface)->removeAllClassNames = removeAllClassNames;
2010 ((ViewInterface *) clazz->interface)->removeAllSubviews = removeAllSubviews;
2011 ((ViewInterface *) clazz->interface)->removeClassName = removeClassName;
2012 ((ViewInterface *) clazz->interface)->removeFromSuperview = removeFromSuperview;
2013 ((ViewInterface *) clazz->interface)->removeSubview = removeSubview;
2014 ((ViewInterface *) clazz->interface)->render = render;
2015 ((ViewInterface *) clazz->interface)->renderDeviceDidReset = renderDeviceDidReset;
2016 ((ViewInterface *) clazz->interface)->renderDeviceWillReset = renderDeviceWillReset;
2017 ((ViewInterface *) clazz->interface)->renderFrame = renderFrame;
2018 ((ViewInterface *) clazz->interface)->replaceSubview = replaceSubview;
2019 ((ViewInterface *) clazz->interface)->resignKeyResponder = resignKeyResponder;
2020 ((ViewInterface *) clazz->interface)->resignTouchResponder = resignTouchResponder;
2021 ((ViewInterface *) clazz->interface)->resize = resize;
2022 ((ViewInterface *) clazz->interface)->resolve = resolve;
2023 ((ViewInterface *) clazz->interface)->respondToEvent = respondToEvent;
2024 ((ViewInterface *) clazz->interface)->select = _select;
2025 ((ViewInterface *) clazz->interface)->selectFirst = selectFirst;
2026 ((ViewInterface *) clazz->interface)->size = size;
2027 ((ViewInterface *) clazz->interface)->sizeThatContains = sizeThatContains;
2028 ((ViewInterface *) clazz->interface)->sizeThatFills = sizeThatFills;
2029 ((ViewInterface *) clazz->interface)->sizeThatFits = sizeThatFits;
2030 ((ViewInterface *) clazz->interface)->sizeToContain = sizeToContain;
2031 ((ViewInterface *) clazz->interface)->sizeToFill = sizeToFill;
2032 ((ViewInterface *) clazz->interface)->sizeToFit = sizeToFit;
2033 ((ViewInterface *) clazz->interface)->subviewWithIdentifier = subviewWithIdentifier;
2034 ((ViewInterface *) clazz->interface)->updateBindings = updateBindings;
2035 ((ViewInterface *) clazz->interface)->viewport = viewport;
2036 ((ViewInterface *) clazz->interface)->viewWithCharacters = viewWithCharacters;
2037 ((ViewInterface *) clazz->interface)->viewWithData = viewWithData;
2038 ((ViewInterface *) clazz->interface)->viewWithDictionary = viewWithDictionary;
2039 ((ViewInterface *) clazz->interface)->viewWithResource = viewWithResource;
2040 ((ViewInterface *) clazz->interface)->viewWithResourceName = viewWithResourceName;
2041 ((ViewInterface *) clazz->interface)->visibleSubviews = visibleSubviews;
2042 ((ViewInterface *) clazz->interface)->warn = warn;
2043 ((ViewInterface *) clazz->interface)->willMoveToWindow = willMoveToWindow;
2044
2045 MVC_NOTIFICATION_EVENT = SDL_RegisterEvents(1);
2046 assert(MVC_NOTIFICATION_EVENT != 0);
2047
2048 MVC_VIEW_EVENT = SDL_RegisterEvents(1);
2049 assert(MVC_VIEW_EVENT != 0);
2050}
2051
2056Class *_View(void) {
2057 static Class *clazz;
2058 static Once once;
2059
2060 do_once(&once, {
2061 clazz = _initialize(&(const ClassDef) {
2062 .name = "View",
2063 .superclass = _Object(),
2064 .instanceSize = sizeof(View),
2065 .interfaceOffset = offsetof(View, interface),
2066 .interfaceSize = sizeof(ViewInterface),
2068 });
2069 });
2070
2071 return clazz;
2072}
2073
2074#undef _Class
View logging facilities via SDL_Log.
#define MVC_LogWarn(fmt,...)
Definition Log.h:54
#define MVC_LogDebug(fmt,...)
Definition Log.h:48
#define MVC_LogError(fmt,...)
Definition Log.h:57
static void drawView(Renderer *self, View *view)
Definition Renderer.c:246
static void drawRectFilled(const Renderer *self, const SDL_Rect *rect, const SDL_Color *color)
Definition Renderer.c:200
static void drawRect(const Renderer *self, const SDL_Rect *rect, const SDL_Color *color)
Definition Renderer.c:181
static Selector * initWithRule(Selector *self, const char *rule)
Definition Selector.c:190
@ SimpleSelectorTypePseudo
@ SimpleSelectorTypeId
@ SimpleSelectorTypeClass
@ SimpleSelectorTypeUniversal
@ SimpleSelectorTypeType
@ SimpleSelectorTypeNone
static bool isComputedEqual(const Style *self, const Style *other)
Definition Style.c:331
static Style * initWithAttributes(Style *self, const Dictionary *attributes)
Definition Style.c:292
static void addAttributes(Style *self, const Dictionary *attributes)
Definition Style.c:119
static Theme * theme(SDL_Window *window)
Definition Theme.c:143
static void addStylesheet(Theme *self, Stylesheet *stylesheet)
Definition Theme.c:55
static Style * computeStyle(const Theme *self, const View *view)
Definition Theme.c:103
static void removeStylesheet(Theme *self, Stylesheet *stylesheet)
Definition Theme.c:135
ViewEvent
View event relay codes.
Definition Types.h:61
@ ViewEventMouseButtonUp
A View has received a mouse button up event.
Definition Types.h:85
@ ViewEventMouseButtonDown
A View has received a mouse button down event.
Definition Types.h:80
@ ViewEventNone
No event.
Definition Types.h:65
@ ViewEventKeyUp
A View has received a key up event.
Definition Types.h:75
@ ViewEventClick
A Control received one or more click events.
Definition Types.h:115
@ ViewEventKeyDown
A View has received a key down event.
Definition Types.h:70
void(* ViewEnumerator)(View *view, ident data)
A function type for View enumeration.
Definition Types.h:56
bool bindInlets(const Inlet *inlets, const Dictionary *dictionary)
Binds each Inlet specified in inlets to the data provided in dictionary.
Definition View+JSON.c:327
@ InletTypeSubviews
Definition View+JSON.h:122
@ InletTypeStyle
Definition View+JSON.h:116
@ InletTypeEnum
Definition View+JSON.h:75
@ InletTypeCharacters
Definition View+JSON.h:52
@ InletTypeBool
Definition View+JSON.h:46
@ InletTypeClassNames
Definition View+JSON.h:57
@ InletTypeInteger
Definition View+JSON.h:95
@ InletTypeColor
Definition View+JSON.h:62
@ InletTypeView
Definition View+JSON.h:139
@ InletTypeSize
Definition View+JSON.h:110
@ InletTypeRectangle
Definition View+JSON.h:105
#define BindInlet(inlet, obj)
Binds the Inlet to obj by invoking the appropriate InletBinding function.
Definition View+JSON.h:244
#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
const EnumName ViewAlignmentNames[]
Definition View.c:39
static SDL_Size size(const View *self)
Definition View.c:1640
static View * viewWithResource(const Resource *resource, Outlet *outlets)
Definition View.c:1869
static void removeClassName(View *self, const char *className)
Definition View.c:1333
static void addClassName(View *self, const char *className)
Definition View.c:159
static View * selectFirst(View *self, const char *rule)
Definition View.c:1625
static bool isContainer(const View *self)
Definition View.c:1030
static void addSubviewRelativeTo(View *self, View *subview, View *other, ViewPosition position)
Definition View.c:185
static void didMoveToWindow(View *self, SDL_Window *window)
Definition View.c:640
static bool isDescendantOfView(const View *self, const View *view)
Definition View.c:1038
static bool filterViewEvents(void *data, SDL_Event *event)
Definition View.c:75
static void becomeKeyResponder(View *self)
Definition View.c:427
static String * path(const View *self)
Definition View.c:1266
static void resize(View *self, const SDL_Size *size)
Definition View.c:1516
static void detachStylesheet(View *self, SDL_Window *window)
Definition View.c:624
static bool isTouchResponder(const View *self)
Definition View.c:1069
static void layoutIfNeeded(View *self)
Definition View.c:1104
static void sizeToContain(View *self)
Definition View.c:1734
static bool acceptsKeyResponder(const View *self)
Definition View.c:143
Class * _View(void)
Definition View.c:2056
static View * init(View *self)
Definition View.c:970
static Set * _select(View *self, const char *rule)
Definition View.c:1610
static void enumerateAncestors(const View *self, ViewEnumerator enumerator, ident data)
Definition View.c:771
static void clearWarnings(const View *self, WarningType type)
Definition View.c:538
static void awakeWithDictionary(View *self, const Dictionary *dictionary)
Definition View.c:385
static int depth(const View *self)
Definition View.c:589
static void enumerateVisible(View *self, ViewEnumerator enumerator, ident data)
Definition View.c:862
static void draw(View *self, Renderer *renderer)
Definition View.c:709
static SDL_Size sizeThatFills(const View *self)
Definition View.c:1660
static void removeAllClassNames(View *self)
Definition View.c:1307
static void removeSubview(View *self, View *subview)
Definition View.c:1361
static void nullifySuperview(View *subview, ident data)
ViewEnumerator to null out superview pointers during parent dealloc.
Definition View.c:87
static void updateBindings_enumerate(View *subview, ident data)
ViewEnumerator for updateBindings recursion.
Definition View.c:1789
static void enumerateSubviews(const View *self, ViewEnumerator enumerator, ident data)
Definition View.c:835
static bool visibleSubviews_filter(ident obj, ident data)
Predicate for visibleSubviews.
Definition View.c:1894
static View * viewWithResourceName(const char *name, Outlet *outlets)
Definition View.c:1880
static void becomeTouchResponder(View *self)
Definition View.c:451
static void removeAllSubviews_enumerate(const Array *array, ident obj, ident data)
ArrayEnumerator for removeAllSubviews.
Definition View.c:1317
static void enumerateSelection(View *self, const char *rule, ViewEnumerator enumerator, ident data)
Definition View.c:784
static void respondToEvent(View *self, const SDL_Event *event)
Definition View.c:1558
static void applyStyle(View *self, const Style *style)
Definition View.c:248
static bool isVisible(const View *self)
Definition View.c:1082
static void resignTouchResponder(View *self)
Definition View.c:1500
static void replaceSubview(View *self, View *subview, View *replacement)
Definition View.c:1471
static bool matchesSelector(const View *self, const SimpleSelector *simpleSelector)
Definition View.c:1180
static SDL_Size sizeThatFits(const View *self)
Definition View.c:1676
static void bringSubviewToFront(View *self, View *subview)
Definition View.c:510
static bool didReceiveEvent(const View *self, const SDL_Event *event)
Definition View.c:657
static View * subviewWithIdentifier(const View *self, const char *identifier)
Definition View.c:1767
static void _draw(View *view, ident data)
ViewEnumerator adapter for draw.
Definition View.c:705
Uint32 MVC_VIEW_EVENT
Definition View.c:37
static String * description(const Object *self)
Definition View.c:121
static bool hasOverflow(const View *self)
Definition View.c:928
static Array * visibleSubviews(const View *self)
Definition View.c:1905
static void invalidateStyle(View *self)
Definition View.c:1022
static void sizeToFill(View *self)
Definition View.c:1745
static void warn(View *self, WarningType type, const char *fmt,...)
Definition View.c:1913
static void awakeWithCharacters(View *self, const char *chars)
Definition View.c:353
static View * descendantWithIdentifier(const View *self, const char *identifier)
Definition View.c:597
static void moveToWindow(View *self, SDL_Window *window)
Definition View.c:1251
static View * initWithFrame(View *self, const SDL_Rect *frame)
Definition View.c:978
static void removeFromSuperview(View *self)
Definition View.c:1350
static bool hasClassName_predicate(const ident obj, ident data)
Predicate for hasClassName.
Definition View.c:881
static void removeAllSubviews(View *self)
Definition View.c:1325
static SDL_Size sizeThatContains(const View *self)
Definition View.c:1648
static void applyTheme(View *self, const Theme *theme)
Definition View.c:285
static void render(View *self, Renderer *renderer)
Definition View.c:1381
static void moveToWindow_enumerate(View *subview, ident data)
ViewEnumerator for moveToWindow recursion.
Definition View.c:1243
static void awakeWithResourceName(View *self, const char *name)
Definition View.c:414
static void dealloc(Object *self)
Definition View.c:94
static void invalidateStyle_enumerate(View *view, ident data)
ViewEnumerator for invalidateStyle.
Definition View.c:1014
static void addSubview(View *self, View *subview)
Definition View.c:177
static SDL_Rect renderFrame(const View *self)
Definition View.c:1444
static void renderDeviceWillReset_enumerate(View *subview, ident data)
ViewEnumerator for renderDeviceWillReset recursion.
Definition View.c:1428
static void resignKeyResponder(View *self)
Definition View.c:1484
static View * viewWithData(const Data *data, Outlet *outlets)
Definition View.c:1833
static View * viewWithCharacters(const char *chars, Outlet *outlets)
Definition View.c:1818
static void initialize(Class *clazz)
Definition View.c:1949
const EnumName ViewAutoresizingNames[]
Definition View.c:59
static bool acceptsTouchResponder(const View *self)
Definition View.c:151
static void renderDeviceWillReset(View *self)
Definition View.c:1436
static bool _bind(View *self, const Inlet *inlets, const Dictionary *dictionary)
Definition View.c:475
static bool clearWarnings_predicate(const ident obj, ident data)
Filter Predicate for clearWarnings.
Definition View.c:526
static void _applyThemeIfNeeded(View *view, ident data)
ViewEnumerator adapter for applyThemeIfNeeded.
Definition View.c:313
static void updateBindings(View *self)
Definition View.c:1797
static void willMoveToWindow(View *self, SDL_Window *window)
Definition View.c:1935
Uint32 MVC_NOTIFICATION_EVENT
Definition View.c:36
static void attachStylesheet(View *self, SDL_Window *window)
Definition View.c:337
static bool isKeyResponder(const View *self)
Definition View.c:1056
static void renderDeviceDidReset(View *self)
Definition View.c:1421
static void awakeWithResource(View *self, const Resource *resource)
Definition View.c:403
static void hasOverflow_enumerate(View *view, ident data)
ViewEnumerator for hasOverflow.
Definition View.c:906
static void enumerateSuperview(const View *self, ViewEnumerator enumerator, ident data)
Definition View.c:849
static void renderDeviceDidReset_enumerate(View *subview, ident data)
ViewEnumerator for renderDeviceDidReset recursion.
Definition View.c:1413
static void layoutSubviews(View *self)
Definition View.c:1122
static void enumerate(View *self, ViewEnumerator enumerator, ident data)
Definition View.c:738
static void enumerateAdjacent(const View *self, ViewEnumerator enumerator, ident data)
Definition View.c:751
static SDL_Rect viewport(const View *self)
Definition View.c:1805
static void layoutIfNeeded_enumerate(View *subview, ident data)
ViewEnumerator for layoutIfNeeded recursion.
Definition View.c:1096
static View * hitTest(const View *self, const SDL_Point *point)
Definition View.c:943
static bool hasClassName(const View *self, const char *className)
Definition View.c:889
static void applyThemeIfNeeded(View *self, const Theme *theme)
Definition View.c:317
static bool containsPoint(const View *self, const SDL_Point *point)
Definition View.c:578
static void sizeToFit(View *self)
Definition View.c:1756
static SDL_Rect clippingFrame(const View *self)
Definition View.c:546
static void enumerateSiblings(const View *self, ViewEnumerator enumerator, ident data)
Definition View.c:815
static View * viewWithDictionary(const Dictionary *dictionary, Outlet *outlets)
Definition View.c:1854
static void awakeWithData(View *self, const Data *data)
Definition View.c:366
static void enumerateDescendants(const View *self, ViewEnumerator enumerator, ident data)
Definition View.c:797
static SDL_Rect bounds(const View *self)
Definition View.c:492
static View * ancestorWithIdentifier(const View *self, const char *identifier)
Definition View.c:227
static void resolve(View *self, Outlet *outlets)
Definition View.c:1538
static void emitViewEvent(View *self, ViewEvent code, ident data)
Definition View.c:725
Views are the fundamental building blocks of ObjectivelyMVC user interfaces.
#define ViewAlignmentMaskBottom
Definition View.h:46
#define ViewAlignmentMaskTop
Definition View.h:44
#define ViewAlignmentMaskMiddle
Definition View.h:45
#define ViewAlignmentMaskHorizontal
Definition View.h:55
ViewPosition
Relative positioning of subviews within their superview.
Definition View.h:119
@ ViewPositionAfter
Definition View.h:121
#define ViewAlignmentMaskVertical
Definition View.h:52
@ ViewAutoresizingNone
Definition View.h:87
@ ViewAutoresizingFill
Definition View.h:90
@ ViewAutoresizingContain
Definition View.h:92
@ ViewAutoresizingWidth
Definition View.h:88
@ ViewAutoresizingHeight
Definition View.h:89
@ ViewAutoresizingFit
Definition View.h:91
@ ViewAlignmentBottomRight
Definition View.h:77
@ ViewAlignmentBottomLeft
Definition View.h:75
@ ViewAlignmentMiddleCenter
Definition View.h:73
@ ViewAlignmentInternal
Definition View.h:78
@ ViewAlignmentTopCenter
Definition View.h:70
@ ViewAlignmentTopRight
Definition View.h:71
@ ViewAlignmentMiddleRight
Definition View.h:74
@ ViewAlignmentTopLeft
Definition View.h:69
@ ViewAlignmentCenter
Definition View.h:67
@ ViewAlignmentLeft
Definition View.h:66
@ ViewAlignmentRight
Definition View.h:68
@ ViewAlignmentTop
Definition View.h:63
@ ViewAlignmentMiddleLeft
Definition View.h:72
@ ViewAlignmentBottom
Definition View.h:65
@ ViewAlignmentMiddle
Definition View.h:64
@ ViewAlignmentBottomCenter
Definition View.h:76
@ ViewAlignmentNone
Definition View.h:62
A ViewController manages a View and its descendants.
static Warning * initWithVaList(Warning *self, WarningType type, const char *fmt, va_list args)
Definition Warning.c:66
WarningType
Warning types.
Definition Warning.h:33
@ WarningTypeStyle
Definition Warning.h:34
@ WarningTypeLayout
Definition Warning.h:35
SDL_Rect MVC_TransformToWindow(SDL_Window *window, const SDL_Rect *rect)
Transforms the specified rectangle to normalized device coordinates in window.
Definition Window.c:29
static View * keyResponder(const WindowController *self)
static View * touchResponder(const WindowController *self)
A WindowController manages a ViewController and its descendants within an SDL_Window.
Inlets enable inbound data binding of View attributes through JSON.
Definition View+JSON.h:155
Outlets enable outbound data binding of Views through JSON.
Definition View+JSON.h:200
const char * identifier
The View identifier.
Definition View+JSON.h:205
SDL_Rect bounds
Definition View.c:899
bool hasOverflow
Definition View.c:900
Renderer extends Object with ObjectivelyMVC's UI rendering layer.
Definition Renderer.h:70
Selectors are comprised of one or more SelectorSequences.
Definition Selector.h:49
The SimpleSelector type.
SimpleSelectorType type
The SimpleSelectorType.
char * pattern
The pattern, as provided by the user.
The Style type.
Definition Style.h:43
Dictionary * attributes
Definition Style.h:59
The Theme type.
Definition Theme.h:51
Views are the fundamental building blocks of ObjectivelyMVC user interfaces.
Definition View.h:134
Stylesheet * stylesheet
An optional Stylesheet.
Definition View.h:248
bool clipsSubviews
If true, subviews will be clipped to this View's frame.
Definition View.h:181
void updateBindings(View *self)
Updates data bindings, prompting the appropriate layout changes.
View * superview
The super View.
Definition View.h:259
Array * subviews
The immediate subviews.
Definition View.h:253
ViewController * viewController
The ViewController.
Definition View.h:265
void invalidateStyle(View *self)
Invalidates the computed Style for this View and its descendants.
Definition View.c:1022
Style * style
The element-level Style of this View.
Definition View.h:241
ViewAlignment alignment
The alignment.
Definition View.h:150
SDL_Window * window
The window.
Definition View.h:277
bool needsApplyTheme
If true, this View will apply the Theme before it is drawn.
Definition View.h:217
View * viewWithResource(const Resource *resource, Outlet *outlets)
Instantiates a View initialized with the JSON data in resource.
Definition View.c:1869
Array * visibleSubviews(const View *self)
Definition PageView.c:92
SDL_Color borderColor
The border color.
Definition View.h:165
View * viewWithResourceName(const char *name, Outlet *outlets)
Instantiates a View initialized with the JSON Resource with the specified name.
Definition View.c:1880
void applyStyle(View *self, const Style *style)
Applies the given Style to this View.
int autoresizingMask
The ViewAutoresizing bitmask.
Definition View.h:155
void detachStylesheet(View *self, SDL_Window *window)
Detaches this View's Stylesheet from the Theme associated with the given window.
Definition View.c:624
View * nextResponder
The next responder, or event handler, in the chain.
Definition View.h:229
bool needsLayout
If true, this View will layout its subviews before it is drawn.
Definition View.h:222
char * identifier
An optional identifier.
Definition View.h:202
Array * warnings
The Warnings this View generated.
Definition View.h:271
SDL_Size size(const View *self)
Definition View.c:1640
ViewPadding padding
The padding.
Definition View.h:234
int borderWidth
The border width.
Definition View.h:170
SDL_Size minSize
The minimum size this View may be resized to during layout.
Definition View.h:212
Style * computedStyle
The computed Style of this View.
Definition View.h:186
Set * classNames
The class names.
Definition View.h:176
View * viewWithData(const Data *data, Outlet *outlets)
Instantiates a View initialized with the contents of data.
Definition View.c:1833
SDL_Size maxSize
The maximum size this View may be resized to during layout.
Definition View.h:207
View * init(View *self)
Initializes this View.
Definition Box.c:67
SDL_Color backgroundColor
The background color.
Definition View.h:160
SDL_Rect frame
The frame, relative to the superview.
Definition View.h:191
View * initWithFrame(View *self, const SDL_Rect *frame)
Initializes this View with the specified frame.
Definition View.c:978
View * viewWithDictionary(const Dictionary *dictionary, Outlet *outlets)
Instantiates a View initialized with the attributes described in dictionary.
Definition View.c:1854
bool hidden
If true, this View is not drawn.
Definition View.h:196
int top
Definition View.h:101
int bottom
Definition View.h:101
int right
Definition View.h:101
int left
Definition View.h:101
The Warning type.
Definition Warning.h:50
String * message
The message.
Definition Warning.h:71
WarningType type
The WarningType.
Definition Warning.h:66