ObjectivelyMVC
Object oriented MVC framework for SDL3 and GNU C
Loading...
Searching...
No Matches
WindowController.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
26#include <Objectively/String.h>
27
28#include "Log.h"
29#include "WindowController.h"
30
31#define _Class _WindowController
32
33#pragma mark - Delegates
34
38static void mouseMotion_enumerate(View *view, ident data) {
39
40 const SDL_MouseMotionEvent *event = data;
41 const SDL_Point a = MakePoint(event->x - event->xrel, event->y - event->yrel);
42 const SDL_Point b = MakePoint(event->x, event->y);
43
44 if ($(view, containsPoint, &a) && !$(view, containsPoint, &b)) {
45 $(view, emitViewEvent, ViewEventMouseLeave, NULL);
46 $(view, invalidateStyle);
47 } else if ($(view, containsPoint, &b) && !$(view, containsPoint, &a)) {
48 $(view, emitViewEvent, ViewEventMouseEnter, NULL);
49 $(view, invalidateStyle);
50 }
51}
52
53#pragma mark - Object
54
58static void dealloc(Object *self) {
59
60 WindowController *this = (WindowController *) self;
61
62 release(this->debugViewController);
63 release(this->renderer);
64 release(this->theme);
65 release(this->viewController);
66
67 super(Object, self, dealloc);
68}
69
70#pragma mark - WindowController
71
76static void debug(WindowController *self) {
77
78 if (self->viewController && self->debugViewController) {
79
80 ViewController *debugViewController = (ViewController *) self->debugViewController;
81
82 float mx, my;
83 SDL_GetMouseState(&mx, &my);
84 SDL_Point point = MakePoint(mx, my);
85
86 View *view = $(self->viewController->view, hitTest, &point);
87 if (view) {
88 $(self->debugViewController, debug, view, self->renderer);
89 }
90
91 $(debugViewController->view, applyThemeIfNeeded, self->theme);
92 $(debugViewController->view, layoutIfNeeded);
93 $(debugViewController->view, draw, self->renderer);
94 }
95}
96
101static View *keyResponder(const WindowController *self) {
102 return SDL_GetPointerProperty(SDL_GetWindowProperties(self->window), "keyResponder", NULL);
103}
104
108static void enumerateKeyResponders(View *view, ident data) {
109
110 if ($(view, acceptsKeyResponder) && $(view, isVisible)) {
111 $((Array *) data, addObject, view);
112 }
113}
114
119static Array *keyResponders(const WindowController *self) {
120
121 Array *array = (Array *) $(alloc(Array), init);
123
124 return array;
125}
126
131static WindowController *initWithDevice(WindowController *self, RenderDevice *device) {
132
133 assert(device);
134
135 self = (WindowController *) super(Object, self, init);
136 if (self) {
137 self->renderer = $(alloc(Renderer), initWithDevice, device);
138 assert(self->renderer);
139
140 $(self, setWindow, device->window);
141 $(self, setViewController, NULL);
142 $(self, setTheme, NULL);
143
146 }
147
148 return self;
149}
150
155static void render(WindowController *self) {
156
157 assert(self->renderer);
158
159 const RenderDevice *device = self->renderer->device;
160 $(self, renderTo, device->commands, device->framebuffer);
161}
162
167static void renderTo(WindowController *self, CommandBuffer *commands, Framebuffer *framebuffer) {
168
169 assert(self->renderer);
170 assert(commands);
171 assert(framebuffer);
172
173 $(self->renderer, beginFrameWith, commands, framebuffer);
174
177 $(self->viewController->view, draw, self->renderer);
178
179 $(self, debug);
180
181 $(self->renderer, endFrame);
182}
183
189
190 View *next = NULL;
191
192 Array *array = $(self, keyResponders);
193
194 if (array->count) {
195 if (keyResponder && $(array, containsObject, keyResponder)) {
196 const ssize_t index = $(array, indexOfObject, keyResponder);
197 next = $(array, objectAtIndex, (index + 1) % array->count);
198 } else {
199 next = $(array, firstObject);
200 }
201 }
202
203 release(array);
204 return next;
205}
206
212
213 View *prev = NULL;
214
215 Array *array = $(self, keyResponders);
216
217 if (array->count) {
218 if (keyResponder && $(array, containsObject, keyResponder)) {
219 const ssize_t index = $(array, indexOfObject, keyResponder);
220 prev = $(array, objectAtIndex, (index + array->count - 1) % array->count);
221 } else {
222 prev = $(array, firstObject);
223 }
224 }
225
226 release(array);
227 return prev;
228}
229
240static void respondToEvent(WindowController *self, const SDL_Event *event) {
241
242 if (event->type >= SDL_EVENT_WINDOW_FIRST && event->type <= SDL_EVENT_WINDOW_LAST) {
243 if (event->window.windowID != SDL_GetWindowID(self->window)) {
244 return;
245 }
246 }
247
248 SDL_SetPointerProperty(SDL_GetWindowProperties(self->window), "event", (ident) event);
249
250 switch (event->type) {
251 case SDL_EVENT_WINDOW_MOVED:
252 case SDL_EVENT_WINDOW_RESIZED:
253 case SDL_EVENT_WINDOW_MAXIMIZED:
254 case SDL_EVENT_WINDOW_RESTORED:
255 case SDL_EVENT_WINDOW_SAFE_AREA_CHANGED:
256 $(self, setWindow, self->window);
257 break;
258 case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
259 case SDL_EVENT_WINDOW_DISPLAY_CHANGED:
260 case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
261 $(self, setWindow, self->window);
265 break;
266 case SDL_EVENT_WINDOW_DESTROYED:
267 case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
270 break;
271 default:
272 break;
273 }
274
275 if (event->type == SDL_EVENT_MOUSE_MOTION) {
277 }
278
280
281 View *touchTarget = $(self, touchTarget, event);
282
283 switch (event->type) {
284 case SDL_EVENT_KEY_UP:
285 case SDL_EVENT_KEY_DOWN:
286 case SDL_EVENT_TEXT_INPUT:
287 if (keyResponder) {
288 $(keyResponder, respondToEvent, event);
289 } else {
290 $(self->viewController, respondToEvent, event);
291 }
292 break;
293 case SDL_EVENT_MOUSE_BUTTON_DOWN:
294 if (touchTarget) {
296 $(touchTarget, respondToEvent, event);
297 } else if (touchResponder) {
300 } else {
301 $(self->viewController, respondToEvent, event);
302 }
303
307 }
308 break;
309 case SDL_EVENT_MOUSE_BUTTON_UP:
310 if (touchResponder) {
312 } else if (touchTarget) {
313 $(touchTarget, respondToEvent, event);
314 } else {
315 $(self->viewController, respondToEvent, event);
316 }
317 break;
318 case SDL_EVENT_MOUSE_MOTION:
319 case SDL_EVENT_MOUSE_WHEEL:
320 if (touchResponder) {
322 } else if (touchTarget) {
323 $(touchTarget, respondToEvent, event);
324 } else {
325 $(self->viewController, respondToEvent, event);
326 }
327 break;
328 default:
329 $(self->viewController, respondToEvent, event);
330 break;
331 }
332
333 if (event->type == SDL_EVENT_KEY_DOWN) {
334 if (event->key.key == SDLK_TAB) {
335
336 if (event->key.mod & SDL_KMOD_SHIFT) {
338 } else {
340 }
341
342 if (keyResponder) {
344 }
345 }
346
347 if (event->key.key == SDLK_D) {
348 if (event->key.mod & SDL_KMOD_CTRL) {
349 $(self, toggleDebugger);
350 }
351 }
352 }
353
354 SDL_SetPointerProperty(SDL_GetWindowProperties(self->window), "event", NULL);
355}
356
361static void setTheme(WindowController *self, Theme *theme) {
362
363 if (self->theme != theme || self->theme == NULL) {
364
365 release(self->theme);
366
367 if (theme) {
368 self->theme = retain(theme);
369 } else {
370 self->theme = $(alloc(Theme), init);
371 }
372 }
373
375}
376
381static void setViewController(WindowController *self, ViewController *viewController) {
382
383 if (self->viewController != viewController || self->viewController == NULL) {
384
385 SDL_SetPointerProperty(SDL_GetWindowProperties(self->window), "keyResponder", NULL);
386
387 if (self->viewController) {
389 $(self->viewController->view, moveToWindow, NULL);
391 }
392
393 release(self->viewController);
394
395 if (viewController) {
396 self->viewController = retain(viewController);
397 } else {
398 self->viewController = $(alloc(ViewController), init);
399 }
400
402
404 $(self->viewController->view, moveToWindow, self->window);
406 }
407}
408
413static void setWindow(WindowController *self, SDL_Window *window) {
414
415 self->window = window;
416 assert(self->window);
417
418 SDL_PropertiesID properties = SDL_GetWindowProperties(self->window);
419
420 SDL_SetPointerProperty(properties, "windowController", self);
421 SDL_SetPointerProperty(properties, "keyResponder", NULL);
422 SDL_SetPointerProperty(properties, "touchResponder", NULL);
423
424 if (self->renderer) {
425 $(self->renderer->device, setWindow, self->window);
426 }
427
428 if (self->viewController) {
429 $(self->viewController->view, moveToWindow, self->window);
430 }
431
432 if (self->debugViewController) {
434 }
435}
436
442
443 if (self->debugViewController == NULL) {
444
446 assert(self->debugViewController);
447
448 ViewController *debugViewController = (ViewController *) self->debugViewController;
449
450 $(debugViewController, loadView);
451 $(debugViewController, viewWillAppear);
452 $(debugViewController->view, moveToWindow, self->window);
453 $(debugViewController, viewDidAppear);
454
455 if (self->viewController) {
456 $(self->viewController->view, addClassName, "debug");
457 }
458 } else {
459 self->debugViewController = release(self->debugViewController);
460
461 if (self->viewController) {
462 $(self->viewController->view, removeClassName, "debug");
463 }
464 }
465}
466
472 return SDL_GetPointerProperty(SDL_GetWindowProperties(self->window), "touchResponder", NULL);
473}
474
479static View *touchTarget(const WindowController *self, const SDL_Event *event) {
480 SDL_Point point;
481
482 switch (event->type) {
483 case SDL_EVENT_MOUSE_BUTTON_DOWN:
484 point = MakePoint(event->button.x, event->button.y);
485 break;
486 case SDL_EVENT_MOUSE_BUTTON_UP:
487 point = MakePoint(event->button.x, event->button.y);
488 break;
489 case SDL_EVENT_MOUSE_MOTION:
490 point = MakePoint(event->motion.x, event->motion.y);
491 break;
492 case SDL_EVENT_MOUSE_WHEEL: {
493 float mx, my;
494 SDL_GetMouseState(&mx, &my);
495 point = MakePoint(mx, my);
496 break;
497 }
498 default:
499 return NULL;
500 }
501
502 return $(self->viewController->view, hitTest, &point);
503}
504
509static WindowController *windowController(SDL_Window *window) {
510
511 assert(window);
512
513 return SDL_GetPointerProperty(SDL_GetWindowProperties(window), "windowController", NULL);
514}
515
516#pragma mark - Class lifecycle
517
521static void initialize(Class *clazz) {
522
523 ((ObjectInterface *) clazz->interface)->dealloc = dealloc;
524
525 ((WindowControllerInterface *) clazz->interface)->debug = debug;
526 ((WindowControllerInterface *) clazz->interface)->keyResponder = keyResponder;
527 ((WindowControllerInterface *) clazz->interface)->keyResponders = keyResponders;
528 ((WindowControllerInterface *) clazz->interface)->initWithDevice = initWithDevice;
529 ((WindowControllerInterface *) clazz->interface)->nextKeyResponder = nextKeyResponder;
530 ((WindowControllerInterface *) clazz->interface)->previousKeyResponder = previousKeyResponder;
531 ((WindowControllerInterface *) clazz->interface)->render = render;
532 ((WindowControllerInterface *) clazz->interface)->renderTo = renderTo;
533 ((WindowControllerInterface *) clazz->interface)->respondToEvent = respondToEvent;
534 ((WindowControllerInterface *) clazz->interface)->setTheme = setTheme;
535 ((WindowControllerInterface *) clazz->interface)->setViewController = setViewController;
536 ((WindowControllerInterface *) clazz->interface)->setWindow = setWindow;
537 ((WindowControllerInterface *) clazz->interface)->toggleDebugger = toggleDebugger;
538 ((WindowControllerInterface *) clazz->interface)->touchResponder = touchResponder;
539 ((WindowControllerInterface *) clazz->interface)->touchTarget = touchTarget;
540 ((WindowControllerInterface *) clazz->interface)->windowController = windowController;
541}
542
547Class *_WindowController(void) {
548 static Class *clazz;
549 static Once once;
550
551 do_once(&once, {
552 clazz = _initialize(&(const ClassDef) {
553 .name = "WindowController",
554 .superclass = _Object(),
555 .instanceSize = sizeof(WindowController),
556 .interfaceOffset = offsetof(WindowController, interface),
557 .interfaceSize = sizeof(WindowControllerInterface),
559 });
560 });
561
562 return clazz;
563}
564
565#undef _Class
static View * init(View *self)
Definition Box.c:67
static void becomeKeyResponder(View *self)
Definition Control.c:130
static bool acceptsKeyResponder(const View *self)
Definition Control.c:69
static void resignKeyResponder(View *self)
Definition Control.c:248
static void loadView(ViewController *self)
static void renderDeviceDidReset(Font *self)
Definition Font.c:248
static void updateBindings(View *self)
static void renderDeviceWillReset(View *self)
Definition ImageView.c:119
View logging facilities via SDL_Log.
static void endFrame(Renderer *self)
Definition Renderer.c:261
static void beginFrameWith(Renderer *self, CommandBuffer *commands, Framebuffer *framebuffer)
Definition Renderer.c:108
static Theme * theme(SDL_Window *window)
Definition Theme.c:143
@ ViewEventMouseEnter
The mouse has entered a View's frame.
Definition Types.h:90
@ ViewEventMouseLeave
The mouse has left a View's frame.
Definition Types.h:95
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 void layoutIfNeeded(View *self)
Definition View.c:1104
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 void becomeTouchResponder(View *self)
Definition View.c:451
static bool isVisible(const View *self)
Definition View.c:1082
static void resignTouchResponder(View *self)
Definition View.c:1500
static void invalidateStyle(View *self)
Definition View.c:1022
static void moveToWindow(View *self, SDL_Window *window)
Definition View.c:1251
static View * hitTest(const View *self, const SDL_Point *point)
Definition View.c:943
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 enumerateDescendants(const View *self, ViewEnumerator enumerator, ident data)
Definition View.c:797
static void emitViewEvent(View *self, ViewEvent code, ident data)
Definition View.c:725
static void viewWillAppear(ViewController *self)
static void viewWillDisappear(ViewController *self)
static void loadViewIfNeeded(ViewController *self)
static void viewDidAppear(ViewController *self)
static void viewDidDisappear(ViewController *self)
static View * touchTarget(const WindowController *self, const SDL_Event *event)
static void enumerateKeyResponders(View *view, ident data)
ViewEnumerator for finding the next key responder.
static WindowController * initWithDevice(WindowController *self, RenderDevice *device)
static Array * keyResponders(const WindowController *self)
static void toggleDebugger(WindowController *self)
static View * keyResponder(const WindowController *self)
static void setViewController(WindowController *self, ViewController *viewController)
Class * _WindowController(void)
static void setWindow(WindowController *self, SDL_Window *window)
static View * touchResponder(const WindowController *self)
static void render(WindowController *self)
static View * previousKeyResponder(const WindowController *self, View *keyResponder)
static void debug(WindowController *self)
static void setTheme(WindowController *self, Theme *theme)
static void dealloc(Object *self)
static WindowController * windowController(SDL_Window *window)
static void initialize(Class *clazz)
static void renderTo(WindowController *self, CommandBuffer *commands, Framebuffer *framebuffer)
static View * nextKeyResponder(const WindowController *self, View *keyResponder)
static void respondToEvent(WindowController *self, const SDL_Event *event)
static void mouseMotion_enumerate(View *view, ident data)
ViewEnumerator for SDL_EVENT_MOUSE_MOTION.
A WindowController manages a ViewController and its descendants within an SDL_Window.
The DebugViewController type.
ViewController viewController
The superclass.
Renderer extends Object with ObjectivelyMVC's UI rendering layer.
Definition Renderer.h:70
RenderDevice * device
The backing RenderDevice.
Definition Renderer.h:92
The Theme type.
Definition Theme.h:51
A ViewController manages a View and its descendants.
View * view
The main view.
Views are the fundamental building blocks of ObjectivelyMVC user interfaces.
Definition View.h:134
void render(View *self, Renderer *renderer)
Renders this View using the given renderer.
Definition Control.c:179
A WindowController manages a ViewController and its descendants within an SDL_Window.
ViewController * viewController
The ViewController.
Theme * theme
The Theme.
View * nextKeyResponder(const WindowController *self, View *keyResponder)
Finds the next available keyResponder from the given keyResponder.
SDL_Window * window
The window.
Renderer * renderer
The Renderer.
DebugViewController * debugViewController
The DebugViewController.