ObjectivelyMVC
Object oriented MVC framework for SDL3 and GNU C
Loading...
Searching...
No Matches
TextView.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/String.h>
29
30#include "Colors.h"
31#include "TextView.h"
32
33#define _Class _TextView
34
35#pragma mark - Object
36
40static void dealloc(Object *self) {
41
42 TextView *this = (TextView *) self;
43
44 memset(&this->delegate, 0, sizeof(this->delegate));
45
46 free(this->defaultText);
47
48 release(this->text);
49
50 release(this->attributedText);
51
52 super(Object, self, dealloc);
53}
54
55#pragma mark - View
56
61static void applyStyle(View *self, const Style *style) {
62
63 super(View, self, applyStyle, style);
64
65 TextView *this = (TextView *) self;
66
67 const Inlet inlets[] = MakeInlets(
68 MakeInlet("editable", InletTypeBool, &this->isEditable, NULL)
69 );
70
71 $(self, bind, inlets, style->attributes);
72}
73
77static void awakeWithDictionary(View *self, const Dictionary *dictionary) {
78
79 super(View, self, awakeWithDictionary, dictionary);
80
81 TextView *this = (TextView *) self;
82
83 const Inlet inlets[] = MakeInlets(
84 MakeInlet("defaultText", InletTypeCharacters, &this->defaultText, NULL)
85 );
86
87 $(self, bind, inlets, dictionary);
88}
89
93static void becomeKeyResponder(View *self) {
94
95 super(View, self, becomeKeyResponder);
96
97 SDL_StartTextInput(self->window);
98}
99
103static View *init(View *self) {
104 return (View *) $((TextView *) self, initWithFrame, NULL);
105}
106
110static void layoutSubviews(View *self) {
111
112 TextView *this = (TextView *) self;
113
114 const char *text = this->attributedText->chars;
115
116 if (text == NULL || strlen(text) == 0) {
117 if ($((Control *) this, isFocused) == false) {
118 text = this->defaultText;
119 }
120 }
121
122 if (text == NULL) {
123 $(this->text, setText, NULL);
124 } else {
125 if (this->text->text) {
126 if (strcmp(text, this->text->text)) {
127 $(this->text, setText, text);
128 }
129 } else {
130 $(this->text, setText, text);
131 }
132 }
133
134 $((View *) this->text, layoutIfNeeded);
135
136 super(View, self, layoutSubviews);
137}
138
142static void render(View *self, Renderer *renderer) {
143
144 super(View, self, render, renderer);
145
146 TextView *this = (TextView *) self;
147
148 if ($((Control *) this, isFocused)) {
149 const char *text = this->text->text ?: "";
150
151 int w, h;
152 if (this->position == strlen(text)) {
153 $(this->text->font, sizeCharacters, text, &w, &h);
154 } else {
155 char *chars = calloc(this->position + 1, sizeof(char));
156 strncpy(chars, text, this->position);
157
158 $(this->text->font, sizeCharacters, chars, &w, &h);
159 free(chars);
160 }
161
162 SDL_Rect frame = $((View *) this->text, renderFrame);
163
164 const SDL_Point points[] = {
165 { frame.x + w, frame.y },
166 { frame.x + w, frame.y + h }
167 };
168
169 $(renderer, drawLine, points, &Colors.White);
170 }
171}
172
176static void resignKeyResponder(View *self) {
177
178 super(View, self, resignKeyResponder);
179
180 SDL_StopTextInput(self->window);
181}
182
183#pragma mark - Control
184
188static bool captureEvent(Control *self, const SDL_Event *event) {
189
190 bool didEdit = false, didCaptureEvent = false;
191
192 View *view = (View *) self;
193
194 TextView *this = (TextView *) self;
195
196 if (this->isEditable) {
197 if (event->type == SDL_EVENT_TEXT_INPUT) {
198 if ($(self, isFocused)) {
199 if (this->position == this->attributedText->length) {
200 $(this->attributedText, appendCharacters, event->text.text);
201 } else {
202 $(this->attributedText, insertCharactersAtIndex, event->text.text, this->position);
203 }
204 this->position += strlen(event->text.text);
205 didEdit = true;
206 didCaptureEvent = true;
207 }
208 } else if (event->type == SDL_EVENT_KEY_DOWN) {
209 didCaptureEvent = true;
210
211 const char *chars = this->attributedText->chars;
212 const size_t len = this->attributedText->length;
213
214 switch (event->key.key) {
215
216 case SDLK_ESCAPE:
217 case SDLK_KP_ENTER:
218 case SDLK_RETURN:
219 case SDLK_TAB:
220 case SDLK_KP_TAB:
221 $(view, resignKeyResponder);
222 break;
223
224 case SDLK_BACKSPACE:
225 case SDLK_KP_BACKSPACE:
226 if (this->position > 0) {
227 const Range range = { .location = this->position - 1, .length = 1 };
228 $(this->attributedText, deleteCharactersInRange, range);
229 this->position--;
230 didEdit = true;
231 }
232 break;
233
234 case SDLK_DELETE:
235 if (this->position < len) {
236 const Range range = { .location = this->position, .length = 1 };
237 $(this->attributedText, deleteCharactersInRange, range);
238 didEdit = true;
239 }
240 break;
241
242 case SDLK_LEFT:
243 if (SDL_GetModState() & SDL_KMOD_CTRL) {
244 while (this->position > 0 && chars[this->position] == ' ') {
245 this->position--;
246 }
247 while (this->position > 0 && chars[this->position] != ' ') {
248 this->position--;
249 }
250 } else if (this->position > 0) {
251 this->position--;
252 }
253 break;
254
255 case SDLK_RIGHT:
256 if (SDL_GetModState() & SDL_KMOD_CTRL) {
257 while (this->position < len && chars[this->position] == ' ') {
258 this->position++;
259 }
260 while (this->position < len && chars[this->position] != ' ') {
261 this->position++;
262 }
263 if (this->position < len) {
264 this->position++;
265 }
266 } else if (this->position < len) {
267 this->position++;
268 }
269 break;
270
271 case SDLK_HOME:
272 this->position = 0;
273 break;
274
275 case SDLK_END:
276 this->position = len;
277 break;
278
279 case SDLK_A:
280 if (SDL_GetModState() & SDL_KMOD_CTRL) {
281 this->position = 0;
282 }
283 break;
284 case SDLK_E:
285 if (SDL_GetModState() & SDL_KMOD_CTRL) {
286 this->position = len;
287 }
288 break;
289
290 case SDLK_V:
291 if ((SDL_GetModState() & (SDL_KMOD_CTRL | SDL_KMOD_GUI)) && SDL_HasClipboardText()) {
292 char *text = SDL_GetClipboardText();
293 if (text != NULL) {
294 if (*text != '\0') {
295 if (this->position == len) {
296 $(this->attributedText, appendCharacters, text);
297 } else {
298 $(this->attributedText, insertCharactersAtIndex, text, this->position);
299 }
300 this->position += strlen(text);
301 didEdit = true;
302 }
303 SDL_free(text);
304 }
305 }
306 break;
307 }
308 }
309
310 if (didEdit) {
311 self->view.needsLayout = true;
312 if (this->delegate.didEdit) {
313 this->delegate.didEdit(this);
314 }
315
316 $((View *) self, emitViewEvent, ViewEventChange, NULL);
317 }
318 }
319
320 if (didCaptureEvent) {
321 return true;
322 }
323
324 return super(Control, self, captureEvent, event);
325}
326
330static void stateDidChange(Control *self) {
331
332 TextView *this = (TextView *) self;
333
334 if ($(self, isFocused)) {
335
336 SDL_StartTextInput(self->view.window);
337
338 if (this->delegate.didBeginEditing) {
339 this->delegate.didBeginEditing(this);
340 }
341 } else {
342
343 SDL_StopTextInput(self->view.window);
344
345 if (this->delegate.didEndEditing) {
346 this->delegate.didEndEditing(this);
347 }
348 }
349
350 super(Control, self, stateDidChange);
351}
352
353#pragma mark - TextView
354
359static TextView *initWithFrame(TextView *self, const SDL_Rect *frame) {
360
361 self = (TextView *) super(Control, self, initWithFrame, frame);
362 if (self) {
363 self->attributedText = $$(String, string);
364 assert(self->attributedText);
365
366 self->isEditable = true;
367
368 self->text = $(alloc(Text), initWithText, NULL, NULL);
369 assert(self->text);
370
371 $((View *) self, addSubview, (View *) self->text);
372
373 self->control.view.clipsSubviews = true;
374 }
375
376 return self;
377}
378
383static void setAttributedText(TextView *self, const char *attributedText) {
384
385 if (strcmp(self->attributedText->chars ?: "", attributedText ?: "")) {
386
387 $(self->attributedText, setCharacters, attributedText);
388
389 self->position = self->attributedText->length;
390
391 self->control.view.needsLayout = true;
392 }
393}
394
399static void setDefaultText(TextView *self, const char *defaultText) {
400
401 if (strcmp(self->defaultText ?: "", defaultText ?: "")) {
402
403 free(self->defaultText);
404
405 if (defaultText) {
406 self->defaultText = strdup(defaultText);
407 } else {
408 self->defaultText = NULL;
409 }
410
411 self->control.view.needsLayout = true;
412 }
413}
414
415#pragma mark - Class lifecycle
416
420static void initialize(Class *clazz) {
421
422 ((ObjectInterface *) clazz->interface)->dealloc = dealloc;
423
424 ((ViewInterface *) clazz->interface)->applyStyle = applyStyle;
425 ((ViewInterface *) clazz->interface)->becomeKeyResponder = becomeKeyResponder;
426 ((ViewInterface *) clazz->interface)->awakeWithDictionary = awakeWithDictionary;
427 ((ViewInterface *) clazz->interface)->init = init;
428 ((ViewInterface *) clazz->interface)->layoutSubviews = layoutSubviews;
429 ((ViewInterface *) clazz->interface)->render = render;
430 ((ViewInterface *) clazz->interface)->resignKeyResponder = resignKeyResponder;
431
432 ((ControlInterface *) clazz->interface)->captureEvent = captureEvent;
433 ((ControlInterface *) clazz->interface)->stateDidChange = stateDidChange;
434
435 ((TextViewInterface *) clazz->interface)->initWithFrame = initWithFrame;
436 ((TextViewInterface *) clazz->interface)->setAttributedText = setAttributedText;
437 ((TextViewInterface *) clazz->interface)->setDefaultText = setDefaultText;
438}
439
444Class *_TextView(void) {
445 static Class *clazz;
446 static Once once;
447
448 do_once(&once, {
449 clazz = _initialize(&(const ClassDef) {
450 .name = "TextView",
451 .superclass = _Control(),
452 .instanceSize = sizeof(TextView),
453 .interfaceOffset = offsetof(TextView, interface),
454 .interfaceSize = sizeof(TextViewInterface),
456 });
457 });
458
459 return clazz;
460}
461
462#undef _Class
W3C Color constants.
Class * _Control(void)
Definition Control.c:391
static bool isFocused(const Control *self)
Definition Control.c:319
static void sizeCharacters(const Font *self, const char *chars, int *w, int *h)
Definition Font.c:273
static Label * initWithText(Label *self, const char *text, Font *font)
Definition Label.c:97
static void addSubview(View *self, View *subview)
Definition PageView.c:49
static void drawLine(const Renderer *self, const SDL_Point *points, const SDL_Color *color)
Definition Renderer.c:114
static void setText(Text *self, const char *text)
Definition Text.c:532
static void setAttributedText(TextView *self, const char *attributedText)
Definition TextView.c:383
static void becomeKeyResponder(View *self)
Definition TextView.c:93
static void stateDidChange(Control *self)
Definition TextView.c:330
static View * init(View *self)
Definition TextView.c:103
static void awakeWithDictionary(View *self, const Dictionary *dictionary)
Definition TextView.c:77
static TextView * initWithFrame(TextView *self, const SDL_Rect *frame)
Definition TextView.c:359
static void applyStyle(View *self, const Style *style)
Definition TextView.c:61
static bool captureEvent(Control *self, const SDL_Event *event)
Definition TextView.c:188
static void setDefaultText(TextView *self, const char *defaultText)
Definition TextView.c:399
static void render(View *self, Renderer *renderer)
Definition TextView.c:142
static void dealloc(Object *self)
Definition TextView.c:40
Class * _TextView(void)
Definition TextView.c:444
static void resignKeyResponder(View *self)
Definition TextView.c:176
static void initialize(Class *clazz)
Definition TextView.c:420
static void layoutSubviews(View *self)
Definition TextView.c:110
A Control for presenting and capturing user-provied Text.
@ ViewEventChange
A Control's input value has changed.
Definition Types.h:110
@ InletTypeCharacters
Definition View+JSON.h:52
@ InletTypeBool
Definition View+JSON.h:46
#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
static void layoutIfNeeded(View *self)
Definition View.c:1115
static SDL_Rect renderFrame(const View *self)
Definition View.c:1455
static void emitViewEvent(View *self, ViewEvent code, ident data)
Definition View.c:736
W3C Color constants.
Definition Colors.h:37
SDL_Color White
Definition Colors.h:185
Controls are Views which capture and respond to events.
Definition Control.h:83
View view
The superclass.
Definition Control.h:88
Inlets enable inbound data binding of View attributes through JSON.
Definition View+JSON.h:155
Renderer extends Object with ObjectivelyMVC's UI rendering layer.
Definition Renderer.h:70
The Style type.
Definition Style.h:43
Dictionary * attributes
Definition Style.h:59
Text rendered with TrueType fonts.
Definition Text.h:69
A Control for presenting and capturing user-provied Text.
Definition TextView.h:79
bool isEditable
True if this TextView supports editing, false otherwise.
Definition TextView.h:110
size_t position
Definition TextView.h:115
char * defaultText
The default text, displayed when no user-provided text is available.
Definition TextView.h:100
String * attributedText
The user-provided text.
Definition TextView.h:95
Text * text
The text.
Definition TextView.h:120
Control control
The superclass.
Definition TextView.h:84
void setAttributedText(TextView *self, const char *attributedText)
Definition TextView.c:383
Views are the fundamental building blocks of ObjectivelyMVC user interfaces.
Definition View.h:134
bool clipsSubviews
If true, subviews will be clipped to this View's frame.
Definition View.h:181
SDL_Window * window
The window.
Definition View.h:277
bool needsLayout
If true, this View will layout its subviews before it is drawn.
Definition View.h:222
layoutSubviews(View *self)
Performs layout for this View's immediate subviews.
Definition Box.c:74