summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/ui.h124
-rw-r--r--src/ui.c329
2 files changed, 402 insertions, 51 deletions
diff --git a/include/ui.h b/include/ui.h
index 43bb2e7..8570552 100644
--- a/include/ui.h
+++ b/include/ui.h
@@ -36,6 +36,9 @@ typedef struct uiPoint {
36 int y; 36 int y;
37} uiPoint; 37} uiPoint;
38 38
39/// Widget ID.
40typedef int uiWidgetId;
41
39/// Widget type. 42/// Widget type.
40typedef enum uiWidgetType { 43typedef enum uiWidgetType {
41 uiTypeButton, 44 uiTypeButton,
@@ -52,7 +55,7 @@ typedef struct uiTable uiTable;
52typedef struct uiWidget uiWidget; 55typedef struct uiWidget uiWidget;
53 56
54/// Widget pointer. 57/// Widget pointer.
55typedef struct uiWidgetPtr { 58typedef struct uiPtr {
56 uiWidgetType type; 59 uiWidgetType type;
57 union { 60 union {
58 uiButton* button; 61 uiButton* button;
@@ -61,7 +64,77 @@ typedef struct uiWidgetPtr {
61 uiTable* table; 64 uiTable* table;
62 uiWidget* widget; 65 uiWidget* widget;
63 }; 66 };
64} uiWidgetPtr; 67} uiPtr;
68
69/// Mouse button.
70typedef enum uiMouseButton {
71 uiLMB,
72 uiRMB,
73 uiMouseButtonMax,
74} uiMouseButton;
75
76/// Mouse button state.
77typedef enum uiMouseButtonState {
78 uiMouseUp,
79 uiMouseDown,
80} uiMouseButtonState;
81
82/// Mouse button event.
83typedef struct uiMouseButtonEvent {
84 uiMouseButton button;
85 uiMouseButtonState state;
86 uiPoint mouse_position;
87} uiMouseButtonEvent;
88
89/// Mouse click event.
90typedef struct uiMouseClickEvent {
91 uiMouseButton button;
92 uiPoint mouse_position;
93} uiMouseClickEvent;
94
95/// Mouse scroll event.
96typedef struct uiMouseScrollEvent {
97 uiPoint mouse_position;
98 int scroll_offset; /// Positive = down; negative = up.
99} uiMouseScrollEvent;
100
101/// Input event type.
102typedef enum uiInputEventType {
103 uiEventMouseButton,
104 uiEventMouseClick,
105 uiEventMouseScroll,
106} uiInputEventType;
107
108/// Input event.
109typedef struct uiInputEvent {
110 uiInputEventType type;
111 union {
112 uiMouseButtonEvent mouse_button;
113 uiMouseClickEvent mouse_click;
114 uiMouseScrollEvent mouse_scroll;
115 };
116} uiInputEvent;
117
118/// Table click event.
119typedef struct uiTableClickEvent {
120 int col;
121 int row;
122} uiTableClickEvent;
123
124/// UI event type.
125typedef enum uiWidgetEventType {
126 uiWidgetEventClick,
127} uiWidgetEventType;
128
129/// UI event.
130/// These are events from the UI widgets back to the client application.
131typedef struct uiWidgetEvent {
132 uiWidgetEventType type;
133 uiPtr widget;
134 union {
135 uiTableClickEvent table_click;
136 };
137} uiWidgetEvent;
65 138
66// ----------------------------------------------------------------------------- 139// -----------------------------------------------------------------------------
67// Library. 140// Library.
@@ -75,14 +148,24 @@ bool uiInit(void);
75void uiShutdown(void); 148void uiShutdown(void);
76 149
77// ----------------------------------------------------------------------------- 150// -----------------------------------------------------------------------------
151// Widget pointers.
152
153uiPtr uiMakeButtonPtr(uiButton*);
154uiPtr uiMakeFramePtr(uiFrame*);
155uiPtr uiMakeLabelPtr(uiLabel*);
156uiPtr uiMakeTablePtr(uiTable*);
157
158uiButton* uiGetButtonPtr(uiPtr ptr);
159uiFrame* uiGetFramePtr(uiPtr ptr);
160uiLabel* uiGetLabelPtr(uiPtr ptr);
161uiTable* uiGetTablePtr(uiPtr ptr);
162
163// -----------------------------------------------------------------------------
78// Widget. 164// Widget.
79 165
80uiWidgetPtr uiMakeButtonPtr(uiButton*); 166uiWidgetType uiWidgetGetType(const uiWidget*);
81uiWidgetPtr uiMakeFramePtr(uiFrame*);
82uiWidgetPtr uiMakeLabelPtr(uiLabel*);
83uiWidgetPtr uiMakeTablePtr(uiTable*);
84 167
85void uiWidgetSetParent(uiWidgetPtr child, uiWidgetPtr parent); 168void uiWidgetSetParent(uiPtr child, uiPtr parent);
86 169
87// ----------------------------------------------------------------------------- 170// -----------------------------------------------------------------------------
88// Button. 171// Button.
@@ -111,17 +194,24 @@ uiSize uiGetFrameSize(const uiFrame*);
111/// Create a label. 194/// Create a label.
112uiLabel* uiMakeLabel(const char* text); 195uiLabel* uiMakeLabel(const char* text);
113 196
197/// Return the label's text.
198const char* uiLabelGetText(const uiLabel*);
199
114// ----------------------------------------------------------------------------- 200// -----------------------------------------------------------------------------
115// Table. 201// Table.
116 202
117/// Create a table. 203/// Create a table.
118uiTable* uiMakeTable(int rows, int cols, const char** header); 204uiTable* uiMakeTable(int rows, int cols, const char** header);
119 205
206/// Clear the table.
207/// This clears the contents, but not the header.
208void uiTableClear(uiTable*);
209
120/// Add a row. 210/// Add a row.
121void uiTableAddRow(uiTable*, const char** row); 211void uiTableAddRow(uiTable*, const char** row);
122 212
123/// Set the table's cell. 213/// Set the table's cell.
124void uiTableSet(uiTable*, int row, int col, uiWidgetPtr widget); 214void uiTableSet(uiTable*, int row, int col, uiPtr widget);
125 215
126/// Get the table's cell. 216/// Get the table's cell.
127const uiWidget* uiTableGet(const uiTable*, int row, int col); 217const uiWidget* uiTableGet(const uiTable*, int row, int col);
@@ -134,3 +224,21 @@ uiWidget* uiTableGetMut(uiTable*, int row, int col);
134 224
135/// Render the frame. 225/// Render the frame.
136void uiRender(const uiFrame*, uiSurface*); 226void uiRender(const uiFrame*, uiSurface*);
227
228// -----------------------------------------------------------------------------
229// UI Events.
230
231/// Get the widget events.
232/// Return the number of events in the returned array.
233///
234/// This function clears the events recorded by the UI library since the last
235/// input event. Subsequent calls to this function, with no further user input,
236/// therefore report zero widget events.
237int uiGetEvents(uiWidgetEvent const**);
238
239// -----------------------------------------------------------------------------
240// User input.
241
242/// Send an input event to the UI.
243/// Return true if the UI requires a redraw.
244bool uiSendEvent(uiFrame*, const uiInputEvent*);
diff --git a/src/ui.c b/src/ui.c
index a5ab8d3..e8c8ee2 100644
--- a/src/ui.c
+++ b/src/ui.c
@@ -7,6 +7,10 @@
7 7
8#include <stdlib.h> 8#include <stdlib.h>
9 9
10#define Max(a, b) ((a) > (b) ? (a) : (b))
11
12#define MaxWidgetEvents 8
13
10static void* uiAlloc(size_t count, size_t size) { 14static void* uiAlloc(size_t count, size_t size) {
11 void* mem = calloc(count, size); 15 void* mem = calloc(count, size);
12 ASSERT(mem); 16 ASSERT(mem);
@@ -60,13 +64,17 @@ typedef struct uiTable {
60 uiWidget widget; 64 uiWidget widget;
61 int rows; 65 int rows;
62 int cols; 66 int cols;
63 int* widths; /// Width, in pixels, for each each column. 67 int* widths; // Width, in pixels, for each column.
64 uiCell* header; /// If non-null, row of 'cols' header cells. 68 uiCell* header; // If non-null, row of 'cols' header cells.
65 uiCell* cells; /// Array of 'rows * cols' cells. 69 uiCell** cells; // Array of 'rows' rows, each of 'cols' cells.
70 int offset; // Offset into the rows of the table. Units: rows.
66} uiTable; 71} uiTable;
67 72
68typedef struct uiLibrary { 73typedef struct uiLibrary {
69 FontAtlas* font; 74 FontAtlas* font;
75 uiMouseButtonState mouse_button_state[uiMouseButtonMax];
76 uiWidgetEvent widget_events[MaxWidgetEvents];
77 int num_widget_events;
70} uiLibrary; 78} uiLibrary;
71 79
72// ----------------------------------------------------------------------------- 80// -----------------------------------------------------------------------------
@@ -99,32 +107,65 @@ bool uiInit(void) {
99void uiShutdown(void) {} 107void uiShutdown(void) {}
100 108
101// ----------------------------------------------------------------------------- 109// -----------------------------------------------------------------------------
102// Widget. 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}
103 136
104static uiButton* uiGetButtonPtr(uiWidgetPtr ptr) { 137uiButton* uiGetButtonPtr(uiPtr ptr) {
105 assert(ptr.type == uiTypeButton); 138 assert(ptr.type == uiTypeButton);
106 assert(ptr.button); 139 assert(ptr.button);
107 return ptr.button; 140 return ptr.button;
108} 141}
109 142
110static uiFrame* uiGetFramePtr(uiWidgetPtr ptr) { 143uiFrame* uiGetFramePtr(uiPtr ptr) {
111 assert(ptr.type == uiTypeFrame); 144 assert(ptr.type == uiTypeFrame);
112 assert(ptr.frame); 145 assert(ptr.frame);
113 return ptr.frame; 146 return ptr.frame;
114} 147}
115 148
116static uiLabel* uiGetLabelPtr(uiWidgetPtr ptr) { 149uiLabel* uiGetLabelPtr(uiPtr ptr) {
117 assert(ptr.type == uiTypeLabel); 150 assert(ptr.type == uiTypeLabel);
118 assert(ptr.label); 151 assert(ptr.label);
119 return ptr.label; 152 return ptr.label;
120} 153}
121 154
122static uiTable* uiGetTablePtr(uiWidgetPtr ptr) { 155uiTable* uiGetTablePtr(uiPtr ptr) {
123 assert(ptr.type == uiTypeTable); 156 assert(ptr.type == uiTypeTable);
124 assert(ptr.table); 157 assert(ptr.table);
125 return ptr.table; 158 return ptr.table;
126} 159}
127 160
161// -----------------------------------------------------------------------------
162// Widget.
163
164uiWidgetType uiWidgetGetType(const uiWidget* widget) {
165 assert(widget);
166 return widget->type;
167}
168
128static void DestroyWidget(uiWidget** ppWidget) { 169static void DestroyWidget(uiWidget** ppWidget) {
129 assert(ppWidget); 170 assert(ppWidget);
130 171
@@ -135,27 +176,7 @@ static void DestroyWidget(uiWidget** ppWidget) {
135 UI_DEL(ppWidget); 176 UI_DEL(ppWidget);
136} 177}
137 178
138uiWidgetPtr uiMakeButtonPtr(uiButton* button) { 179void uiWidgetSetParent(uiPtr child_, uiPtr parent_) {
139 assert(button);
140 return (uiWidgetPtr){.type = uiTypeButton, .button = button};
141}
142
143uiWidgetPtr uiMakeFramePtr(uiFrame* frame) {
144 assert(frame);
145 return (uiWidgetPtr){.type = uiTypeFrame, .frame = frame};
146}
147
148uiWidgetPtr uiMakeLabelPtr(uiLabel* label) {
149 assert(label);
150 return (uiWidgetPtr){.type = uiTypeLabel, .label = label};
151}
152
153uiWidgetPtr uiMakeTablePtr(uiTable* table) {
154 assert(table);
155 return (uiWidgetPtr){.type = uiTypeTable, .table = table};
156}
157
158void uiWidgetSetParent(uiWidgetPtr child_, uiWidgetPtr parent_) {
159 uiWidget* child = child_.widget; 180 uiWidget* child = child_.widget;
160 uiWidget* parent = parent_.widget; 181 uiWidget* parent = parent_.widget;
161 182
@@ -196,12 +217,21 @@ uiLabel* uiMakeLabel(const char* text) {
196 .widget = 217 .widget =
197 (uiWidget){ 218 (uiWidget){
198 .type = uiTypeLabel, 219 .type = uiTypeLabel,
199 }, 220 .rect =
221 (uiRect){
222 .width =
223 (int)strlen(text) * g_ui.font->header.glyph_width,
224 .height = g_ui.font->header.glyph_height}},
200 .text = string_new(text), 225 .text = string_new(text),
201 }; 226 };
202 return label; 227 return label;
203} 228}
204 229
230const char* uiLabelGetText(const uiLabel* label) {
231 assert(label);
232 return string_data(label->text);
233}
234
205// ----------------------------------------------------------------------------- 235// -----------------------------------------------------------------------------
206// Frame. 236// Frame.
207 237
@@ -226,7 +256,7 @@ uiSize uiGetFrameSize(const uiFrame* frame) {
226 256
227static const uiCell* GetCell(const uiTable* table, int row, int col) { 257static const uiCell* GetCell(const uiTable* table, int row, int col) {
228 assert(table); 258 assert(table);
229 return table->cells + (row * table->cols) + col; 259 return &table->cells[row][col];
230} 260}
231 261
232static uiCell* GetCellMut(uiTable* table, int row, int col) { 262static uiCell* GetCellMut(uiTable* table, int row, int col) {
@@ -234,10 +264,10 @@ static uiCell* GetCellMut(uiTable* table, int row, int col) {
234 return (uiCell*)GetCell(table, row, col); 264 return (uiCell*)GetCell(table, row, col);
235} 265}
236 266
237static uiCell* GetLastRow(uiTable* table) { 267static uiCell** GetLastRow(uiTable* table) {
238 assert(table); 268 assert(table);
239 assert(table->rows > 0); 269 assert(table->rows > 0);
240 return &table->cells[table->cols * (table->rows - 1)]; 270 return &table->cells[table->rows - 1];
241} 271}
242 272
243uiTable* uiMakeTable(int rows, int cols, const char** header) { 273uiTable* uiMakeTable(int rows, int cols, const char** header) {
@@ -249,7 +279,7 @@ uiTable* uiMakeTable(int rows, int cols, const char** header) {
249 .cols = cols, 279 .cols = cols,
250 .widths = (cols > 0) ? calloc(cols, sizeof(int)) : 0, 280 .widths = (cols > 0) ? calloc(cols, sizeof(int)) : 0,
251 .header = header ? calloc(cols, sizeof(uiCell)) : 0, 281 .header = header ? calloc(cols, sizeof(uiCell)) : 0,
252 .cells = (rows * cols > 0) ? calloc(rows * cols, sizeof(uiCell)) : 0, 282 .cells = (rows * cols > 0) ? calloc(rows, sizeof(uiCell*)) : 0,
253 }; 283 };
254 284
255 if (header) { 285 if (header) {
@@ -261,23 +291,50 @@ uiTable* uiMakeTable(int rows, int cols, const char** header) {
261 return table; 291 return table;
262} 292}
263 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
264void uiTableAddRow(uiTable* table, const char** row) { 318void uiTableAddRow(uiTable* table, const char** row) {
265 assert(table); 319 assert(table);
266 320
267 table->rows++; 321 table->rows++;
268 322
269 uiCell* cells = 323 uiCell** cells = realloc(table->cells, table->rows * sizeof(uiCell*));
270 realloc(table->cells, table->rows * table->cols * sizeof(uiCell)); 324 ASSERT(cells);
271 assert(cells);
272 table->cells = cells; 325 table->cells = cells;
273 326
274 uiCell* cell = GetLastRow(table); 327 uiCell** pLastRow = GetLastRow(table);
275 for (int col = 0; col < table->cols; ++col, ++cell) { 328 *pLastRow = calloc(table->cols, sizeof(uiCell));
276 cell->child = (uiWidget*)uiMakeLabel(row[col]); 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]);
277 } 334 }
278} 335}
279 336
280void uiTableSet(uiTable* table, int row, int col, uiWidgetPtr child) { 337void uiTableSet(uiTable* table, int row, int col, uiPtr child) {
281 assert(table); 338 assert(table);
282 assert(child.widget); 339 assert(child.widget);
283 340
@@ -618,7 +675,8 @@ static void RenderTable(const uiTable* table, RenderState* state) {
618 state->pen.y += g_ui.font->header.glyph_height; 675 state->pen.y += g_ui.font->header.glyph_height;
619 676
620 // Render rows. 677 // Render rows.
621 for (int row = 0; (row < table->rows) && PenInSurface(state, 0, 0); ++row) { 678 for (int row = table->offset;
679 (row < table->rows) && PenInSurface(state, 0, 0); ++row) {
622 for (int col = 0; (col < table->cols) && PenInSurface(state, 0, 0); ++col) { 680 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 681 // Crop the column contents to the column width so that one column does
624 // not spill into the next. 682 // not spill into the next.
@@ -688,3 +746,188 @@ void uiRender(const uiFrame* frame, uiSurface* surface) {
688 }, 746 },
689 (const uiWidget*)frame); 747 (const uiWidget*)frame);
690} 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}