35 return window ? SDL_GetWindowPixelDensity(window) : 1.0f;
40#pragma mark - Color Escape Sequences
43 { 0x00, 0x00, 0x00, 0xFF },
44 { 0xFF, 0x00, 0x00, 0xFF },
45 { 0x00, 0xFF, 0x00, 0xFF },
46 { 0xFF, 0xFF, 0x00, 0xFF },
47 { 0x00, 0x00, 0xFF, 0xFF },
48 { 0xFF, 0x00, 0xFF, 0xFF },
49 { 0x00, 0xFF, 0xFF, 0xFF },
50 { 0xFF, 0xFF, 0xFF, 0xFF },
51 { 0xFF, 0x80, 0x00, 0xFF },
52 { 0x80, 0x80, 0x80, 0xFF }
62 char *stripped = malloc(strlen(text) + 1);
65 for (
const char *p = text; *p; p++) {
66 if (p[0] ==
'^' && p[1] >=
'0' && p[1] <=
'9') {
90 const float fx = floorf(info->
rect.x * scale);
91 const float fy = floorf(info->
rect.y * scale);
92 const float fw = ceilf(info->
rect.w * scale);
93 const float fh = ceilf(info->
rect.h * scale);
100 if (w <= 0 || h <= 0) {
104 if (x < 0) { w += x; x = 0; }
105 if (y < 0) { h += y; y = 0; }
107 uint32_t *pixels = (uint32_t *) surface->pixels;
108 const int pitch = surface->pitch / 4;
109 const SDL_Color color = info->
color;
111 for (
int row = y; row < y + h && row < surface->h; row++) {
112 for (
int col = x; col < x + w && col < surface->w; col++) {
113 SDL_Color *c = (SDL_Color *) &pixels[row * pitch + col];
114 c->r = (int) (c->r * color.r / 255.0f);
115 c->g = (int) (c->g * color.g / 255.0f);
116 c->b = (int) (c->b * color.b / 255.0f);
129 SDL_Color defaultColor,
int wrapWidth,
int *outCount) {
131 if (!text || !*text) {
137 if (!stripped || !*stripped) {
143 const size_t strippedLen = strlen(stripped);
146 const int scaledWrapWidth = wrapWidth ? (int) (wrapWidth * font->
scale) : 0;
151 SDL_Color currentColor = defaultColor;
153 int lineStartByte = 0;
156 for (
const char *p = text; *p; p++) {
157 if (p[0] ==
'^' && p[1] >=
'0' && p[1] <=
'9') {
164 if (scaledWrapWidth) {
165 TTF_GetStringSizeWrapped(font->
font, stripped, charIdx + 1, scaledWrapWidth, NULL, &currH);
167 TTF_GetStringSize(font->
font, stripped, charIdx + 1, NULL, &currH);
171 lineStartByte = charIdx;
176 if (charIdx > lineStartByte) {
177 TTF_GetStringSize(font->
font, stripped + lineStartByte, charIdx - lineStartByte, &lineX, NULL);
181 TTF_GetStringSize(font->
font, stripped + charIdx, 1, &charW, NULL);
183 chars[charIdx].
rect.x = (int) (lineX / font->
scale);
184 chars[charIdx].
rect.y = (int) (currH / font->
scale) - lineHeight;
185 chars[charIdx].
rect.w = (int) (charW / font->
scale);
186 chars[charIdx].
rect.h = lineHeight;
187 chars[charIdx].
color = currentColor;
214 SDL_LockSurface(surface);
215 for (
int i = 0; i < charCount; i++) {
218 SDL_UnlockSurface(surface);
235#pragma mark - ObjectInterface
248 this->texture = release(this->texture);
261 String *classNames = $((Object *) this->classNames,
description);
262 String *
description = str(
"%s@%p \"%s\" %s [%d, %d, %d, %d]",
263 this->identifier ?: classnameof(self),
265 ((
Text *) self)->text,
288 if ($(self, bind, colorInlets, style->
attributes)) {
289 this->texture = release(this->texture);
290 this->textureSize = MakeSize(0, 0);
293 char *fontFamily = NULL;
294 int fontSize = -1, fontStyle = -1;
302 if ($(self, bind, fontInlets, style->
attributes)) {
332 $(self, bind, inlets, dictionary);
357 if (this->font->scale != scale) {
358 this->font->scale = scale;
360 this->texture = release(this->texture);
361 this->textureSize = MakeSize(0, 0);
368 if (this->texture == NULL) {
369 SDL_Surface *surface;
371 if (this->colorEscapes) {
377 this->lineWrap ? frame.w : 0);
382 const float textureWidth = roundf(surface->w / scale);
383 const float textureHeight = roundf(surface->h / scale);
385 this->textureSize.w = (int) textureWidth;
386 this->textureSize.h = (int) textureHeight;
388 SDL_Surface *upload = surface;
389 SDL_Surface *converted = NULL;
391 if (SDL_BYTESPERPIXEL(surface->format) == 1) {
392 converted = SDL_CreateSurface(surface->w, surface->h, SDL_PIXELFORMAT_RGBA32);
394 const SDL_PixelFormatDetails *details = SDL_GetPixelFormatDetails(SDL_PIXELFORMAT_RGBA32);
396 const Uint8 *src = (
const Uint8 *) surface->pixels;
397 Uint32 *dst = (Uint32 *) converted->pixels;
398 for (
int y = 0; y < surface->h; y++) {
399 for (
int x = 0; x < surface->w; x++) {
400 const Uint8 a = src[y * surface->pitch + x];
401 dst[y * surface->w + x] = SDL_MapRGBA(details, NULL, 255, 255, 255, a);
405 }
else if (surface->format != SDL_PIXELFORMAT_RGBA32) {
406 converted = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
411 const SDL_GPUTextureCreateInfo texInfo = {
412 .type = SDL_GPU_TEXTURETYPE_2D,
413 .format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM,
414 .usage = SDL_GPU_TEXTUREUSAGE_SAMPLER,
415 .width = (Uint32) upload->w,
416 .height = (Uint32) upload->h,
417 .layer_count_or_depth = 1,
421 this->texture = $(renderer->
device, createTexture, &texInfo, upload->pixels);
424 SDL_DestroySurface(converted);
427 SDL_DestroySurface(surface);
430 assert(this->texture);
432 const SDL_Rect draw_rect = { frame.x, frame.y, this->textureSize.w, this->textureSize.h };
457 this->texture = release(this->texture);
458 this->textureSize = MakeSize(0, 0);
493 SDL_Size
size = MakeSize(0, 0);
496 const char *text = self->
text ?:
"";
516 if (font != self->
font) {
519 self->
font = retain(font);
534 if (strcmp(self->
text ?:
"", text ?:
"")) {
538 if (text && strlen(text)) {
539 self->
text = strdup(text);
564 const int len = vasprintf(&text, fmt, args);
573#pragma mark - Class lifecycle
580 ((ObjectInterface *) clazz->interface)->dealloc =
dealloc;
581 ((ObjectInterface *) clazz->interface)->description =
description;
583 ((ViewInterface *) clazz->interface)->applyStyle =
applyStyle;
585 ((ViewInterface *) clazz->interface)->init =
init;
589 ((ViewInterface *) clazz->interface)->sizeThatFits =
sizeThatFits;
591 ((TextInterface *) clazz->interface)->initWithText =
initWithText;
593 ((TextInterface *) clazz->interface)->setFont =
setFont;
594 ((TextInterface *) clazz->interface)->setText =
setText;
607 clazz = _initialize(&(
const ClassDef) {
609 .superclass =
_View(),
610 .instanceSize =
sizeof(
Text),
611 .interfaceOffset = offsetof(
Text, interface),
612 .interfaceSize =
sizeof(TextInterface),
static Box * initWithFrame(Box *self, const SDL_Rect *frame)
static void sizeCharacters(const Font *self, const char *chars, int *w, int *h)
static SDL_Surface * renderCharacters(const Font *self, const char *chars, SDL_Color color, int wrapWidth)
static Font * cachedFont(const char *family, int size, int style)
const EnumName FontStyleNames[]
static Font * defaultFont(void)
static SDL_Size size(const Image *self)
static void drawTexture(const Renderer *self, Texture *texture, const SDL_Rect *rect, const SDL_Color *color)
static SDL_Surface * renderWithColorEscapes(const Text *self, int wrapWidth)
Renders text with color escape sequences applied to an SDL_Surface.
static void setFont(Text *self, Font *font)
static void setTextWithFormat(Text *self, const char *fmt,...)
static View * init(View *self)
SDL_Color TextEscapeColors[]
static void sizeWithColorEscapes(const Text *self, int *w, int *h)
Resolves the rendered size of this Text's content, stripping color escapes.
static CharInfo * buildCharInfo(const Font *font, const char *text, SDL_Color defaultColor, int wrapWidth, int *outCount)
Builds a CharInfo table for text containing color escape sequences.
static void awakeWithDictionary(View *self, const Dictionary *dictionary)
static void setText(Text *self, const char *text)
static void applyStyle(View *self, const Style *style)
static SDL_Size sizeThatFits(const View *self)
static String * description(const Object *self)
static SDL_Size naturalSize(const Text *self)
static char * stripColors(const char *text)
Strips color escape sequences from text, returning a newly allocated string.
static void colorize(SDL_Surface *surface, const CharInfo *info, float scale)
Colorizes a character's rect on a locked SDL_Surface.
static void render(View *self, Renderer *renderer)
static float view_pixel_density(SDL_Window *window)
static void dealloc(Object *self)
static Text * initWithText(Text *self, const char *text, Font *font)
static void initialize(Class *clazz)
static void renderDeviceWillReset(View *self)
static void renderDeviceDidReset(View *self)
Text rendered with TrueType fonts.
#define MakeInlets(...)
Creates a null-termianted array of Inlets.
#define MakeInlet(name, type, dest, data)
Creates an Inlet with the specified parameters.
static SDL_Rect renderFrame(const View *self)
static void sizeToFit(View *self)
static SDL_Rect bounds(const View *self)
Character position and color for one visible character in a color-escaped string.
float scale
The display pixel density scale (e.g. 2.0 on Retina). Updated by callers via the scale field before i...
TTF_Font * font
The backing font.
Inlets enable inbound data binding of View attributes through JSON.
Renderer extends Object with ObjectivelyMVC's UI rendering layer.
RenderDevice * device
The backing RenderDevice.
Text rendered with TrueType fonts.
SDL_Size textureSize
The logical draw dimensions of the texture (surface size / pixel density).
Texture * texture
The rendered GPU texture.
bool colorEscapes
If true, render text with color escape sequence support (^0-^7).
SDL_Size naturalSize(const Text *self)
Resolves the rendered size of this Text.
SDL_Color color
The text color.
Views are the fundamental building blocks of ObjectivelyMVC user interfaces.
SDL_Window * window
The window.
void render(View *self, Renderer *renderer)
Renders this View using the given renderer.