diff options
author | 3gg <3gg@shellblade.net> | 2024-05-04 16:44:28 -0700 |
---|---|---|
committer | 3gg <3gg@shellblade.net> | 2024-05-04 16:52:53 -0700 |
commit | af641426fad35cd857c1f14bda523db3d85a70cd (patch) | |
tree | 8a219b03aef0c80cac56cd6b88571a7a6988b35b /src/ui.c |
Initial commit.
Diffstat (limited to 'src/ui.c')
-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 | } | ||