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
565 if (MVC_LogEnabled(SDL_LOG_PRIORITY_VERBOSE)) {
566 String *desc = $((Object *) self, description);
567 String *superdesc = $((Object *) superview, description);
568
569 MVC_LogVerbose("%s is clipped by %s\n", desc->chars, superdesc->chars);
570
571 release(desc);
572 release(superdesc);
573 }
574
575 frame.w = frame.h = 0;
576 break;
577 }
578 }
579 superview = superview->superview;
580 }
581
582 return frame;
583}
584
589static bool containsPoint(const View *self, const SDL_Point *point) {
590
591 const SDL_Rect frame = $(self, clippingFrame);
592
593 return (bool) SDL_PointInRect(point, &frame);
594}
595
600static int depth(const View *self) {
601 return (self->superview ? $(self->superview, depth) + 1 : 0);
602}
603
608static View *descendantWithIdentifier(const View *self, const char *identifier) {
609
610 assert(identifier);
611
612 if (self->identifier) {
613 if (strcmp(identifier, self->identifier) == 0) {
614 return (View *) self;
615 }
616 }
617
618 const Array *subviews = (Array *) self->subviews;
619 for (size_t i = 0; i < subviews->count; i++) {
620
621 const View *subview = subviews->elements[i];
622 View *descendant = $(subview, descendantWithIdentifier, identifier);
623 if (descendant) {
624 return descendant;
625 }
626 }
627
628 return NULL;
629}
630
635static void detachStylesheet(View *self, SDL_Window *window) {
636
637 assert(window);
638
639 if (self->stylesheet) {
640 Theme *theme = $$(Theme, theme, window);
641 if (theme) {
643 }
644 }
645}
646
651static void didMoveToWindow(View *self, SDL_Window *window) {
652
653 if (window) {
654 $(self, attachStylesheet, window);
655
656 if (self->superview == NULL) {
657 $(self, sizeToFill);
658 }
659
660 self->needsLayout = true;
661 }
662}
663
668static bool didReceiveEvent(const View *self, const SDL_Event *event) {
669
670 if ($(self, isKeyResponder)) {
671 switch (event->type) {
672 case SDL_EVENT_KEY_DOWN:
673 case SDL_EVENT_KEY_UP:
674 case SDL_EVENT_TEXT_INPUT:
675 return true;
676 }
677 }
678
679 if ($(self, isVisible)) {
680
681 SDL_Point point;
682
683 switch (event->type) {
684 case SDL_EVENT_MOUSE_BUTTON_DOWN:
685 case SDL_EVENT_MOUSE_BUTTON_UP:
686 point = MakePoint(event->button.x, event->button.y);
687 break;
688 case SDL_EVENT_MOUSE_MOTION:
689 point = MakePoint(event->motion.x, event->motion.y);
690 break;
691 case SDL_EVENT_MOUSE_WHEEL: {
692 float mx, my;
693 SDL_GetMouseState(&mx, &my);
694 point = MakePoint(mx, my);
695 break;
696 }
697 default:
698 return false;
699 }
700
701 return $(self, containsPoint, &point);
702 }
703
704 return false;
705}
706
711static void draw(View *self, Renderer *renderer);
712
716static void _draw(View *view, ident data) {
717 draw(view, (Renderer *) data);
718}
719
720static void draw(View *self, Renderer *renderer) {
721
722 assert(self->window);
723
724 if (self->hidden == false) {
725
726 $(renderer, drawView, self);
727
728 $(self, enumerateSubviews, _draw, renderer);
729 }
730}
731
736static void emitViewEvent(View *self, ViewEvent code, ident data) {
737 SDL_PushEvent((SDL_Event *) &(const SDL_UserEvent) {
738 .type = MVC_VIEW_EVENT,
739 .code = code,
740 .data1 = self,
741 .data2 = data
742 });
743}
744
749static void enumerate(View *self, ViewEnumerator enumerator, ident data) {
750
751 assert(enumerator);
752
753 enumerator(self, data);
754
755 $(self, enumerateDescendants, enumerator, data);
756}
757
762static void enumerateAdjacent(const View *self, ViewEnumerator enumerator, ident data) {
763
764 assert(enumerator);
765
766 if (self->superview) {
767 const Array *siblings = (Array *) self->superview->subviews;
768 const ssize_t index = $(siblings, indexOfObject, (const ident) self);
769 if (index > 0) {
770 enumerator($(siblings, objectAtIndex, index - 1), data);
771 }
772 if (index < (ssize_t) (siblings->count - 1)) {
773 enumerator($(siblings, objectAtIndex, index + 1), data);
774 }
775 }
776}
777
782static void enumerateAncestors(const View *self, ViewEnumerator enumerator, ident data) {
783
784 assert(enumerator);
785
786 for (View *view = self->superview; view; view = view->superview) {
787 enumerator(view, data);
788 }
789}
790
795static void enumerateSelection(View *self, const char *rule, ViewEnumerator enumerator, ident data) {
796
797 Selector *selector = $(alloc(Selector), initWithRule, rule);
798 assert(selector);
799
800 $(selector, enumerateSelection, self, enumerator, data);
801 release(selector);
802}
803
808static void enumerateDescendants(const View *self, ViewEnumerator enumerator, ident data) {
809
810 assert(enumerator);
811
812 const Array *subviews = (Array *) self->subviews;
813 for (size_t i = 0; i < subviews->count; i++) {
814
815 View *subview = subviews->elements[i];
816 enumerator(subview, data);
817
818 $(subview, enumerateDescendants, enumerator, data);
819 }
820}
821
826static void enumerateSiblings(const View *self, ViewEnumerator enumerator, ident data) {
827
828 assert(enumerator);
829
830 if (self->superview) {
831
832 const Array *siblings = (Array *) self->superview->subviews;
833 for (size_t i = 0; i < siblings->count; i++) {
834 View *sibling = siblings->elements[i];
835 if (sibling != self) {
836 enumerator(sibling, data);
837 }
838 }
839 }
840}
841
846static void enumerateSubviews(const View *self, ViewEnumerator enumerator, ident data) {
847
848 assert(enumerator);
849
850 const Array *subviews = (Array *) self->subviews;
851 for (size_t i = 0; i < subviews->count; i++) {
852 enumerator((View *) subviews->elements[i], data);
853 }
854}
855
860static void enumerateSuperview(const View *self, ViewEnumerator enumerator, ident data) {
861
862 assert(enumerator);
863
864 if (self->superview) {
865 enumerator(self->superview, data);
866 }
867}
868
873static void enumerateVisible(View *self, ViewEnumerator enumerator, ident data) {
874
875 if ($(self, isVisible) == false) {
876 return;
877 }
878
879 enumerator(self, data);
880
881 const Array *subviews = (Array *) self->subviews;
882 for (size_t i = 0; i < subviews->count; i++) {
883
884 View *subview = subviews->elements[i];
885 $(subview, enumerateVisible, enumerator, data);
886 }
887}
888
892static bool hasClassName_predicate(const ident obj, ident data) {
893 return strcmp(((String *) obj)->chars, (const char *) data) == 0;
894}
895
900static bool hasClassName(const View *self, const char *className) {
901
902 if (className) {
903 return $((Set *) self->classNames, containsObjectMatching, hasClassName_predicate, (ident) className);
904 }
905
906 return false;
907}
908
909typedef struct {
910 SDL_Rect bounds;
912} Overflow;
913
917static void hasOverflow_enumerate(View *view, ident data) {
918
919 Overflow *overflow = data;
920
921 if (!view->hidden) {
922
923 const SDL_Rect bounds = $(view, bounds);
924
925 if (bounds.x + bounds.w > overflow->bounds.w || bounds.y + bounds.h > overflow->bounds.h) {
926
927 overflow->hasOverflow = true;
928
929 $(view, warn, WarningTypeLayout, "Exceeds superview bounds [%d %d %d %d]",
930 overflow->bounds.x, overflow->bounds.y, overflow->bounds.w, overflow->bounds.h);
931 }
932 }
933}
934
939static bool hasOverflow(const View *self) {
940
941 Overflow overflow = {
942 .bounds = $(self, bounds)
943 };
944
945 $(self, enumerateSubviews, hasOverflow_enumerate, (ident) &overflow);
946
947 return overflow.hasOverflow;
948}
949
954static View *hitTest(const View *self, const SDL_Point *point) {
955
956 if (self->hidden == false) {
957
958 if ($(self, containsPoint, point)) {
959
960 const Array *subviews = (Array *) self->subviews;
961 for (size_t i = subviews->count; i; i--) {
962
963 const View *subview = subviews->elements[i - 1];
964 const View *view = $(subview, hitTest, point);
965 if (view) {
966 return (View *) view;
967 }
968 }
969
970 return (View *) self;
971 }
972 }
973
974 return NULL;
975}
976
981static View *init(View *self) {
982 return $(self, initWithFrame, NULL);
983}
984
989static View *initWithFrame(View *self, const SDL_Rect *frame) {
990
991 self = (View *) super(Object, self, init);
992 if (self) {
993
994 if (frame) {
995 self->frame = *frame;
996 }
997
998 self->classNames = $$(Set, setWithCapacity, 0);
999 assert(self->classNames);
1000
1001 self->computedStyle = $(alloc(Style), initWithAttributes, NULL);
1002 assert(self->computedStyle);
1003
1004 self->subviews = $$(Array, arrayWithCapacity, 0);
1005 assert(self->subviews);
1006
1007 self->style = $(alloc(Style), initWithAttributes, NULL);
1008 assert(self->style);
1009
1010 self->warnings = $$(Array, arrayWithCapacity, 0);
1011 assert(self->warnings);
1012
1013 self->maxSize = MakeSize(INT32_MAX, INT32_MAX);
1014
1015 self->needsApplyTheme = true;
1016 self->needsLayout = true;
1017 }
1018
1019 return self;
1020}
1021
1025static void invalidateStyle_enumerate(View *view, ident data) {
1026 view->needsApplyTheme = true;
1027}
1028
1033static void invalidateStyle(View *self) {
1034 $(self, enumerate, invalidateStyle_enumerate, NULL);
1035}
1036
1041static bool isContainer(const View *self) {
1043}
1044
1049static bool isDescendantOfView(const View *self, const View *view) {
1050
1051 assert(view);
1052
1053 while (self) {
1054 if (self == view) {
1055 return true;
1056 }
1057 self = self->superview;
1058 }
1059
1060 return false;
1061}
1062
1067static bool isKeyResponder(const View *self) {
1068
1069 if (self->window) {
1070 return SDL_GetPointerProperty(SDL_GetWindowProperties(self->window), "keyResponder", NULL) == self;
1071 } else {
1072 return false;
1073 }
1074}
1075
1080static bool isTouchResponder(const View *self) {
1081
1082 if (self->window) {
1083 return SDL_GetPointerProperty(SDL_GetWindowProperties(self->window), "touchResponder", NULL) == self;
1084 } else {
1085 return false;
1086 }
1087}
1088
1093static bool isVisible(const View *self) {
1094
1095 for (const View *view = self; view; view = view->superview) {
1096 if (view->hidden) {
1097 return false;
1098 }
1099 }
1100
1101 return true;
1102}
1103
1107static void layoutIfNeeded_enumerate(View *subview, ident data) {
1108 $(subview, layoutIfNeeded);
1109}
1110
1115static void layoutIfNeeded(View *self) {
1116
1118
1119 if (self->needsLayout) {
1120
1122
1123 $(self, layoutSubviews);
1124
1125 self->needsLayout = false;
1126 }
1127}
1128
1133static void layoutSubviews(View *self) {
1134
1136 $(self, sizeToContain);
1137 } else if (self->autoresizingMask & ViewAutoresizingFit) {
1138 $(self, sizeToFit);
1139 }
1140
1141 const SDL_Rect bounds = $(self, bounds);
1142
1143 const Array *subviews = (Array *) self->subviews;
1144 for (size_t i = 0; i < subviews->count; i++) {
1145
1146 View *subview = subviews->elements[i];
1147
1148 SDL_Size subviewSize = $(subview, size);
1149
1150 if (subview->autoresizingMask & ViewAutoresizingWidth) {
1151 subviewSize.w = bounds.w;
1152 }
1153
1155 subviewSize.h = bounds.h;
1156 }
1157
1158 $(subview, resize, &subviewSize);
1159 $(subview, layoutIfNeeded);
1160
1161 switch (subview->alignment & ViewAlignmentMaskHorizontal) {
1162 case ViewAlignmentLeft:
1163 subview->frame.x = 0;
1164 break;
1166 subview->frame.x = (bounds.w - subview->frame.w) * 0.5f;
1167 break;
1168 case ViewAlignmentRight:
1169 subview->frame.x = bounds.w - subview->frame.w;
1170 break;
1171 }
1172
1173 switch (subview->alignment & ViewAlignmentMaskVertical) {
1175 subview->frame.y = 0;
1176 break;
1178 subview->frame.y = (bounds.h - subview->frame.h) * 0.5f;
1179 break;
1181 subview->frame.y = bounds.h - subview->frame.h;
1182 break;
1183 }
1184 }
1185}
1186
1191static bool matchesSelector(const View *self, const SimpleSelector *simpleSelector) {
1192
1193 assert(simpleSelector);
1194
1195 const char *pattern = simpleSelector->pattern;
1196
1197 switch (simpleSelector->type) {
1199 return false;
1200
1202 return true;
1203
1205 const Class *clazz = classForName(pattern);
1206 if (clazz) {
1207 return $((Object *) self, isKindOfClass, clazz);
1208 }
1209 }
1210 break;
1211
1213 return $(self, hasClassName, pattern);
1214
1216 if (self->identifier) {
1217 return strcmp(self->identifier, pattern) == 0;
1218 } else {
1219 return false;
1220 }
1221
1223 if (strcmp("first-child", pattern) == 0) {
1224 if (self->superview) {
1225 return $((Array *) self->superview->subviews, firstObject) == self;
1226 }
1227 } else if (strcmp("last-child", pattern) == 0) {
1228 if (self->superview) {
1229 return $((Array *) self->superview->subviews, lastObject) == self;
1230 }
1231 } else if (strcmp("nth-child(even)", pattern) == 0) {
1232 if (self->superview) {
1233 return ($((Array *) self->superview->subviews, indexOfObject, (ident) self) & 1) == 0;
1234 }
1235 } else if (strcmp("nth-child(odd)", pattern) == 0) {
1236 if (self->superview) {
1237 return $((Array *) self->superview->subviews, indexOfObject, (ident) self) & 1;
1238 }
1239 } else if (strcmp("hover", pattern) == 0) {
1240 float mx, my;
1241 SDL_GetMouseState(&mx, &my);
1242 SDL_Point point = MakePoint(mx, my);
1243 return $(self, containsPoint, &point);
1244 }
1245 break;
1246 }
1247
1248 return false;
1249}
1250
1254static void moveToWindow_enumerate(View *subview, ident data) {
1255 $(subview, moveToWindow, data);
1256}
1257
1262static void moveToWindow(View *self, SDL_Window *window) {
1263
1264 $(self, willMoveToWindow, window);
1265
1266 self->window = window;
1267
1268 $(self, didMoveToWindow, window);
1269
1270 $(self, enumerateSubviews, moveToWindow_enumerate, window);
1271}
1272
1277static String *path(const View *self) {
1278
1279 Array *parts = $$(Array, array);
1280
1281 const View *view = self;
1282 while (view) {
1283
1284 Array *classNames = $((Set *) view->classNames, allObjects);
1285
1286 String *part;
1287 if (view->identifier) {
1288 part = str("#%s", view->identifier);
1289 } else if (classNames->count) {
1290 part = str(".%s", ((String *) $(classNames, firstObject))->chars);
1291 } else {
1292 part = str("%s", classnameof(view));
1293 }
1294
1295 release(classNames);
1296
1297 $(parts, insertObjectAtIndex, part, 0);
1298 release(part);
1299
1300 if (view->viewController) {
1301 break;
1302 }
1303
1304 view = view->superview;
1305 }
1306
1307 String *path = $((Array *) parts, componentsJoinedByCharacters, " > ");
1308
1309 release(parts);
1310
1311 return path;
1312}
1313
1318static void removeAllClassNames(View *self) {
1319
1320 $(self->classNames, removeAllObjects);
1321
1322 $(self, invalidateStyle);
1323}
1324
1328static void removeAllSubviews_enumerate(const Array *array, ident obj, ident data) {
1329 $((View *) data, removeSubview, obj);
1330}
1331
1336static void removeAllSubviews(View *self) {
1337 $(self->subviews, removeAllObjectsWithEnumerator, removeAllSubviews_enumerate, self);
1338}
1339
1344static void removeClassName(View *self, const char *className) {
1345
1346 if (className) {
1347 String *string = $$(String, stringWithCharacters, className);
1348 assert(string);
1349
1350 $(self->classNames, removeObject, string);
1351 release(string);
1352
1353 $(self, invalidateStyle);
1354 }
1355}
1356
1361static void removeFromSuperview(View *self) {
1362
1363 if (self->superview) {
1364 $(self->superview, removeSubview, self);
1365 }
1366}
1367
1372static void removeSubview(View *self, View *subview) {
1373
1374 assert(subview);
1375
1376 if (subview->superview == self) {
1377
1378 subview->superview = NULL;
1379
1380 $(subview, moveToWindow, NULL);
1381
1382 $(self->subviews, removeObject, subview);
1383
1384 self->needsLayout = true;
1385 }
1386}
1387
1392static void render(View *self, Renderer *renderer) {
1393
1394 assert(self->window);
1395
1396 if ($(self, hasClassName, "breakpoint")) {
1397 SDL_TriggerBreakpoint();
1398 }
1399
1400 if (self->backgroundColor.a) {
1401
1402 const SDL_Rect frame = $(self, renderFrame);
1403 $(renderer, drawRectFilled, &frame, &self->backgroundColor);
1404 }
1405
1406 if (self->borderWidth && self->borderColor.a) {
1407
1408 SDL_Rect frame = $(self, renderFrame);
1409 for (int i = 0; i < self->borderWidth; i++) {
1410
1411 $(renderer, drawRect, &frame, &self->borderColor);
1412
1413 frame.x -= 1;
1414 frame.y -= 1;
1415 frame.w += 2;
1416 frame.h += 2;
1417 }
1418 }
1419}
1420
1424static void renderDeviceDidReset_enumerate(View *subview, ident data) {
1425 $(subview, renderDeviceDidReset);
1426}
1427
1432static void renderDeviceDidReset(View *self) {
1434}
1435
1439static void renderDeviceWillReset_enumerate(View *subview, ident data) {
1440 $(subview, renderDeviceWillReset);
1441}
1442
1447static void renderDeviceWillReset(View *self) {
1449}
1450
1455static SDL_Rect renderFrame(const View *self) {
1456
1457 SDL_Rect frame = self->frame;
1458
1459 const View *view = self;
1460 const View *superview = view->superview;
1461 while (superview) {
1462
1463 frame.x += superview->frame.x;
1464 frame.y += superview->frame.y;
1465
1466 if (view->alignment != ViewAlignmentInternal) {
1467 frame.x += superview->padding.left;
1468 frame.y += superview->padding.top;
1469 }
1470
1471 view = superview;
1472 superview = view->superview;
1473 }
1474
1475 return frame;
1476}
1477
1482static void replaceSubview(View *self, View *subview, View *replacement) {
1483
1484 assert(subview);
1485 assert(replacement);
1486
1487 $(self, addSubviewRelativeTo, replacement, subview, ViewPositionAfter);
1488 $(self, removeSubview, subview);
1489}
1490
1495static void resignKeyResponder(View *self) {
1496
1497 if (self->window && $(self, isKeyResponder)) {
1498
1499 String *path = $(self, path);
1500 MVC_LogDebug("%s\n", path->chars);
1501 release(path);
1502
1503 SDL_SetPointerProperty(SDL_GetWindowProperties(self->window), "keyResponder", NULL);
1504 }
1505}
1506
1511static void resignTouchResponder(View *self) {
1512
1513 if (self->window && $(self, isTouchResponder)) {
1514
1515 String *path = $(self, path);
1516 MVC_LogDebug("%s\n", path->chars);
1517 release(path);
1518
1519 SDL_SetPointerProperty(SDL_GetWindowProperties(self->window), "touchResponder", NULL);
1520 }
1521}
1522
1527static void resize(View *self, const SDL_Size *size) {
1528
1529 const int w = clamp(size->w, self->minSize.w, self->maxSize.w);
1530 const int h = clamp(size->h, self->minSize.h, self->maxSize.h);
1531
1532 if (self->frame.w != w || self->frame.h != h) {
1533
1534 self->frame.w = w;
1535 self->frame.h = h;
1536
1537 self->needsLayout = true;
1538
1539 if (self->superview && $(self->superview, isContainer)) {
1540 self->superview->needsLayout = true;
1541 }
1542 }
1543}
1544
1549static void resolve(View *self, Outlet *outlets) {
1550
1551 if (outlets) {
1552 for (Outlet *outlet = outlets; outlet->identifier; outlet++) {
1553 *outlet->view = $(self, descendantWithIdentifier, outlet->identifier);
1554 if (*outlet->view == NULL) {
1555 String *desc = $((Object *) self, description);
1556 MVC_LogError("Failed to resolve outlet '%s' for %s", outlet->identifier, desc->chars);
1557 release(desc);
1558 }
1559
1560 assert(*outlet->view);
1561 }
1562 }
1563}
1564
1569static void respondToEvent(View *self, const SDL_Event *event) {
1570
1571 assert(event);
1572
1573 ViewEvent code = ViewEventNone;
1574
1575 switch (event->type) {
1576 case SDL_EVENT_MOUSE_BUTTON_DOWN:
1578 break;
1579 case SDL_EVENT_MOUSE_BUTTON_UP:
1581 $(self, resignTouchResponder);
1582 break;
1583 case SDL_EVENT_KEY_DOWN:
1584 code = ViewEventKeyDown;
1585 break;
1586 case SDL_EVENT_KEY_UP:
1587 code = ViewEventKeyUp;
1588 break;
1589 default:
1590 break;
1591 }
1592
1593 if (code != ViewEventNone) {
1594 $(self, emitViewEvent, code, NULL);
1595
1596 if (code == ViewEventMouseButtonUp) {
1597 if (event->button.clicks) {
1598 $(self, emitViewEvent, ViewEventClick, NULL);
1599 }
1600 }
1601 }
1602
1603 if (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
1604 if ($(self, acceptsKeyResponder)) {
1605 $(self, becomeKeyResponder);
1606 return;
1607 }
1608 }
1609
1610 if (self->nextResponder) {
1611 $(self->nextResponder, respondToEvent, event);
1612 } else if (self->superview) {
1613 $(self->superview, respondToEvent, event);
1614 }
1615}
1616
1621static Set *_select(View *self, const char *rule) {
1622
1623 Selector *selector = $(alloc(Selector), initWithRule, rule);
1624 assert(selector);
1625
1626 Set *selection = $(selector, select, self);
1627
1628 release(selector);
1629 return selection;
1630}
1631
1636static View *selectFirst(View *self, const char *rule) {
1637
1638 Selector *selector = $(alloc(Selector), initWithRule, rule);
1639 assert(selector);
1640
1641 View *first = $(selector, selectFirst, self);
1642
1643 release(selector);
1644 return first;
1645}
1646
1651static SDL_Size size(const View *self) {
1652 return MakeSize(self->frame.w, self->frame.h);
1653}
1654
1659static SDL_Size sizeThatContains(const View *self) {
1660
1661 const SDL_Size size = $(self, size);
1662 const SDL_Size sizeThatFits = $(self, sizeThatFits);
1663
1664 return MakeSize(max(size.w, sizeThatFits.w), max(size.h, sizeThatFits.h));
1665}
1666
1671static SDL_Size sizeThatFills(const View *self) {
1672 SDL_Size size;
1673
1674 if (self->superview == NULL) {
1675 SDL_GetWindowSize(self->window, &size.w, &size.h);
1676 } else {
1677 size = $(self->superview, size);
1678 }
1679
1680 return size;
1681}
1682
1687static SDL_Size sizeThatFits(const View *self) {
1688
1689 SDL_Size size = $(self, size);
1690
1692 size.w = 0;
1693 }
1694
1696 size.h = 0;
1697 }
1698
1699 if ($(self, isContainer)) {
1700 size = MakeSize(0, 0);
1701
1702 Array *subviews = $(self, visibleSubviews);
1703 for (size_t i = 0; i < subviews->count; i++) {
1704
1705 const View *subview = subviews->elements[i];
1706
1707 SDL_Size subviewSize;
1709 subviewSize = $(subview, sizeThatContains);
1710 } else if (subview->autoresizingMask & ViewAutoresizingFit) {
1711 subviewSize = $(subview, sizeThatFits);
1712 } else {
1713 subviewSize = $(subview, size);
1714 }
1715
1716 SDL_Point subviewOrigin = MakePoint(0, 0);
1717 switch (subview->alignment) {
1718 case ViewAlignmentNone:
1719 subviewOrigin = MakePoint(subview->frame.x, subview->frame.y);
1720 break;
1721 default:
1722 break;
1723 }
1724
1725 size.w = max(size.w, subviewOrigin.x + subviewSize.w);
1726 size.h = max(size.h, subviewOrigin.y + subviewSize.h);
1727 }
1728
1729 size.w += self->padding.left + self->padding.right;
1730 size.h += self->padding.top + self->padding.bottom;
1731
1732 release(subviews);
1733 }
1734
1735 size.w = clamp(size.w, self->minSize.w, self->maxSize.w);
1736 size.h = clamp(size.h, self->minSize.h, self->maxSize.h);
1737
1738 return size;
1739}
1740
1745static void sizeToContain(View *self) {
1746
1747 const SDL_Size size = $(self, sizeThatContains);
1748
1749 $(self, resize, &size);
1750}
1751
1756static void sizeToFill(View *self) {
1757
1758 const SDL_Size size = $(self, sizeThatFills);
1759
1760 $(self, resize, &size);
1761}
1762
1767static void sizeToFit(View *self) {
1768
1769 const SDL_Size size = $(self, sizeThatFits);
1770
1771 $(self, resize, &size);
1772}
1773
1778static View *subviewWithIdentifier(const View *self, const char *identifier) {
1779
1780 assert(identifier);
1781
1782 const Array *subviews = (Array *) self->subviews;
1783 for (size_t i = 0; i < subviews->count; i++) {
1784
1785 View *subview = subviews->elements[i];
1786 if (subview->identifier) {
1787
1788 if (strcmp(identifier, subview->identifier) == 0) {
1789 return subview;
1790 }
1791 }
1792 }
1793
1794 return NULL;
1795}
1796
1800static void updateBindings_enumerate(View *subview, ident data) {
1801 $(subview, updateBindings);
1802}
1803
1808static void updateBindings(View *self) {
1810}
1811
1816static SDL_Rect viewport(const View *self) {
1817
1818 assert(self->window);
1819
1820 const SDL_Rect frame = $(self, renderFrame);
1821
1822 return MVC_TransformToWindow(self->window, &frame);
1823}
1824
1829static View *viewWithCharacters(const char *chars, Outlet *outlets) {
1830
1831 Data *data = $$(Data, dataWithConstMemory, (ident) chars, strlen(chars));
1832
1833 View *view = $$(View, viewWithData, data, outlets);
1834
1835 release(data);
1836
1837 return view;
1838}
1839
1844static View *viewWithData(const Data *data, Outlet *outlets) {
1845
1846 JSONContext *ctx = $(alloc(JSONContext), init);
1847 Dictionary *dictionary = $(ctx, objectFromData, data, 0);
1848
1849 View *view = NULL;
1850 if (dictionary) {
1851 view = $$(View, viewWithDictionary, dictionary, outlets);
1852 release(dictionary);
1853 } else {
1854 MVC_LogError("Failed to parse JSON\n");
1855 }
1856
1857 release(ctx);
1858 return view;
1859}
1860
1865static View *viewWithDictionary(const Dictionary *dictionary, Outlet *outlets) {
1866
1867 View *view = NULL;
1868
1869 BindInlet(&MakeInlet(NULL, InletTypeView, &view, NULL), dictionary);
1870
1871 $(view, resolve, outlets);
1872
1873 return view;
1874}
1875
1880static View *viewWithResource(const Resource *resource, Outlet *outlets) {
1881
1882 assert(resource);
1883
1884 return $$(View, viewWithData, resource->data, outlets);
1885}
1886
1891static View *viewWithResourceName(const char *name, Outlet *outlets) {
1892
1893 Resource *resource = $$(Resource, resourceWithName, name);
1894
1895 View *view = $$(View, viewWithResource, resource, outlets);
1896
1897 release(resource);
1898
1899 return view;
1900}
1901
1905static bool visibleSubviews_filter(ident obj, ident data) {
1906
1907 const View *view = (View *) obj;
1908
1909 return view->hidden == false && view->alignment != ViewAlignmentInternal;
1910}
1911
1916static Array *visibleSubviews(const View *self) {
1917 return $((Array *) self->subviews, filteredArray, visibleSubviews_filter, NULL);
1918}
1919
1924static void warn(View *self, WarningType type, const char *fmt, ...) {
1925
1926 va_list args;
1927 va_start(args, fmt);
1928
1929 Warning *warning = $(alloc(Warning), initWithVaList, type, fmt, args);
1930
1931 va_end(args);
1932
1933 String *description = $((Object *) self, description);
1934 MVC_LogWarn("%s:: %s\n", description->chars, warning->message->chars);
1935 release(description);
1936
1937 $(self->warnings, addObject, warning);
1938
1939 release(warning);
1940}
1941
1946static void willMoveToWindow(View *self, SDL_Window *window) {
1947
1948 if (self->window) {
1949 $(self, resignKeyResponder);
1950 $(self, resignTouchResponder);
1951 $(self, detachStylesheet, self->window);
1952 }
1953}
1954
1955#pragma mark - View class methods
1956
1960static void initialize(Class *clazz) {
1961
1962 ((ObjectInterface *) clazz->interface)->dealloc = dealloc;
1963 ((ObjectInterface *) clazz->interface)->description = description;
1964
1965 ((ViewInterface *) clazz->interface)->acceptsKeyResponder = acceptsKeyResponder;
1966 ((ViewInterface *) clazz->interface)->acceptsTouchResponder = acceptsTouchResponder;
1967 ((ViewInterface *) clazz->interface)->addClassName = addClassName;
1968 ((ViewInterface *) clazz->interface)->addSubview = addSubview;
1969 ((ViewInterface *) clazz->interface)->addSubviewRelativeTo = addSubviewRelativeTo;
1970 ((ViewInterface *) clazz->interface)->ancestorWithIdentifier = ancestorWithIdentifier;
1971 ((ViewInterface *) clazz->interface)->applyStyle = applyStyle;
1972 ((ViewInterface *) clazz->interface)->applyTheme = applyTheme;
1973 ((ViewInterface *) clazz->interface)->applyThemeIfNeeded = applyThemeIfNeeded;
1974 ((ViewInterface *) clazz->interface)->attachStylesheet = attachStylesheet;
1975 ((ViewInterface *) clazz->interface)->awakeWithCharacters = awakeWithCharacters;
1976 ((ViewInterface *) clazz->interface)->awakeWithData = awakeWithData;
1977 ((ViewInterface *) clazz->interface)->awakeWithDictionary = awakeWithDictionary;
1978 ((ViewInterface *) clazz->interface)->awakeWithResource = awakeWithResource;
1979 ((ViewInterface *) clazz->interface)->awakeWithResourceName = awakeWithResourceName;
1980 ((ViewInterface *) clazz->interface)->becomeKeyResponder = becomeKeyResponder;
1981 ((ViewInterface *) clazz->interface)->becomeTouchResponder = becomeTouchResponder;
1982 ((ViewInterface *) clazz->interface)->bind = _bind;
1983 ((ViewInterface *) clazz->interface)->bounds = bounds;
1984 ((ViewInterface *) clazz->interface)->bringSubviewToFront = bringSubviewToFront;
1985 ((ViewInterface *) clazz->interface)->clearWarnings = clearWarnings;
1986 ((ViewInterface *) clazz->interface)->clippingFrame = clippingFrame;
1987 ((ViewInterface *) clazz->interface)->containsPoint = containsPoint;
1988 ((ViewInterface *) clazz->interface)->depth = depth;
1989 ((ViewInterface *) clazz->interface)->descendantWithIdentifier = descendantWithIdentifier;
1990 ((ViewInterface *) clazz->interface)->detachStylesheet = detachStylesheet;
1991 ((ViewInterface *) clazz->interface)->didMoveToWindow = didMoveToWindow;
1992 ((ViewInterface *) clazz->interface)->didReceiveEvent = didReceiveEvent;
1993 ((ViewInterface *) clazz->interface)->draw = draw;
1994 ((ViewInterface *) clazz->interface)->emitViewEvent = emitViewEvent;
1995 ((ViewInterface *) clazz->interface)->enumerate = enumerate;
1996 ((ViewInterface *) clazz->interface)->enumerateAdjacent = enumerateAdjacent;
1997 ((ViewInterface *) clazz->interface)->enumerateAncestors = enumerateAncestors;
1998 ((ViewInterface *) clazz->interface)->enumerateDescendants = enumerateDescendants;
1999 ((ViewInterface *) clazz->interface)->enumerateSelection = enumerateSelection;
2000 ((ViewInterface *) clazz->interface)->enumerateSiblings = enumerateSiblings;
2001 ((ViewInterface *) clazz->interface)->enumerateSubviews = enumerateSubviews;
2002 ((ViewInterface *) clazz->interface)->enumerateSuperview = enumerateSuperview;
2003 ((ViewInterface *) clazz->interface)->enumerateVisible = enumerateVisible;
2004 ((ViewInterface *) clazz->interface)->hasClassName = hasClassName;
2005 ((ViewInterface *) clazz->interface)->hasOverflow = hasOverflow;
2006 ((ViewInterface *) clazz->interface)->hitTest = hitTest;
2007 ((ViewInterface *) clazz->interface)->init = init;
2008 ((ViewInterface *) clazz->interface)->initWithFrame = initWithFrame;
2009 ((ViewInterface *) clazz->interface)->invalidateStyle = invalidateStyle;
2010 ((ViewInterface *) clazz->interface)->isContainer = isContainer;
2011 ((ViewInterface *) clazz->interface)->isDescendantOfView = isDescendantOfView;
2012 ((ViewInterface *) clazz->interface)->isKeyResponder = isKeyResponder;
2013 ((ViewInterface *) clazz->interface)->isTouchResponder = isTouchResponder;
2014 ((ViewInterface *) clazz->interface)->isVisible = isVisible;
2015 ((ViewInterface *) clazz->interface)->layoutIfNeeded = layoutIfNeeded;
2016 ((ViewInterface *) clazz->interface)->layoutSubviews = layoutSubviews;
2017 ((ViewInterface *) clazz->interface)->matchesSelector = matchesSelector;
2018 ((ViewInterface *) clazz->interface)->moveToWindow = moveToWindow;
2019 ((ViewInterface *) clazz->interface)->path = path;
2020 ((ViewInterface *) clazz->interface)->removeAllClassNames = removeAllClassNames;
2021 ((ViewInterface *) clazz->interface)->removeAllSubviews = removeAllSubviews;
2022 ((ViewInterface *) clazz->interface)->removeClassName = removeClassName;
2023 ((ViewInterface *) clazz->interface)->removeFromSuperview = removeFromSuperview;
2024 ((ViewInterface *) clazz->interface)->removeSubview = removeSubview;
2025 ((ViewInterface *) clazz->interface)->render = render;
2026 ((ViewInterface *) clazz->interface)->renderDeviceDidReset = renderDeviceDidReset;
2027 ((ViewInterface *) clazz->interface)->renderDeviceWillReset = renderDeviceWillReset;
2028 ((ViewInterface *) clazz->interface)->renderFrame = renderFrame;
2029 ((ViewInterface *) clazz->interface)->replaceSubview = replaceSubview;
2030 ((ViewInterface *) clazz->interface)->resignKeyResponder = resignKeyResponder;
2031 ((ViewInterface *) clazz->interface)->resignTouchResponder = resignTouchResponder;
2032 ((ViewInterface *) clazz->interface)->resize = resize;
2033 ((ViewInterface *) clazz->interface)->resolve = resolve;
2034 ((ViewInterface *) clazz->interface)->respondToEvent = respondToEvent;
2035 ((ViewInterface *) clazz->interface)->select = _select;
2036 ((ViewInterface *) clazz->interface)->selectFirst = selectFirst;
2037 ((ViewInterface *) clazz->interface)->size = size;
2038 ((ViewInterface *) clazz->interface)->sizeThatContains = sizeThatContains;
2039 ((ViewInterface *) clazz->interface)->sizeThatFills = sizeThatFills;
2040 ((ViewInterface *) clazz->interface)->sizeThatFits = sizeThatFits;
2041 ((ViewInterface *) clazz->interface)->sizeToContain = sizeToContain;
2042 ((ViewInterface *) clazz->interface)->sizeToFill = sizeToFill;
2043 ((ViewInterface *) clazz->interface)->sizeToFit = sizeToFit;
2044 ((ViewInterface *) clazz->interface)->subviewWithIdentifier = subviewWithIdentifier;
2045 ((ViewInterface *) clazz->interface)->updateBindings = updateBindings;
2046 ((ViewInterface *) clazz->interface)->viewport = viewport;
2047 ((ViewInterface *) clazz->interface)->viewWithCharacters = viewWithCharacters;
2048 ((ViewInterface *) clazz->interface)->viewWithData = viewWithData;
2049 ((ViewInterface *) clazz->interface)->viewWithDictionary = viewWithDictionary;
2050 ((ViewInterface *) clazz->interface)->viewWithResource = viewWithResource;
2051 ((ViewInterface *) clazz->interface)->viewWithResourceName = viewWithResourceName;
2052 ((ViewInterface *) clazz->interface)->visibleSubviews = visibleSubviews;
2053 ((ViewInterface *) clazz->interface)->warn = warn;
2054 ((ViewInterface *) clazz->interface)->willMoveToWindow = willMoveToWindow;
2055
2056 MVC_NOTIFICATION_EVENT = SDL_RegisterEvents(1);
2057 assert(MVC_NOTIFICATION_EVENT != 0);
2058
2059 MVC_VIEW_EVENT = SDL_RegisterEvents(1);
2060 assert(MVC_VIEW_EVENT != 0);
2061}
2062
2067Class *_View(void) {
2068 static Class *clazz;
2069 static Once once;
2070
2071 do_once(&once, {
2072 clazz = _initialize(&(const ClassDef) {
2073 .name = "View",
2074 .superclass = _Object(),
2075 .instanceSize = sizeof(View),
2076 .interfaceOffset = offsetof(View, interface),
2077 .interfaceSize = sizeof(ViewInterface),
2079 });
2080 });
2081
2082 return clazz;
2083}
2084
2085#undef _Class
View logging facilities via SDL_Log.
#define MVC_LogVerbose(fmt,...)
Definition Log.h:45
#define MVC_LogWarn(fmt,...)
Definition Log.h:54
#define MVC_LogDebug(fmt,...)
Definition Log.h:48
#define MVC_LogError(fmt,...)
Definition Log.h:57
#define MVC_LogEnabled(priority)
Definition Log.h:39
static void drawView(Renderer *self, View *view)
Definition Renderer.c:234
static void drawRectFilled(const Renderer *self, const SDL_Rect *rect, const SDL_Color *color)
Definition Renderer.c:188
static void drawRect(const Renderer *self, const SDL_Rect *rect, const SDL_Color *color)
Definition Renderer.c:169
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:1651
static View * viewWithResource(const Resource *resource, Outlet *outlets)
Definition View.c:1880
static void removeClassName(View *self, const char *className)
Definition View.c:1344
static void addClassName(View *self, const char *className)
Definition View.c:159
static View * selectFirst(View *self, const char *rule)
Definition View.c:1636
static bool isContainer(const View *self)
Definition View.c:1041
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:651
static bool isDescendantOfView(const View *self, const View *view)
Definition View.c:1049
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:1277
static void resize(View *self, const SDL_Size *size)
Definition View.c:1527
static void detachStylesheet(View *self, SDL_Window *window)
Definition View.c:635
static bool isTouchResponder(const View *self)
Definition View.c:1080
static void layoutIfNeeded(View *self)
Definition View.c:1115
static void sizeToContain(View *self)
Definition View.c:1745
static bool acceptsKeyResponder(const View *self)
Definition View.c:143
Class * _View(void)
Definition View.c:2067
static View * init(View *self)
Definition View.c:981
static Set * _select(View *self, const char *rule)
Definition View.c:1621
static void enumerateAncestors(const View *self, ViewEnumerator enumerator, ident data)
Definition View.c:782
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:600
static void enumerateVisible(View *self, ViewEnumerator enumerator, ident data)
Definition View.c:873
static void draw(View *self, Renderer *renderer)
Definition View.c:720
static SDL_Size sizeThatFills(const View *self)
Definition View.c:1671
static void removeAllClassNames(View *self)
Definition View.c:1318
static void removeSubview(View *self, View *subview)
Definition View.c:1372
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:1800
static void enumerateSubviews(const View *self, ViewEnumerator enumerator, ident data)
Definition View.c:846
static bool visibleSubviews_filter(ident obj, ident data)
Predicate for visibleSubviews.
Definition View.c:1905
static View * viewWithResourceName(const char *name, Outlet *outlets)
Definition View.c:1891
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:1328
static void enumerateSelection(View *self, const char *rule, ViewEnumerator enumerator, ident data)
Definition View.c:795
static void respondToEvent(View *self, const SDL_Event *event)
Definition View.c:1569
static void applyStyle(View *self, const Style *style)
Definition View.c:248
static bool isVisible(const View *self)
Definition View.c:1093
static void resignTouchResponder(View *self)
Definition View.c:1511
static void replaceSubview(View *self, View *subview, View *replacement)
Definition View.c:1482
static bool matchesSelector(const View *self, const SimpleSelector *simpleSelector)
Definition View.c:1191
static SDL_Size sizeThatFits(const View *self)
Definition View.c:1687
static void bringSubviewToFront(View *self, View *subview)
Definition View.c:510
static bool didReceiveEvent(const View *self, const SDL_Event *event)
Definition View.c:668
static View * subviewWithIdentifier(const View *self, const char *identifier)
Definition View.c:1778
static void _draw(View *view, ident data)
ViewEnumerator adapter for draw.
Definition View.c:716
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:939
static Array * visibleSubviews(const View *self)
Definition View.c:1916
static void invalidateStyle(View *self)
Definition View.c:1033
static void sizeToFill(View *self)
Definition View.c:1756
static void warn(View *self, WarningType type, const char *fmt,...)
Definition View.c:1924
static void awakeWithCharacters(View *self, const char *chars)
Definition View.c:353
static View * descendantWithIdentifier(const View *self, const char *identifier)
Definition View.c:608
static void moveToWindow(View *self, SDL_Window *window)
Definition View.c:1262
static View * initWithFrame(View *self, const SDL_Rect *frame)
Definition View.c:989
static void removeFromSuperview(View *self)
Definition View.c:1361
static bool hasClassName_predicate(const ident obj, ident data)
Predicate for hasClassName.
Definition View.c:892
static void removeAllSubviews(View *self)
Definition View.c:1336
static SDL_Size sizeThatContains(const View *self)
Definition View.c:1659
static void applyTheme(View *self, const Theme *theme)
Definition View.c:285
static void render(View *self, Renderer *renderer)
Definition View.c:1392
static void moveToWindow_enumerate(View *subview, ident data)
ViewEnumerator for moveToWindow recursion.
Definition View.c:1254
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:1025
static void addSubview(View *self, View *subview)
Definition View.c:177
static SDL_Rect renderFrame(const View *self)
Definition View.c:1455
static void renderDeviceWillReset_enumerate(View *subview, ident data)
ViewEnumerator for renderDeviceWillReset recursion.
Definition View.c:1439
static void resignKeyResponder(View *self)
Definition View.c:1495
static View * viewWithData(const Data *data, Outlet *outlets)
Definition View.c:1844
static View * viewWithCharacters(const char *chars, Outlet *outlets)
Definition View.c:1829
static void initialize(Class *clazz)
Definition View.c:1960
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:1447
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:1808
static void willMoveToWindow(View *self, SDL_Window *window)
Definition View.c:1946
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:1067
static void renderDeviceDidReset(View *self)
Definition View.c:1432
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:917
static void enumerateSuperview(const View *self, ViewEnumerator enumerator, ident data)
Definition View.c:860
static void renderDeviceDidReset_enumerate(View *subview, ident data)
ViewEnumerator for renderDeviceDidReset recursion.
Definition View.c:1424
static void layoutSubviews(View *self)
Definition View.c:1133
static void enumerate(View *self, ViewEnumerator enumerator, ident data)
Definition View.c:749
static void enumerateAdjacent(const View *self, ViewEnumerator enumerator, ident data)
Definition View.c:762
static SDL_Rect viewport(const View *self)
Definition View.c:1816
static void layoutIfNeeded_enumerate(View *subview, ident data)
ViewEnumerator for layoutIfNeeded recursion.
Definition View.c:1107
static View * hitTest(const View *self, const SDL_Point *point)
Definition View.c:954
static bool hasClassName(const View *self, const char *className)
Definition View.c:900
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:589
static void sizeToFit(View *self)
Definition View.c:1767
static SDL_Rect clippingFrame(const View *self)
Definition View.c:546
static void enumerateSiblings(const View *self, ViewEnumerator enumerator, ident data)
Definition View.c:826
static View * viewWithDictionary(const Dictionary *dictionary, Outlet *outlets)
Definition View.c:1865
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:808
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:1549
static void emitViewEvent(View *self, ViewEvent code, ident data)
Definition View.c:736
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:910
bool hasOverflow
Definition View.c:911
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:1033
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:1880
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:1891
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:635
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:1651
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:1844
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:989
View * viewWithDictionary(const Dictionary *dictionary, Outlet *outlets)
Instantiates a View initialized with the attributes described in dictionary.
Definition View.c:1865
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