28#include <Objectively/String.h>
35#define _Class _TextView
46 memset(&this->delegate, 0,
sizeof(this->delegate));
48 free(this->defaultText);
52 release(this->attributedText);
54 release(this->completions);
91 $(self, bind, inlets, dictionary);
101 SDL_StartTextInput(self->
window);
118 const char *text = this->attributedText->chars;
120 if (text == NULL || strlen(text) == 0) {
122 text = this->defaultText;
129 if (this->text->text) {
130 if (strcmp(text, this->text->text)) {
140 if (this->text->font) {
141 this->text->view.minSize.h = $(this->text,
sizeText,
" ").h;
159 const char *text = this->text->text ?:
"";
162 char *prefix = calloc(this->position + 1,
sizeof(
char));
163 strncpy(prefix, text, this->position);
177 const SDL_Point points[] = {
178 { frame.x + w, frame.y },
179 { frame.x + w, frame.y + h }
195 if (wasKeyResponder) {
196 SDL_StopTextInput(self->
window);
207 if (position >= len) {
211 if (chars[position] ==
'^' && position + 1 < len &&
212 ((chars[position + 1] >=
'0' && chars[position + 1] <=
'9') || chars[position + 1] ==
'^')) {
216 if (chars[position] ==
':') {
218 if (n && position + n <= len) {
224 while (position + n < len && (chars[position + n] & 0xC0) == 0x80) {
241 for (
size_t p = 0; p < position; p += unit) {
256 const Range range = { .location = 0, .length = self->
position };
272 if (self->completions) {
273 const size_t count = self->completions->count;
276 self->completion = self->completion == count ? 0 : (self->completion + 1) % count;
278 self->completion = self->completion == 0 || self->completion == count ? count - 1 : self->completion - 1;
281 const String *completion = $(self->completions, objectAtIndex, self->completion);
290 String *prefix = $(self->
attributedText, substring, (Range) { .length = self->position });
293 bool didComplete =
false;
295 if (completions && completions->count) {
296 const size_t count = completions->count;
297 const String *first = $(completions, objectAtIndex, 0);
299 size_t len = first->length;
300 for (
size_t i = 1; i < count; i++) {
301 const String *other = $(completions, objectAtIndex, i);
303 while (j < len && j < other->length && first->chars[j] == other->chars[j]) {
311 while (shared < len) {
313 if (shared + unit > len) {
319 if (shared > prefix->length) {
320 String *common = $(first, substring, (Range) { .length = shared });
324 self->completion = count;
326 }
else if (count > 1) {
327 self->completion = step > 0 ? 0 : count - 1;
329 const String *completion = $(completions, objectAtIndex, self->completion);
332 }
else if (strcmp(first->chars ?:
"", prefix->chars ?:
"")) {
338 self->completions = retain(completions);
342 release(completions);
348#pragma mark - Control
355 bool didEdit =
false, didComplete =
false, didCaptureEvent =
false;
361 const size_t position = this->position;
363 if (this->isEditable) {
364 if (event->type == SDL_EVENT_TEXT_INPUT) {
366 if (this->position == this->attributedText->length) {
367 $(this->attributedText, appendCharacters, event->text.text);
369 $(this->attributedText, insertCharactersAtIndex, event->text.text, this->position);
371 this->position += strlen(event->text.text);
373 didCaptureEvent =
true;
375 }
else if (event->type == SDL_EVENT_KEY_DOWN) {
376 didCaptureEvent =
true;
378 const char *chars = this->attributedText->chars;
379 const size_t len = this->attributedText->length;
384 switch (event->key.key) {
389 didEdit = didComplete =
true;
402 case SDLK_KP_BACKSPACE:
403 if (this->position > 0) {
405 const Range range = { .location = this->position - n, .length = n };
406 $(this->attributedText, deleteCharactersInRange, range);
413 if (this->position < len) {
414 const Range range = { .location = this->position, .length =
unitLengthAt(chars, len, this->position,
icons) };
415 $(this->attributedText, deleteCharactersInRange, range);
421 if (SDL_GetModState() & SDL_KMOD_CTRL) {
422 while (this->position > 0 && chars[this->position] ==
' ') {
425 while (this->position > 0 && chars[this->position] !=
' ') {
434 if (SDL_GetModState() & SDL_KMOD_CTRL) {
435 while (this->position < len && chars[this->position] ==
' ') {
438 while (this->position < len && chars[this->position] !=
' ') {
441 if (this->position < len) {
454 this->position = len;
458 if (SDL_GetModState() & SDL_KMOD_CTRL) {
463 if (SDL_GetModState() & SDL_KMOD_CTRL) {
464 this->position = len;
469 if ((SDL_GetModState() & (SDL_KMOD_CTRL | SDL_KMOD_GUI)) && SDL_HasClipboardText()) {
470 char *text = SDL_GetClipboardText();
473 if (this->position == len) {
474 $(this->attributedText, appendCharacters, text);
476 $(this->attributedText, insertCharactersAtIndex, text, this->position);
478 this->position += strlen(text);
488 if (!didComplete && (didEdit || this->position != position)) {
489 this->completions = release(this->completions);
494 if (this->delegate.didEdit) {
495 this->delegate.didEdit(
this);
502 if (didCaptureEvent) {
520 if (this->delegate.didBeginEditing) {
521 this->delegate.didBeginEditing(
this);
527 this->completions = release(this->completions);
529 if (this->delegate.didEndEditing) {
530 this->delegate.didEndEditing(
this);
537#pragma mark - TextView
568 if (strcmp(self->
attributedText->chars ?:
"", attributedText ?:
"")) {
574 self->completions = release(self->completions);
586 if (strcmp(self->
defaultText ?:
"", defaultText ?:
"")) {
600#pragma mark - Class lifecycle
607 ((ObjectInterface *) clazz->interface)->dealloc =
dealloc;
609 ((ViewInterface *) clazz->interface)->applyStyle =
applyStyle;
612 ((ViewInterface *) clazz->interface)->init =
init;
614 ((ViewInterface *) clazz->interface)->render =
render;
617 ((ControlInterface *) clazz->interface)->captureEvent =
captureEvent;
618 ((ControlInterface *) clazz->interface)->stateDidChange =
stateDidChange;
620 ((TextViewInterface *) clazz->interface)->initWithFrame =
initWithFrame;
622 ((TextViewInterface *) clazz->interface)->setDefaultText =
setDefaultText;
634 clazz = _initialize(&(
const ClassDef) {
638 .interfaceSize =
sizeof(TextViewInterface),
static bool isFocused(const Control *self)
static SDL_Size size(const Image *self)
static Label * initWithText(Label *self, const char *text, Font *font)
static void addSubview(View *self, View *subview)
static void drawLine(const Renderer *self, const SDL_Point *points, const SDL_Color *color)
static SDL_Size sizeText(const Text *self, const char *text)
static void setText(Text *self, const char *text)
size_t MVC_IconEscapeLength(const char *chars, const ImageAtlas *icons, AtlasImage **image)
Parses an icon escape at the start of chars: :name:, where name is one or more of [A-Za-z0-9_-] and u...
Text rendered with TrueType fonts.
static void setAttributedText(TextView *self, const char *attributedText)
static bool autocomplete(TextView *self, int step, const ImageAtlas *icons)
Auto-completes the text before the cursor.
static void becomeKeyResponder(View *self)
static size_t unitLengthBefore(const char *chars, size_t len, size_t position, const ImageAtlas *icons)
Returns the length in bytes of the cursor unit ending at position.
static void stateDidChange(Control *self)
static View * init(View *self)
static void awakeWithDictionary(View *self, const Dictionary *dictionary)
static TextView * initWithFrame(TextView *self, const SDL_Rect *frame)
static void applyStyle(View *self, const Style *style)
static bool captureEvent(Control *self, const SDL_Event *event)
static void setDefaultText(TextView *self, const char *defaultText)
static size_t unitLengthAt(const char *chars, size_t len, size_t position, const ImageAtlas *icons)
Returns the length in bytes of the cursor unit starting at position.
static void replacePrefix(TextView *self, const char *chars)
Replaces the text before the cursor with chars, leaving the cursor after them.
static void render(View *self, Renderer *renderer)
static void dealloc(Object *self)
static void resignKeyResponder(View *self)
static void initialize(Class *clazz)
static void layoutSubviews(View *self)
A Control for presenting and capturing user-provied Text.
static ImageAtlas * icons(Theme *self)
static Theme * theme(SDL_Window *window)
@ ViewEventChange
A Control's input value has changed.
#define MakeInlets(...)
Creates a null-termianted array of Inlets.
#define MakeInlet(name, type, dest, data)
Creates an Inlet with the specified parameters.
static void layoutIfNeeded(View *self)
static void setNeedsLayout(View *self)
static SDL_Rect renderFrame(const View *self)
static bool isKeyResponder(const View *self)
static void emitViewEvent(View *self, ViewEvent code, ident data)
Controls are Views which capture and respond to events.
Packs many Images into one sheet, backed by one Texture.
Inlets enable inbound data binding of View attributes through JSON.
Renderer extends Object with ObjectivelyMVC's UI rendering layer.
Text rendered with TrueType fonts.
Array *(* completionsForPrefix)(TextView *textView, const char *prefix)
Delegate callback for auto-completing the text before the cursor with Tab.
A Control for presenting and capturing user-provied Text.
bool isEditable
True if this TextView supports editing, false otherwise.
char * defaultText
The default text, displayed when no user-provided text is available.
String * attributedText
The user-provided text.
TextViewDelegate delegate
The delegate.
void setAttributedText(TextView *self, const char *attributedText)
Views are the fundamental building blocks of ObjectivelyMVC user interfaces.
SDL_Window * window
The window.
layoutSubviews(View *self)
Arranges this View's immediate subviews within its (already final) frame.