summaryrefslogtreecommitdiff
path: root/src/ui.c
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2024-07-13 10:52:24 -0700
committer3gg <3gg@shellblade.net>2024-07-13 10:52:24 -0700
commita4294e4a94189dffb1fdf99c9a60d87d77272926 (patch)
tree2e92f7c95116861bc39f4dae1d0ab5d388550000 /src/ui.c
parentcf9579d7546c04dbc708bd8719e3f935a28088bd (diff)
Restructure project.
Diffstat (limited to 'src/ui.c')
-rw-r--r--src/ui.c906
1 files changed, 2 insertions, 904 deletions
diff --git a/src/ui.c b/src/ui.c
index e8c8ee2..4d09584 100644
--- a/src/ui.c
+++ b/src/ui.c
@@ -1,87 +1,11 @@
1#include <ui.h> 1#include <ui.h>
2 2
3#include <cassert.h> 3#include "uiLibrary.h"
4#include <cstring.h> 4#include "widget/widget.h"
5#include <font.h>
6#include <list.h>
7
8#include <stdlib.h>
9
10#define Max(a, b) ((a) > (b) ? (a) : (b))
11
12#define MaxWidgetEvents 8
13
14static void* uiAlloc(size_t count, size_t size) {
15 void* mem = calloc(count, size);
16 ASSERT(mem);
17 return mem;
18}
19
20#define UI_NEW(TYPE) (TYPE*)uiAlloc(1, sizeof(TYPE))
21#define UI_DEL(ppWidget) \
22 { \
23 assert(ppWidget); \
24 void* widget_ = *ppWidget; \
25 if (widget_) { \
26 free(widget_); \
27 *ppWidget = 0; \
28 } \
29 }
30
31DEF_LIST(Widget, uiWidget*)
32
33/// Base widget type.
34typedef struct uiWidget {
35 uiWidgetType type;
36 uiRect rect;
37 Widget_list children;
38} uiWidget;
39
40/// Button.
41typedef struct uiButton {
42 uiWidget widget;
43 string text;
44} uiButton;
45
46/// Frame.
47typedef struct uiFrame {
48 uiWidget widget;
49} uiFrame;
50
51/// Label.
52typedef struct uiLabel {
53 uiWidget widget;
54 string text;
55} uiLabel;
56
57/// Table cell.
58typedef struct uiCell {
59 uiWidget* child;
60} uiCell;
61
62/// Table.
63typedef struct uiTable {
64 uiWidget widget;
65 int rows;
66 int cols;
67 int* widths; // Width, in pixels, for each column.
68 uiCell* header; // If non-null, row of 'cols' header cells.
69 uiCell** cells; // Array of 'rows' rows, each of 'cols' cells.
70 int offset; // Offset into the rows of the table. Units: rows.
71} uiTable;
72
73typedef struct uiLibrary {
74 FontAtlas* font;
75 uiMouseButtonState mouse_button_state[uiMouseButtonMax];
76 uiWidgetEvent widget_events[MaxWidgetEvents];
77 int num_widget_events;
78} uiLibrary;
79 5
80// ----------------------------------------------------------------------------- 6// -----------------------------------------------------------------------------
81// Library. 7// Library.
82 8
83uiLibrary g_ui = {0};
84
85bool uiInit(void) { 9bool uiInit(void) {
86 // TODO: Embed the font into the library instead. 10 // TODO: Embed the font into the library instead.
87 const char* font_path = "../ui/fontbaker/NK57.bin"; 11 const char* font_path = "../ui/fontbaker/NK57.bin";
@@ -105,829 +29,3 @@ bool uiInit(void) {
105} 29}
106 30
107void uiShutdown(void) {} 31void uiShutdown(void) {}
108
109// -----------------------------------------------------------------------------
110// Widget pointers.
111
112uiPtr uiMakeButtonPtr(uiButton* button) {
113 assert(button);
114 return (uiPtr){.type = uiTypeButton, .button = button};
115}
116
117uiPtr uiMakeFramePtr(uiFrame* frame) {
118 assert(frame);
119 return (uiPtr){.type = uiTypeFrame, .frame = frame};
120}
121
122uiPtr uiMakeLabelPtr(uiLabel* label) {
123 assert(label);
124 return (uiPtr){.type = uiTypeLabel, .label = label};
125}
126
127uiPtr uiMakeTablePtr(uiTable* table) {
128 assert(table);
129 return (uiPtr){.type = uiTypeTable, .table = table};
130}
131
132static uiPtr uiMakeWidgetPtr(uiWidget* widget) {
133 assert(widget);
134 return (uiPtr){.type = widget->type, .widget = widget};
135}
136
137uiButton* uiGetButtonPtr(uiPtr ptr) {
138 assert(ptr.type == uiTypeButton);
139 assert(ptr.button);
140 return ptr.button;
141}
142
143uiFrame* uiGetFramePtr(uiPtr ptr) {
144 assert(ptr.type == uiTypeFrame);
145 assert(ptr.frame);
146 return ptr.frame;
147}
148
149uiLabel* uiGetLabelPtr(uiPtr ptr) {
150 assert(ptr.type == uiTypeLabel);
151 assert(ptr.label);
152 return ptr.label;
153}
154
155uiTable* uiGetTablePtr(uiPtr ptr) {
156 assert(ptr.type == uiTypeTable);
157 assert(ptr.table);
158 return ptr.table;
159}
160
161// -----------------------------------------------------------------------------
162// Widget.
163
164uiWidgetType uiWidgetGetType(const uiWidget* widget) {
165 assert(widget);
166 return widget->type;
167}
168
169static void DestroyWidget(uiWidget** ppWidget) {
170 assert(ppWidget);
171
172 uiWidget* widget = *ppWidget;
173 if (widget) {
174 list_foreach_mut(widget->children, child, { DestroyWidget(&child); });
175 }
176 UI_DEL(ppWidget);
177}
178
179void uiWidgetSetParent(uiPtr child_, uiPtr parent_) {
180 uiWidget* child = child_.widget;
181 uiWidget* parent = parent_.widget;
182
183 assert(child);
184 assert(parent);
185
186 list_add(parent->children, child);
187}
188
189// -----------------------------------------------------------------------------
190// Button.
191
192uiButton* uiMakeButton(const char* text) {
193 assert(text);
194
195 uiButton* button = UI_NEW(uiButton);
196
197 *button = (uiButton){
198 .widget =
199 (uiWidget){
200 .type = uiTypeButton,
201 .rect = {0},
202 },
203 .text = string_new(text),
204 };
205 return button;
206}
207
208// -----------------------------------------------------------------------------
209// Label.
210
211uiLabel* uiMakeLabel(const char* text) {
212 assert(text);
213
214 uiLabel* label = UI_NEW(uiLabel);
215
216 *label = (uiLabel){
217 .widget =
218 (uiWidget){
219 .type = uiTypeLabel,
220 .rect =
221 (uiRect){
222 .width =
223 (int)strlen(text) * g_ui.font->header.glyph_width,
224 .height = g_ui.font->header.glyph_height}},
225 .text = string_new(text),
226 };
227 return label;
228}
229
230const char* uiLabelGetText(const uiLabel* label) {
231 assert(label);
232 return string_data(label->text);
233}
234
235// -----------------------------------------------------------------------------
236// Frame.
237
238uiFrame* uiMakeFrame(void) {
239 uiFrame* frame = UI_NEW(uiFrame);
240 frame->widget.type = uiTypeFrame;
241 return frame;
242}
243
244void uiDestroyFrame(uiFrame** ppFrame) { DestroyWidget((uiWidget**)ppFrame); }
245
246uiSize uiGetFrameSize(const uiFrame* frame) {
247 assert(frame);
248 return (uiSize){
249 .width = frame->widget.rect.width,
250 .height = frame->widget.rect.height,
251 };
252}
253
254// -----------------------------------------------------------------------------
255// Table.
256
257static const uiCell* GetCell(const uiTable* table, int row, int col) {
258 assert(table);
259 return &table->cells[row][col];
260}
261
262static uiCell* GetCellMut(uiTable* table, int row, int col) {
263 assert(table);
264 return (uiCell*)GetCell(table, row, col);
265}
266
267static uiCell** GetLastRow(uiTable* table) {
268 assert(table);
269 assert(table->rows > 0);
270 return &table->cells[table->rows - 1];
271}
272
273uiTable* uiMakeTable(int rows, int cols, const char** header) {
274 uiTable* table = UI_NEW(uiTable);
275
276 *table = (uiTable){
277 .widget = (uiWidget){.type = uiTypeTable},
278 .rows = rows,
279 .cols = cols,
280 .widths = (cols > 0) ? calloc(cols, sizeof(int)) : 0,
281 .header = header ? calloc(cols, sizeof(uiCell)) : 0,
282 .cells = (rows * cols > 0) ? calloc(rows, sizeof(uiCell*)) : 0,
283 };
284
285 if (header) {
286 for (int col = 0; col < cols; ++col) {
287 table->header[col].child = (uiWidget*)uiMakeLabel(header[col]);
288 }
289 }
290
291 return table;
292}
293
294void uiTableClear(uiTable* table) {
295 assert(table);
296
297 // Free row data.
298 if (table->cells) {
299 for (int row = 0; row < table->rows; ++row) {
300 for (int col = 0; col < table->cols; ++col) {
301 DestroyWidget(&table->cells[row][col].child);
302 }
303 free(table->cells[row]);
304 }
305 free(table->cells);
306 table->cells = 0;
307 }
308 table->rows = 0;
309
310 // Clear row widths.
311 for (int i = 0; i < table->cols; ++i) {
312 table->widths[i] = 0;
313 }
314
315 table->offset = 0;
316}
317
318void uiTableAddRow(uiTable* table, const char** row) {
319 assert(table);
320
321 table->rows++;
322
323 uiCell** cells = realloc(table->cells, table->rows * sizeof(uiCell*));
324 ASSERT(cells);
325 table->cells = cells;
326
327 uiCell** pLastRow = GetLastRow(table);
328 *pLastRow = calloc(table->cols, sizeof(uiCell));
329 ASSERT(*pLastRow);
330 uiCell* lastRow = *pLastRow;
331
332 for (int col = 0; col < table->cols; ++col) {
333 lastRow[col].child = (uiWidget*)uiMakeLabel(row[col]);
334 }
335}
336
337void uiTableSet(uiTable* table, int row, int col, uiPtr child) {
338 assert(table);
339 assert(child.widget);
340
341 GetCellMut(table, row, col)->child = child.widget;
342}
343
344const uiWidget* uiTableGet(const uiTable* table, int row, int col) {
345 assert(table);
346 return GetCell(table, row, col)->child;
347}
348
349uiWidget* uiTableGetMut(uiTable* table, int row, int col) {
350 assert(table);
351 return GetCellMut(table, row, col)->child;
352}
353
354// -----------------------------------------------------------------------------
355// Layout and resizing.
356
357static void ResizeTable(uiTable* table, int width, int height) {
358 assert(table);
359
360 if (table->cols == 0) {
361 return;
362 }
363
364 // Surface width: W.
365 // Columns: N
366 //
367 // First, find the minimum width of each column based on their contents.
368 //
369 // If the sum of column widths < N, then distribute the extra space first
370 // among the smallest columns and building up towards the larger.
371 //
372 // If the sum of column widths > N, subtract from the largest column first and
373 // move towards the smaller ones to distribute the space as evenly as
374 // possible.
375
376 // Find the minimum width for each column.
377 int* widths = table->widths;
378 // Header.
379 for (int col = 0; col < table->cols; ++col) {
380 const uiCell* cell = &table->header[col];
381 const uiLabel* label = (uiLabel*)cell->child;
382 const int length = (int)string_length(label->text);
383
384 widths[col] = length;
385 }
386 // Table contents.
387 for (int row = 0; row < table->rows; ++row) {
388 for (int col = 0; col < table->cols; ++col) {
389 const uiCell* cell = GetCell(table, row, col);
390 if (cell->child) {
391 const uiLabel* label = (uiLabel*)cell->child;
392 const int length = (int)string_length(label->text);
393
394 widths[col] = length > widths[col] ? length : widths[col];
395 }
396 }
397 }
398 // Multiply string lengths times glyph width to compute pixel size.
399 for (int col = 0; col < table->cols; ++col) {
400 widths[col] *= g_ui.font->header.glyph_width;
401 }
402
403 // Find the sum of widths.
404 int used_width = 0;
405 for (int col = 0; col < table->cols; ++col) {
406 used_width += widths[col];
407 }
408
409 // Pad if available width is larger than sum of widths.
410 if (used_width < width) {
411 // Divide evenly among columns.
412 // const int extra = width - used_width;
413 // const int pad = extra / table->cols;
414 // const int mod = extra % table->cols;
415 // for (int col = 0; col < table->cols; ++col) {
416 // table->widths[col] += pad + (col < mod ? 1 : 0);
417 // }
418
419 int extra = width - used_width;
420 while (extra > 0) {
421 // Find smallest column.
422 int smallest = 0;
423 for (int col = 1; col < table->cols; ++col) {
424 if (widths[col] < widths[smallest]) {
425 smallest = col;
426 }
427 }
428 // Pad it and subtract from the budget.
429 widths[smallest] += 1;
430 extra--;
431 }
432 }
433 // Shrink if available width is smaller than the sum of widths.
434 else if (used_width > width) {
435 int deficit = used_width - width;
436 while (deficit > 0) {
437 // Find largest column.
438 int largest = 0;
439 for (int col = 1; col < table->cols; ++col) {
440 if (widths[col] > widths[largest]) {
441 largest = col;
442 }
443 }
444 // Shrink it and subtract from the deficit.
445 widths[largest] -= 1;
446 deficit--;
447 }
448 }
449}
450
451static void ResizeWidget(uiWidget* widget, int width, int height) {
452 assert(widget);
453
454 widget->rect.width = width;
455 widget->rect.height = height;
456
457 switch (widget->type) {
458 case uiTypeButton:
459 break;
460 case uiTypeFrame:
461 list_foreach_mut(
462 widget->children, child, { ResizeWidget(child, width, height); });
463 break;
464 case uiTypeLabel:
465 break;
466 case uiTypeTable:
467 ResizeTable((uiTable*)widget, width, height);
468 break;
469 case uiTypeMax:
470 TRAP();
471 break;
472 }
473}
474
475void uiResizeFrame(uiFrame* frame, int width, int height) {
476 assert(frame);
477 ResizeWidget(&frame->widget, width, height);
478}
479
480// -----------------------------------------------------------------------------
481// Rendering.
482
483static const uiPixel uiBlack = {40, 40, 40, 255};
484static const uiPixel uiWhite = {255, 255, 255, 255};
485static const uiPixel uiPink = {128, 0, 128, 255};
486
487/// Render state.
488///
489/// Render functions are allowed to manipulate the state internally (e.g., the
490/// subsurface), but must leave the state intact before returning, except, of
491/// course, for the rendered pixels.
492///
493/// We store a subsurface separate from the surface so that we can always check
494/// whether a given coordinate is within the bounds of the physical surface.
495typedef struct RenderState {
496 uiSurface surface; /// Surface of pixels on which the UI is rendered.
497 uiRect subsurface; /// Subregion where the current UI widget is rendered.
498 uiPoint pen; /// Current pen position relative to subsurface.
499} RenderState;
500
501static void RenderWidget(RenderState* state, const uiWidget* widget);
502
503void PushSubsurface(
504 RenderState* state, int width, int height, uiRect* original_subsurface,
505 uiPoint* original_pen) {
506 assert(state);
507 assert(original_subsurface);
508 assert(original_pen);
509
510 *original_subsurface = state->subsurface;
511 *original_pen = state->pen;
512
513 state->subsurface.x = state->subsurface.x + state->pen.x;
514 state->subsurface.width = width;
515 state->subsurface.height = height;
516 state->pen.x = 0;
517}
518
519void PopSubsurface(
520 RenderState* state, const uiRect* original_subsurface,
521 const uiPoint* original_pen) {
522 assert(state);
523 assert(original_subsurface);
524 assert(original_pen);
525
526 state->subsurface = *original_subsurface;
527 state->pen = *original_pen;
528}
529
530/// Checks whether pen + (w,h) is within the surface and subsurface.
531static bool PenInSurface(const RenderState* state, int w, int h) {
532 assert(state);
533
534 // Surface.
535 const bool in_surface =
536 ((state->subsurface.x + state->pen.x + w) < state->surface.width) &&
537 ((state->subsurface.y + state->pen.y + h) < state->surface.height);
538
539 // Subsurface.
540 const bool in_subsurface = ((state->pen.x + w) < state->subsurface.width) &&
541 ((state->pen.y + h) < state->subsurface.height);
542
543 return in_surface && in_subsurface;
544}
545
546/// Get the pixel at (x,y).
547static uiPixel* SurfaceXy(uiSurface* surface, int x, int y) {
548 assert(surface);
549 assert(x >= 0);
550 assert(y >= 0);
551 assert(x < surface->width);
552 assert(y < surface->height);
553 return surface->pixels + (surface->width * y) + x;
554}
555
556/// Get the pixel at pen + (x,y).
557static uiPixel* PixelXy(RenderState* state, int x, int y) {
558 assert(state);
559 return SurfaceXy(
560 &state->surface, state->subsurface.x + state->pen.x + x,
561 state->subsurface.y + state->pen.y + y);
562}
563
564static void FillRect(const uiRect* rect, uiPixel colour, RenderState* state) {
565 assert(rect);
566 assert(state);
567 assert(rect->width <= state->subsurface.width);
568 assert(rect->height <= state->subsurface.height);
569
570 for (int y = rect->y; y < rect->y + rect->height; ++y) {
571 uiPixel* pixel = PixelXy(state, rect->x, y);
572 for (int x = rect->x; x < rect->x + rect->width; ++x) {
573 *pixel++ = colour;
574 }
575 }
576}
577
578/// Render a glyph.
579/// The glyph is clamped to the surface's bounds.
580static void RenderGlyph(
581 const FontAtlas* atlas, unsigned char c, RenderState* state) {
582 assert(atlas);
583 assert(state);
584 assert(atlas->header.glyph_width <= state->subsurface.width);
585 assert(atlas->header.glyph_height <= state->subsurface.height);
586
587 const int glyph_width = atlas->header.glyph_width;
588 const int glyph_height = atlas->header.glyph_height;
589
590 const unsigned char* glyph = FontGetGlyph(atlas, c);
591
592 for (int y = 0; (y < atlas->header.glyph_height) &&
593 PenInSurface(state, glyph_width - 1, glyph_height - 1);
594 ++y) {
595 for (int x = 0; (x < atlas->header.glyph_width) &&
596 PenInSurface(state, glyph_width - 1, glyph_height - 1);
597 ++x, ++glyph) {
598 uiPixel* pixel = PixelXy(state, x, y);
599 if (*glyph > 0) {
600 pixel->r = *glyph;
601 pixel->g = *glyph;
602 pixel->b = *glyph;
603 pixel->a = 255;
604 }
605 }
606 }
607}
608
609static void RenderText(const char* text, size_t length, RenderState* state) {
610 assert(text);
611 assert(state);
612
613 const FontAtlas* atlas = g_ui.font;
614
615 const int glyph_width = atlas->header.glyph_width;
616 const int glyph_height = atlas->header.glyph_height;
617
618 // Save the x-pen so that we can restore it after rendering the text.
619 const int x0 = state->pen.x;
620
621 // Truncate the text rendering if it exceeds the subsurface's width or height.
622 const char* c = text;
623 for (size_t i = 0;
624 (i < length) && PenInSurface(state, glyph_width - 1, glyph_height - 1);
625 ++i, ++c, state->pen.x += glyph_width) {
626 RenderGlyph(atlas, *c, state);
627 }
628
629 state->pen.x = x0;
630}
631
632static void RenderFrame(const uiFrame* frame, RenderState* state) {
633 assert(frame);
634
635 FillRect(&frame->widget.rect, uiBlack, state);
636}
637
638static void RenderLabel(const uiLabel* label, RenderState* state) {
639 assert(label);
640 assert(state);
641
642 RenderText(string_data(label->text), string_length(label->text), state);
643}
644
645static void RenderTable(const uiTable* table, RenderState* state) {
646 assert(table);
647 assert(state);
648
649 const int x0 = state->pen.x;
650 const int y0 = state->pen.y;
651
652 uiRect original_subsurface = {0};
653 uiPoint original_pen = {0};
654
655 // Render header.
656 if (table->header) {
657 for (int col = 0; col < table->cols; ++col) {
658 // Crop the column contents to the column width so that one column does
659 // not spill into the next.
660 PushSubsurface(
661 state, table->widths[col], state->subsurface.height,
662 &original_subsurface, &original_pen);
663
664 const uiCell* cell = &table->header[col];
665 RenderWidget(state, cell->child);
666
667 // Reset the original subsurface and pen for subsequent columns.
668 PopSubsurface(state, &original_subsurface, &original_pen);
669
670 // Next column.
671 state->pen.x += table->widths[col];
672 }
673 }
674 state->pen.x = x0;
675 state->pen.y += g_ui.font->header.glyph_height;
676
677 // Render rows.
678 for (int row = table->offset;
679 (row < table->rows) && PenInSurface(state, 0, 0); ++row) {
680 for (int col = 0; (col < table->cols) && PenInSurface(state, 0, 0); ++col) {
681 // Crop the column contents to the column width so that one column does
682 // not spill into the next.
683 PushSubsurface(
684 state, table->widths[col], state->subsurface.height,
685 &original_subsurface, &original_pen);
686
687 state->subsurface.x = state->subsurface.x + state->pen.x;
688 state->subsurface.width = table->widths[col];
689 state->pen.x = 0;
690
691 const uiCell* cell = GetCell(table, row, col);
692 RenderWidget(state, cell->child);
693
694 // Reset the original subsurface and pen for subsequent columns.
695 PopSubsurface(state, &original_subsurface, &original_pen);
696
697 // Next column.
698 state->pen.x += table->widths[col];
699 }
700 state->pen.x = x0;
701 state->pen.y += g_ui.font->header.glyph_height;
702 }
703 state->pen.y = y0;
704}
705
706static void RenderWidget(RenderState* state, const uiWidget* widget) {
707 assert(state);
708 assert(widget);
709
710 // Render this widget.
711 switch (widget->type) {
712 case uiTypeButton:
713 break;
714 case uiTypeFrame:
715 RenderFrame((const uiFrame*)widget, state);
716 break;
717 case uiTypeLabel:
718 RenderLabel((const uiLabel*)widget, state);
719 break;
720 case uiTypeTable:
721 RenderTable((const uiTable*)widget, state);
722 break;
723 case uiTypeMax:
724 TRAP();
725 break;
726 }
727
728 // Render children.
729 list_foreach(widget->children, child, { RenderWidget(state, child); });
730}
731
732void uiRender(const uiFrame* frame, uiSurface* surface) {
733 assert(frame);
734 assert(surface);
735
736 RenderWidget(
737 &(RenderState){
738 .surface = *surface,
739 .subsurface =
740 (uiRect){
741 .x = 0,
742 .y = 0,
743 .width = surface->width,
744 .height = surface->height},
745 .pen = {.x = 0, .y = 0},
746 },
747 (const uiWidget*)frame);
748}
749
750// -----------------------------------------------------------------------------
751// UI Events.
752
753static void PushWidgetEvent(uiWidgetEvent* event) {
754 assert(event);
755 assert(g_ui.num_widget_events < MaxWidgetEvents);
756
757 g_ui.widget_events[g_ui.num_widget_events++] = *event;
758}
759
760int uiGetEvents(uiWidgetEvent const** ppWidgetEvents) {
761 assert(ppWidgetEvents);
762
763 const int count = g_ui.num_widget_events;
764 g_ui.num_widget_events = 0;
765
766 *ppWidgetEvents = g_ui.widget_events;
767 return count;
768}
769
770// -----------------------------------------------------------------------------
771// User input.
772
773static bool RectContains(uiRect rect, uiPoint point) {
774 return (rect.x <= point.x) && (point.x <= (rect.x + rect.width)) &&
775 (rect.y <= point.y) && (point.y <= (rect.y + rect.height));
776}
777
778static uiWidget* GetWidgetUnderMouse(uiWidget* parent, uiPoint mouse) {
779 assert(parent);
780
781 // First check the children so that the selection is from "most specific" to
782 // "less specific" from the user's perspective.
783 list_foreach(parent->children, child, {
784 uiWidget* target = GetWidgetUnderMouse(child, mouse);
785 if (target != 0) {
786 return target;
787 }
788 });
789
790 if (RectContains(parent->rect, mouse)) {
791 return parent;
792 }
793
794 return 0;
795}
796
797static void GetTableRowColAtXy(
798 const uiTable* table, uiPoint p, int* out_row, int* out_col) {
799 assert(table);
800 assert(out_row);
801 assert(out_col);
802
803 const uiWidget* widget = (uiWidget*)table;
804
805 int col = -1;
806 int row = -1;
807
808 if (RectContains(widget->rect, p)) {
809 int x = p.x - widget->rect.x;
810 for (col = 0; (col < table->cols) && (x > table->widths[col]); ++col) {
811 x -= table->widths[col];
812 }
813 // 0 is the header and we want to map the first row to 0, so -1.
814 row = table->offset +
815 ((p.y - widget->rect.y) / g_ui.font->header.glyph_height) - 1;
816 // Out-of-bounds check.
817 if ((col >= table->cols) || (row >= table->rows)) {
818 col = row = -1;
819 }
820 }
821
822 *out_col = col;
823 *out_row = row;
824}
825
826static void ClickTable(uiTable* table, const uiMouseClickEvent* event) {
827 assert(table);
828 assert(event);
829
830 int row, col;
831 GetTableRowColAtXy(table, event->mouse_position, &row, &col);
832
833 if ((row != -1) && (col != -1)) {
834 PushWidgetEvent(&(uiWidgetEvent){
835 .type = uiWidgetEventClick,
836 .widget = uiMakeTablePtr(table),
837 .table_click = (uiTableClickEvent){.row = row, .col = col}
838 });
839 }
840}
841
842static void ScrollTable(uiTable* table, const uiMouseScrollEvent* event) {
843 assert(table);
844 assert(event);
845 table->offset = Max(0, table->offset - event->scroll_offset);
846}
847
848static bool ProcessScrollEvent(
849 uiWidget* widget, const uiMouseScrollEvent* event) {
850 assert(widget);
851 assert(event);
852
853 bool processed = false;
854
855 switch (widget->type) {
856 case uiTypeTable:
857 ScrollTable((uiTable*)widget, event);
858 processed = true;
859 break;
860 default:
861 break;
862 }
863
864 return processed;
865}
866
867static bool ProcessClickEvent(
868 uiWidget* widget, const uiMouseClickEvent* event) {
869 assert(widget);
870 assert(event);
871
872 bool processed = false;
873
874 switch (widget->type) {
875 case uiTypeTable:
876 ClickTable((uiTable*)widget, event);
877 processed = true;
878 break;
879 default:
880 break;
881 }
882
883 return processed;
884}
885
886bool uiSendEvent(uiFrame* frame, const uiInputEvent* event) {
887 assert(frame);
888 assert(event);
889
890 uiWidget* widget = (uiWidget*)frame;
891
892 bool processed = false;
893
894 switch (event->type) {
895 case uiEventMouseButton: {
896 const uiMouseButtonEvent* ev = &event->mouse_button;
897
898 uiMouseButtonState* prev_state = &g_ui.mouse_button_state[ev->button];
899
900 if ((*prev_state == uiMouseDown) && (ev->state == uiMouseUp)) {
901 // Click.
902 uiSendEvent(
903 frame,
904 &(uiInputEvent){
905 .type = uiEventMouseClick,
906 .mouse_click = (uiMouseClickEvent){
907 .button = ev->button, .mouse_position = ev->mouse_position}
908 });
909 }
910
911 *prev_state = ev->state;
912 break;
913 }
914 case uiEventMouseClick: {
915 const uiMouseClickEvent* ev = &event->mouse_click;
916 uiWidget* target = GetWidgetUnderMouse(widget, ev->mouse_position);
917 if (target) {
918 processed = ProcessClickEvent(target, ev);
919 }
920 break;
921 }
922 case uiEventMouseScroll: {
923 const uiMouseScrollEvent* ev = &event->mouse_scroll;
924 uiWidget* target = GetWidgetUnderMouse(widget, ev->mouse_position);
925 if (target) {
926 processed = ProcessScrollEvent(target, ev);
927 }
928 break;
929 }
930 }
931
932 return processed;
933}