diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/ui.c | 690 |
1 files changed, 690 insertions, 0 deletions
diff --git a/src/ui.c b/src/ui.c new file mode 100644 index 0000000..a5ab8d3 --- /dev/null +++ b/src/ui.c | |||
| @@ -0,0 +1,690 @@ | |||
| 1 | #include <ui.h> | ||
| 2 | |||
| 3 | #include <cassert.h> | ||
| 4 | #include <cstring.h> | ||
| 5 | #include <font.h> | ||
| 6 | #include <list.h> | ||
| 7 | |||
| 8 | #include <stdlib.h> | ||
| 9 | |||
| 10 | static void* uiAlloc(size_t count, size_t size) { | ||
| 11 | void* mem = calloc(count, size); | ||
| 12 | ASSERT(mem); | ||
| 13 | return mem; | ||
| 14 | } | ||
| 15 | |||
| 16 | #define UI_NEW(TYPE) (TYPE*)uiAlloc(1, sizeof(TYPE)) | ||
| 17 | #define UI_DEL(ppWidget) \ | ||
| 18 | { \ | ||
| 19 | assert(ppWidget); \ | ||
| 20 | void* widget_ = *ppWidget; \ | ||
| 21 | if (widget_) { \ | ||
| 22 | free(widget_); \ | ||
| 23 | *ppWidget = 0; \ | ||
| 24 | } \ | ||
| 25 | } | ||
| 26 | |||
| 27 | DEF_LIST(Widget, uiWidget*) | ||
| 28 | |||
| 29 | /// Base widget type. | ||
| 30 | typedef struct uiWidget { | ||
| 31 | uiWidgetType type; | ||
| 32 | uiRect rect; | ||
| 33 | Widget_list children; | ||
| 34 | } uiWidget; | ||
| 35 | |||
| 36 | /// Button. | ||
| 37 | typedef struct uiButton { | ||
| 38 | uiWidget widget; | ||
| 39 | string text; | ||
| 40 | } uiButton; | ||
| 41 | |||
| 42 | /// Frame. | ||
| 43 | typedef struct uiFrame { | ||
| 44 | uiWidget widget; | ||
| 45 | } uiFrame; | ||
| 46 | |||
| 47 | /// Label. | ||
| 48 | typedef struct uiLabel { | ||
| 49 | uiWidget widget; | ||
| 50 | string text; | ||
| 51 | } uiLabel; | ||
| 52 | |||
| 53 | /// Table cell. | ||
| 54 | typedef struct uiCell { | ||
| 55 | uiWidget* child; | ||
| 56 | } uiCell; | ||
| 57 | |||
| 58 | /// Table. | ||
| 59 | typedef struct uiTable { | ||
| 60 | uiWidget widget; | ||
| 61 | int rows; | ||
| 62 | int cols; | ||
| 63 | int* widths; /// Width, in pixels, for each each column. | ||
| 64 | uiCell* header; /// If non-null, row of 'cols' header cells. | ||
| 65 | uiCell* cells; /// Array of 'rows * cols' cells. | ||
| 66 | } uiTable; | ||
| 67 | |||
| 68 | typedef struct uiLibrary { | ||
| 69 | FontAtlas* font; | ||
| 70 | } uiLibrary; | ||
| 71 | |||
| 72 | // ----------------------------------------------------------------------------- | ||
| 73 | // Library. | ||
| 74 | |||
| 75 | uiLibrary g_ui = {0}; | ||
| 76 | |||
| 77 | bool uiInit(void) { | ||
| 78 | // TODO: Embed the font into the library instead. | ||
| 79 | const char* font_path = "../ui/fontbaker/NK57.bin"; | ||
| 80 | if (!(g_ui.font = LoadFontAtlas(font_path))) { | ||
| 81 | return false; | ||
| 82 | } | ||
| 83 | |||
| 84 | // TODO: Remove. | ||
| 85 | const FontHeader* header = &g_ui.font->header; | ||
| 86 | const int glyph_size = header->glyph_width * header->glyph_height; | ||
| 87 | const int atlas_size = header->num_glyphs * glyph_size; | ||
| 88 | printf("Loaded font: %s\n", font_path); | ||
| 89 | printf( | ||
| 90 | "Glyph: %dx%d (%d bytes)\n", header->glyph_width, header->glyph_height, | ||
| 91 | glyph_size); | ||
| 92 | printf( | ||
| 93 | "Atlas: %dx%d (%d bytes)\n", header->num_glyphs * header->glyph_width, | ||
| 94 | header->glyph_height, atlas_size); | ||
| 95 | |||
| 96 | return true; | ||
| 97 | } | ||
| 98 | |||
| 99 | void uiShutdown(void) {} | ||
| 100 | |||
| 101 | // ----------------------------------------------------------------------------- | ||
| 102 | // Widget. | ||
| 103 | |||
| 104 | static uiButton* uiGetButtonPtr(uiWidgetPtr ptr) { | ||
| 105 | assert(ptr.type == uiTypeButton); | ||
| 106 | assert(ptr.button); | ||
| 107 | return ptr.button; | ||
| 108 | } | ||
| 109 | |||
| 110 | static uiFrame* uiGetFramePtr(uiWidgetPtr ptr) { | ||
| 111 | assert(ptr.type == uiTypeFrame); | ||
| 112 | assert(ptr.frame); | ||
| 113 | return ptr.frame; | ||
| 114 | } | ||
| 115 | |||
| 116 | static uiLabel* uiGetLabelPtr(uiWidgetPtr ptr) { | ||
| 117 | assert(ptr.type == uiTypeLabel); | ||
| 118 | assert(ptr.label); | ||
| 119 | return ptr.label; | ||
| 120 | } | ||
| 121 | |||
| 122 | static uiTable* uiGetTablePtr(uiWidgetPtr ptr) { | ||
| 123 | assert(ptr.type == uiTypeTable); | ||
| 124 | assert(ptr.table); | ||
| 125 | return ptr.table; | ||
| 126 | } | ||
| 127 | |||
| 128 | static void DestroyWidget(uiWidget** ppWidget) { | ||
| 129 | assert(ppWidget); | ||
| 130 | |||
| 131 | uiWidget* widget = *ppWidget; | ||
| 132 | if (widget) { | ||
| 133 | list_foreach_mut(widget->children, child, { DestroyWidget(&child); }); | ||
| 134 | } | ||
| 135 | UI_DEL(ppWidget); | ||
| 136 | } | ||
| 137 | |||
| 138 | uiWidgetPtr uiMakeButtonPtr(uiButton* button) { | ||
| 139 | assert(button); | ||
| 140 | return (uiWidgetPtr){.type = uiTypeButton, .button = button}; | ||
| 141 | } | ||
| 142 | |||
| 143 | uiWidgetPtr uiMakeFramePtr(uiFrame* frame) { | ||
| 144 | assert(frame); | ||
| 145 | return (uiWidgetPtr){.type = uiTypeFrame, .frame = frame}; | ||
| 146 | } | ||
| 147 | |||
| 148 | uiWidgetPtr uiMakeLabelPtr(uiLabel* label) { | ||
| 149 | assert(label); | ||
| 150 | return (uiWidgetPtr){.type = uiTypeLabel, .label = label}; | ||
| 151 | } | ||
| 152 | |||
| 153 | uiWidgetPtr uiMakeTablePtr(uiTable* table) { | ||
| 154 | assert(table); | ||
| 155 | return (uiWidgetPtr){.type = uiTypeTable, .table = table}; | ||
| 156 | } | ||
| 157 | |||
| 158 | void uiWidgetSetParent(uiWidgetPtr child_, uiWidgetPtr parent_) { | ||
| 159 | uiWidget* child = child_.widget; | ||
| 160 | uiWidget* parent = parent_.widget; | ||
| 161 | |||
| 162 | assert(child); | ||
| 163 | assert(parent); | ||
| 164 | |||
| 165 | list_add(parent->children, child); | ||
| 166 | } | ||
| 167 | |||
| 168 | // ----------------------------------------------------------------------------- | ||
| 169 | // Button. | ||
| 170 | |||
| 171 | uiButton* uiMakeButton(const char* text) { | ||
| 172 | assert(text); | ||
| 173 | |||
| 174 | uiButton* button = UI_NEW(uiButton); | ||
| 175 | |||
| 176 | *button = (uiButton){ | ||
| 177 | .widget = | ||
| 178 | (uiWidget){ | ||
| 179 | .type = uiTypeButton, | ||
| 180 | .rect = {0}, | ||
| 181 | }, | ||
| 182 | .text = string_new(text), | ||
| 183 | }; | ||
| 184 | return button; | ||
| 185 | } | ||
| 186 | |||
| 187 | // ----------------------------------------------------------------------------- | ||
| 188 | // Label. | ||
| 189 | |||
| 190 | uiLabel* uiMakeLabel(const char* text) { | ||
| 191 | assert(text); | ||
| 192 | |||
| 193 | uiLabel* label = UI_NEW(uiLabel); | ||
| 194 | |||
| 195 | *label = (uiLabel){ | ||
| 196 | .widget = | ||
| 197 | (uiWidget){ | ||
| 198 | .type = uiTypeLabel, | ||
| 199 | }, | ||
| 200 | .text = string_new(text), | ||
| 201 | }; | ||
| 202 | return label; | ||
| 203 | } | ||
| 204 | |||
| 205 | // ----------------------------------------------------------------------------- | ||
| 206 | // Frame. | ||
| 207 | |||
| 208 | uiFrame* uiMakeFrame(void) { | ||
| 209 | uiFrame* frame = UI_NEW(uiFrame); | ||
| 210 | frame->widget.type = uiTypeFrame; | ||
| 211 | return frame; | ||
| 212 | } | ||
| 213 | |||
| 214 | void uiDestroyFrame(uiFrame** ppFrame) { DestroyWidget((uiWidget**)ppFrame); } | ||
| 215 | |||
| 216 | uiSize uiGetFrameSize(const uiFrame* frame) { | ||
| 217 | assert(frame); | ||
| 218 | return (uiSize){ | ||
| 219 | .width = frame->widget.rect.width, | ||
| 220 | .height = frame->widget.rect.height, | ||
| 221 | }; | ||
| 222 | } | ||
| 223 | |||
| 224 | // ----------------------------------------------------------------------------- | ||
| 225 | // Table. | ||
| 226 | |||
| 227 | static const uiCell* GetCell(const uiTable* table, int row, int col) { | ||
| 228 | assert(table); | ||
| 229 | return table->cells + (row * table->cols) + col; | ||
| 230 | } | ||
| 231 | |||
| 232 | static uiCell* GetCellMut(uiTable* table, int row, int col) { | ||
| 233 | assert(table); | ||
| 234 | return (uiCell*)GetCell(table, row, col); | ||
| 235 | } | ||
| 236 | |||
| 237 | static uiCell* GetLastRow(uiTable* table) { | ||
| 238 | assert(table); | ||
| 239 | assert(table->rows > 0); | ||
| 240 | return &table->cells[table->cols * (table->rows - 1)]; | ||
| 241 | } | ||
| 242 | |||
| 243 | uiTable* uiMakeTable(int rows, int cols, const char** header) { | ||
| 244 | uiTable* table = UI_NEW(uiTable); | ||
| 245 | |||
| 246 | *table = (uiTable){ | ||
| 247 | .widget = (uiWidget){.type = uiTypeTable}, | ||
| 248 | .rows = rows, | ||
| 249 | .cols = cols, | ||
| 250 | .widths = (cols > 0) ? calloc(cols, sizeof(int)) : 0, | ||
| 251 | .header = header ? calloc(cols, sizeof(uiCell)) : 0, | ||
| 252 | .cells = (rows * cols > 0) ? calloc(rows * cols, sizeof(uiCell)) : 0, | ||
| 253 | }; | ||
| 254 | |||
| 255 | if (header) { | ||
| 256 | for (int col = 0; col < cols; ++col) { | ||
| 257 | table->header[col].child = (uiWidget*)uiMakeLabel(header[col]); | ||
| 258 | } | ||
| 259 | } | ||
| 260 | |||
| 261 | return table; | ||
| 262 | } | ||
| 263 | |||
| 264 | void uiTableAddRow(uiTable* table, const char** row) { | ||
| 265 | assert(table); | ||
| 266 | |||
| 267 | table->rows++; | ||
| 268 | |||
| 269 | uiCell* cells = | ||
| 270 | realloc(table->cells, table->rows * table->cols * sizeof(uiCell)); | ||
| 271 | assert(cells); | ||
| 272 | table->cells = cells; | ||
| 273 | |||
| 274 | uiCell* cell = GetLastRow(table); | ||
| 275 | for (int col = 0; col < table->cols; ++col, ++cell) { | ||
| 276 | cell->child = (uiWidget*)uiMakeLabel(row[col]); | ||
| 277 | } | ||
| 278 | } | ||
| 279 | |||
| 280 | void uiTableSet(uiTable* table, int row, int col, uiWidgetPtr child) { | ||
| 281 | assert(table); | ||
| 282 | assert(child.widget); | ||
| 283 | |||
| 284 | GetCellMut(table, row, col)->child = child.widget; | ||
| 285 | } | ||
| 286 | |||
| 287 | const uiWidget* uiTableGet(const uiTable* table, int row, int col) { | ||
| 288 | assert(table); | ||
| 289 | return GetCell(table, row, col)->child; | ||
| 290 | } | ||
| 291 | |||
| 292 | uiWidget* uiTableGetMut(uiTable* table, int row, int col) { | ||
| 293 | assert(table); | ||
| 294 | return GetCellMut(table, row, col)->child; | ||
| 295 | } | ||
| 296 | |||
| 297 | // ----------------------------------------------------------------------------- | ||
| 298 | // Layout and resizing. | ||
| 299 | |||
| 300 | static void ResizeTable(uiTable* table, int width, int height) { | ||
| 301 | assert(table); | ||
| 302 | |||
| 303 | if (table->cols == 0) { | ||
| 304 | return; | ||
| 305 | } | ||
| 306 | |||
| 307 | // Surface width: W. | ||
| 308 | // Columns: N | ||
| 309 | // | ||
| 310 | // First, find the minimum width of each column based on their contents. | ||
| 311 | // | ||
| 312 | // If the sum of column widths < N, then distribute the extra space first | ||
| 313 | // among the smallest columns and building up towards the larger. | ||
| 314 | // | ||
| 315 | // If the sum of column widths > N, subtract from the largest column first and | ||
| 316 | // move towards the smaller ones to distribute the space as evenly as | ||
| 317 | // possible. | ||
| 318 | |||
| 319 | // Find the minimum width for each column. | ||
| 320 | int* widths = table->widths; | ||
| 321 | // Header. | ||
| 322 | for (int col = 0; col < table->cols; ++col) { | ||
| 323 | const uiCell* cell = &table->header[col]; | ||
| 324 | const uiLabel* label = (uiLabel*)cell->child; | ||
| 325 | const int length = (int)string_length(label->text); | ||
| 326 | |||
| 327 | widths[col] = length; | ||
| 328 | } | ||
| 329 | // Table contents. | ||
| 330 | for (int row = 0; row < table->rows; ++row) { | ||
| 331 | for (int col = 0; col < table->cols; ++col) { | ||
| 332 | const uiCell* cell = GetCell(table, row, col); | ||
| 333 | if (cell->child) { | ||
| 334 | const uiLabel* label = (uiLabel*)cell->child; | ||
| 335 | const int length = (int)string_length(label->text); | ||
| 336 | |||
| 337 | widths[col] = length > widths[col] ? length : widths[col]; | ||
| 338 | } | ||
| 339 | } | ||
| 340 | } | ||
| 341 | // Multiply string lengths times glyph width to compute pixel size. | ||
| 342 | for (int col = 0; col < table->cols; ++col) { | ||
| 343 | widths[col] *= g_ui.font->header.glyph_width; | ||
| 344 | } | ||
| 345 | |||
| 346 | // Find the sum of widths. | ||
| 347 | int used_width = 0; | ||
| 348 | for (int col = 0; col < table->cols; ++col) { | ||
| 349 | used_width += widths[col]; | ||
| 350 | } | ||
| 351 | |||
| 352 | // Pad if available width is larger than sum of widths. | ||
| 353 | if (used_width < width) { | ||
| 354 | // Divide evenly among columns. | ||
| 355 | // const int extra = width - used_width; | ||
| 356 | // const int pad = extra / table->cols; | ||
| 357 | // const int mod = extra % table->cols; | ||
| 358 | // for (int col = 0; col < table->cols; ++col) { | ||
| 359 | // table->widths[col] += pad + (col < mod ? 1 : 0); | ||
| 360 | // } | ||
| 361 | |||
| 362 | int extra = width - used_width; | ||
| 363 | while (extra > 0) { | ||
| 364 | // Find smallest column. | ||
| 365 | int smallest = 0; | ||
| 366 | for (int col = 1; col < table->cols; ++col) { | ||
| 367 | if (widths[col] < widths[smallest]) { | ||
| 368 | smallest = col; | ||
| 369 | } | ||
| 370 | } | ||
| 371 | // Pad it and subtract from the budget. | ||
| 372 | widths[smallest] += 1; | ||
| 373 | extra--; | ||
| 374 | } | ||
| 375 | } | ||
| 376 | // Shrink if available width is smaller than the sum of widths. | ||
| 377 | else if (used_width > width) { | ||
| 378 | int deficit = used_width - width; | ||
| 379 | while (deficit > 0) { | ||
| 380 | // Find largest column. | ||
| 381 | int largest = 0; | ||
| 382 | for (int col = 1; col < table->cols; ++col) { | ||
| 383 | if (widths[col] > widths[largest]) { | ||
| 384 | largest = col; | ||
| 385 | } | ||
| 386 | } | ||
| 387 | // Shrink it and subtract from the deficit. | ||
| 388 | widths[largest] -= 1; | ||
| 389 | deficit--; | ||
| 390 | } | ||
| 391 | } | ||
| 392 | } | ||
| 393 | |||
| 394 | static void ResizeWidget(uiWidget* widget, int width, int height) { | ||
| 395 | assert(widget); | ||
| 396 | |||
| 397 | widget->rect.width = width; | ||
| 398 | widget->rect.height = height; | ||
| 399 | |||
| 400 | switch (widget->type) { | ||
| 401 | case uiTypeButton: | ||
| 402 | break; | ||
| 403 | case uiTypeFrame: | ||
| 404 | list_foreach_mut( | ||
| 405 | widget->children, child, { ResizeWidget(child, width, height); }); | ||
| 406 | break; | ||
| 407 | case uiTypeLabel: | ||
| 408 | break; | ||
| 409 | case uiTypeTable: | ||
| 410 | ResizeTable((uiTable*)widget, width, height); | ||
| 411 | break; | ||
| 412 | case uiTypeMax: | ||
| 413 | TRAP(); | ||
| 414 | break; | ||
| 415 | } | ||
| 416 | } | ||
| 417 | |||
| 418 | void uiResizeFrame(uiFrame* frame, int width, int height) { | ||
| 419 | assert(frame); | ||
| 420 | ResizeWidget(&frame->widget, width, height); | ||
| 421 | } | ||
| 422 | |||
| 423 | // ----------------------------------------------------------------------------- | ||
| 424 | // Rendering. | ||
| 425 | |||
| 426 | static const uiPixel uiBlack = {40, 40, 40, 255}; | ||
| 427 | static const uiPixel uiWhite = {255, 255, 255, 255}; | ||
| 428 | static const uiPixel uiPink = {128, 0, 128, 255}; | ||
| 429 | |||
| 430 | /// Render state. | ||
| 431 | /// | ||
| 432 | /// Render functions are allowed to manipulate the state internally (e.g., the | ||
| 433 | /// subsurface), but must leave the state intact before returning, except, of | ||
| 434 | /// course, for the rendered pixels. | ||
| 435 | /// | ||
| 436 | /// We store a subsurface separate from the surface so that we can always check | ||
| 437 | /// whether a given coordinate is within the bounds of the physical surface. | ||
| 438 | typedef struct RenderState { | ||
| 439 | uiSurface surface; /// Surface of pixels on which the UI is rendered. | ||
| 440 | uiRect subsurface; /// Subregion where the current UI widget is rendered. | ||
| 441 | uiPoint pen; /// Current pen position relative to subsurface. | ||
| 442 | } RenderState; | ||
| 443 | |||
| 444 | static void RenderWidget(RenderState* state, const uiWidget* widget); | ||
| 445 | |||
| 446 | void PushSubsurface( | ||
| 447 | RenderState* state, int width, int height, uiRect* original_subsurface, | ||
| 448 | uiPoint* original_pen) { | ||
| 449 | assert(state); | ||
| 450 | assert(original_subsurface); | ||
| 451 | assert(original_pen); | ||
| 452 | |||
| 453 | *original_subsurface = state->subsurface; | ||
| 454 | *original_pen = state->pen; | ||
| 455 | |||
| 456 | state->subsurface.x = state->subsurface.x + state->pen.x; | ||
| 457 | state->subsurface.width = width; | ||
| 458 | state->subsurface.height = height; | ||
| 459 | state->pen.x = 0; | ||
| 460 | } | ||
| 461 | |||
| 462 | void PopSubsurface( | ||
| 463 | RenderState* state, const uiRect* original_subsurface, | ||
| 464 | const uiPoint* original_pen) { | ||
| 465 | assert(state); | ||
| 466 | assert(original_subsurface); | ||
| 467 | assert(original_pen); | ||
| 468 | |||
| 469 | state->subsurface = *original_subsurface; | ||
| 470 | state->pen = *original_pen; | ||
| 471 | } | ||
| 472 | |||
| 473 | /// Checks whether pen + (w,h) is within the surface and subsurface. | ||
| 474 | static bool PenInSurface(const RenderState* state, int w, int h) { | ||
| 475 | assert(state); | ||
| 476 | |||
| 477 | // Surface. | ||
| 478 | const bool in_surface = | ||
| 479 | ((state->subsurface.x + state->pen.x + w) < state->surface.width) && | ||
| 480 | ((state->subsurface.y + state->pen.y + h) < state->surface.height); | ||
| 481 | |||
| 482 | // Subsurface. | ||
| 483 | const bool in_subsurface = ((state->pen.x + w) < state->subsurface.width) && | ||
| 484 | ((state->pen.y + h) < state->subsurface.height); | ||
| 485 | |||
| 486 | return in_surface && in_subsurface; | ||
| 487 | } | ||
| 488 | |||
| 489 | /// Get the pixel at (x,y). | ||
| 490 | static uiPixel* SurfaceXy(uiSurface* surface, int x, int y) { | ||
| 491 | assert(surface); | ||
| 492 | assert(x >= 0); | ||
| 493 | assert(y >= 0); | ||
| 494 | assert(x < surface->width); | ||
| 495 | assert(y < surface->height); | ||
| 496 | return surface->pixels + (surface->width * y) + x; | ||
| 497 | } | ||
| 498 | |||
| 499 | /// Get the pixel at pen + (x,y). | ||
| 500 | static uiPixel* PixelXy(RenderState* state, int x, int y) { | ||
| 501 | assert(state); | ||
| 502 | return SurfaceXy( | ||
| 503 | &state->surface, state->subsurface.x + state->pen.x + x, | ||
| 504 | state->subsurface.y + state->pen.y + y); | ||
| 505 | } | ||
| 506 | |||
| 507 | static void FillRect(const uiRect* rect, uiPixel colour, RenderState* state) { | ||
| 508 | assert(rect); | ||
| 509 | assert(state); | ||
| 510 | assert(rect->width <= state->subsurface.width); | ||
| 511 | assert(rect->height <= state->subsurface.height); | ||
| 512 | |||
| 513 | for (int y = rect->y; y < rect->y + rect->height; ++y) { | ||
| 514 | uiPixel* pixel = PixelXy(state, rect->x, y); | ||
| 515 | for (int x = rect->x; x < rect->x + rect->width; ++x) { | ||
| 516 | *pixel++ = colour; | ||
| 517 | } | ||
| 518 | } | ||
| 519 | } | ||
| 520 | |||
| 521 | /// Render a glyph. | ||
| 522 | /// The glyph is clamped to the surface's bounds. | ||
| 523 | static void RenderGlyph( | ||
| 524 | const FontAtlas* atlas, unsigned char c, RenderState* state) { | ||
| 525 | assert(atlas); | ||
| 526 | assert(state); | ||
| 527 | assert(atlas->header.glyph_width <= state->subsurface.width); | ||
| 528 | assert(atlas->header.glyph_height <= state->subsurface.height); | ||
| 529 | |||
| 530 | const int glyph_width = atlas->header.glyph_width; | ||
| 531 | const int glyph_height = atlas->header.glyph_height; | ||
| 532 | |||
| 533 | const unsigned char* glyph = FontGetGlyph(atlas, c); | ||
| 534 | |||
| 535 | for (int y = 0; (y < atlas->header.glyph_height) && | ||
| 536 | PenInSurface(state, glyph_width - 1, glyph_height - 1); | ||
| 537 | ++y) { | ||
| 538 | for (int x = 0; (x < atlas->header.glyph_width) && | ||
| 539 | PenInSurface(state, glyph_width - 1, glyph_height - 1); | ||
| 540 | ++x, ++glyph) { | ||
| 541 | uiPixel* pixel = PixelXy(state, x, y); | ||
| 542 | if (*glyph > 0) { | ||
| 543 | pixel->r = *glyph; | ||
| 544 | pixel->g = *glyph; | ||
| 545 | pixel->b = *glyph; | ||
| 546 | pixel->a = 255; | ||
| 547 | } | ||
| 548 | } | ||
| 549 | } | ||
| 550 | } | ||
| 551 | |||
| 552 | static void RenderText(const char* text, size_t length, RenderState* state) { | ||
| 553 | assert(text); | ||
| 554 | assert(state); | ||
| 555 | |||
| 556 | const FontAtlas* atlas = g_ui.font; | ||
| 557 | |||
| 558 | const int glyph_width = atlas->header.glyph_width; | ||
| 559 | const int glyph_height = atlas->header.glyph_height; | ||
| 560 | |||
| 561 | // Save the x-pen so that we can restore it after rendering the text. | ||
| 562 | const int x0 = state->pen.x; | ||
| 563 | |||
| 564 | // Truncate the text rendering if it exceeds the subsurface's width or height. | ||
| 565 | const char* c = text; | ||
| 566 | for (size_t i = 0; | ||
| 567 | (i < length) && PenInSurface(state, glyph_width - 1, glyph_height - 1); | ||
| 568 | ++i, ++c, state->pen.x += glyph_width) { | ||
| 569 | RenderGlyph(atlas, *c, state); | ||
| 570 | } | ||
| 571 | |||
| 572 | state->pen.x = x0; | ||
| 573 | } | ||
| 574 | |||
| 575 | static void RenderFrame(const uiFrame* frame, RenderState* state) { | ||
| 576 | assert(frame); | ||
| 577 | |||
| 578 | FillRect(&frame->widget.rect, uiBlack, state); | ||
| 579 | } | ||
| 580 | |||
| 581 | static void RenderLabel(const uiLabel* label, RenderState* state) { | ||
| 582 | assert(label); | ||
| 583 | assert(state); | ||
| 584 | |||
| 585 | RenderText(string_data(label->text), string_length(label->text), state); | ||
| 586 | } | ||
| 587 | |||
| 588 | static void RenderTable(const uiTable* table, RenderState* state) { | ||
| 589 | assert(table); | ||
| 590 | assert(state); | ||
| 591 | |||
| 592 | const int x0 = state->pen.x; | ||
| 593 | const int y0 = state->pen.y; | ||
| 594 | |||
| 595 | uiRect original_subsurface = {0}; | ||
| 596 | uiPoint original_pen = {0}; | ||
| 597 | |||
| 598 | // Render header. | ||
| 599 | if (table->header) { | ||
| 600 | for (int col = 0; col < table->cols; ++col) { | ||
| 601 | // Crop the column contents to the column width so that one column does | ||
| 602 | // not spill into the next. | ||
| 603 | PushSubsurface( | ||
| 604 | state, table->widths[col], state->subsurface.height, | ||
| 605 | &original_subsurface, &original_pen); | ||
| 606 | |||
| 607 | const uiCell* cell = &table->header[col]; | ||
| 608 | RenderWidget(state, cell->child); | ||
| 609 | |||
| 610 | // Reset the original subsurface and pen for subsequent columns. | ||
| 611 | PopSubsurface(state, &original_subsurface, &original_pen); | ||
| 612 | |||
| 613 | // Next column. | ||
| 614 | state->pen.x += table->widths[col]; | ||
| 615 | } | ||
| 616 | } | ||
| 617 | state->pen.x = x0; | ||
| 618 | state->pen.y += g_ui.font->header.glyph_height; | ||
| 619 | |||
| 620 | // Render rows. | ||
| 621 | for (int row = 0; (row < table->rows) && PenInSurface(state, 0, 0); ++row) { | ||
| 622 | for (int col = 0; (col < table->cols) && PenInSurface(state, 0, 0); ++col) { | ||
| 623 | // Crop the column contents to the column width so that one column does | ||
| 624 | // not spill into the next. | ||
| 625 | PushSubsurface( | ||
| 626 | state, table->widths[col], state->subsurface.height, | ||
| 627 | &original_subsurface, &original_pen); | ||
| 628 | |||
| 629 | state->subsurface.x = state->subsurface.x + state->pen.x; | ||
| 630 | state->subsurface.width = table->widths[col]; | ||
| 631 | state->pen.x = 0; | ||
| 632 | |||
| 633 | const uiCell* cell = GetCell(table, row, col); | ||
| 634 | RenderWidget(state, cell->child); | ||
| 635 | |||
| 636 | // Reset the original subsurface and pen for subsequent columns. | ||
| 637 | PopSubsurface(state, &original_subsurface, &original_pen); | ||
| 638 | |||
| 639 | // Next column. | ||
| 640 | state->pen.x += table->widths[col]; | ||
| 641 | } | ||
| 642 | state->pen.x = x0; | ||
| 643 | state->pen.y += g_ui.font->header.glyph_height; | ||
| 644 | } | ||
| 645 | state->pen.y = y0; | ||
| 646 | } | ||
| 647 | |||
| 648 | static void RenderWidget(RenderState* state, const uiWidget* widget) { | ||
| 649 | assert(state); | ||
| 650 | assert(widget); | ||
| 651 | |||
| 652 | // Render this widget. | ||
| 653 | switch (widget->type) { | ||
| 654 | case uiTypeButton: | ||
| 655 | break; | ||
| 656 | case uiTypeFrame: | ||
| 657 | RenderFrame((const uiFrame*)widget, state); | ||
| 658 | break; | ||
| 659 | case uiTypeLabel: | ||
| 660 | RenderLabel((const uiLabel*)widget, state); | ||
| 661 | break; | ||
| 662 | case uiTypeTable: | ||
| 663 | RenderTable((const uiTable*)widget, state); | ||
| 664 | break; | ||
| 665 | case uiTypeMax: | ||
| 666 | TRAP(); | ||
| 667 | break; | ||
| 668 | } | ||
| 669 | |||
| 670 | // Render children. | ||
| 671 | list_foreach(widget->children, child, { RenderWidget(state, child); }); | ||
| 672 | } | ||
| 673 | |||
| 674 | void uiRender(const uiFrame* frame, uiSurface* surface) { | ||
| 675 | assert(frame); | ||
| 676 | assert(surface); | ||
| 677 | |||
| 678 | RenderWidget( | ||
| 679 | &(RenderState){ | ||
| 680 | .surface = *surface, | ||
| 681 | .subsurface = | ||
| 682 | (uiRect){ | ||
| 683 | .x = 0, | ||
| 684 | .y = 0, | ||
| 685 | .width = surface->width, | ||
| 686 | .height = surface->height}, | ||
| 687 | .pen = {.x = 0, .y = 0}, | ||
| 688 | }, | ||
| 689 | (const uiWidget*)frame); | ||
| 690 | } | ||
