diff options
author | 3gg <3gg@shellblade.net> | 2024-06-22 13:23:42 -0700 |
---|---|---|
committer | 3gg <3gg@shellblade.net> | 2024-06-22 13:23:42 -0700 |
commit | 2f2d42e28a14cdc856f8cf0c45cd572646be6750 (patch) | |
tree | 441d759cc8a34898aef2d33686b925c0ff27bd23 | |
parent | af641426fad35cd857c1f14bda523db3d85a70cd (diff) |
Table user input.
-rw-r--r-- | include/ui.h | 124 | ||||
-rw-r--r-- | src/ui.c | 329 |
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. | ||
40 | typedef int uiWidgetId; | ||
41 | |||
39 | /// Widget type. | 42 | /// Widget type. |
40 | typedef enum uiWidgetType { | 43 | typedef enum uiWidgetType { |
41 | uiTypeButton, | 44 | uiTypeButton, |
@@ -52,7 +55,7 @@ typedef struct uiTable uiTable; | |||
52 | typedef struct uiWidget uiWidget; | 55 | typedef struct uiWidget uiWidget; |
53 | 56 | ||
54 | /// Widget pointer. | 57 | /// Widget pointer. |
55 | typedef struct uiWidgetPtr { | 58 | typedef 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. | ||
70 | typedef enum uiMouseButton { | ||
71 | uiLMB, | ||
72 | uiRMB, | ||
73 | uiMouseButtonMax, | ||
74 | } uiMouseButton; | ||
75 | |||
76 | /// Mouse button state. | ||
77 | typedef enum uiMouseButtonState { | ||
78 | uiMouseUp, | ||
79 | uiMouseDown, | ||
80 | } uiMouseButtonState; | ||
81 | |||
82 | /// Mouse button event. | ||
83 | typedef struct uiMouseButtonEvent { | ||
84 | uiMouseButton button; | ||
85 | uiMouseButtonState state; | ||
86 | uiPoint mouse_position; | ||
87 | } uiMouseButtonEvent; | ||
88 | |||
89 | /// Mouse click event. | ||
90 | typedef struct uiMouseClickEvent { | ||
91 | uiMouseButton button; | ||
92 | uiPoint mouse_position; | ||
93 | } uiMouseClickEvent; | ||
94 | |||
95 | /// Mouse scroll event. | ||
96 | typedef struct uiMouseScrollEvent { | ||
97 | uiPoint mouse_position; | ||
98 | int scroll_offset; /// Positive = down; negative = up. | ||
99 | } uiMouseScrollEvent; | ||
100 | |||
101 | /// Input event type. | ||
102 | typedef enum uiInputEventType { | ||
103 | uiEventMouseButton, | ||
104 | uiEventMouseClick, | ||
105 | uiEventMouseScroll, | ||
106 | } uiInputEventType; | ||
107 | |||
108 | /// Input event. | ||
109 | typedef 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. | ||
119 | typedef struct uiTableClickEvent { | ||
120 | int col; | ||
121 | int row; | ||
122 | } uiTableClickEvent; | ||
123 | |||
124 | /// UI event type. | ||
125 | typedef enum uiWidgetEventType { | ||
126 | uiWidgetEventClick, | ||
127 | } uiWidgetEventType; | ||
128 | |||
129 | /// UI event. | ||
130 | /// These are events from the UI widgets back to the client application. | ||
131 | typedef 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); | |||
75 | void uiShutdown(void); | 148 | void uiShutdown(void); |
76 | 149 | ||
77 | // ----------------------------------------------------------------------------- | 150 | // ----------------------------------------------------------------------------- |
151 | // Widget pointers. | ||
152 | |||
153 | uiPtr uiMakeButtonPtr(uiButton*); | ||
154 | uiPtr uiMakeFramePtr(uiFrame*); | ||
155 | uiPtr uiMakeLabelPtr(uiLabel*); | ||
156 | uiPtr uiMakeTablePtr(uiTable*); | ||
157 | |||
158 | uiButton* uiGetButtonPtr(uiPtr ptr); | ||
159 | uiFrame* uiGetFramePtr(uiPtr ptr); | ||
160 | uiLabel* uiGetLabelPtr(uiPtr ptr); | ||
161 | uiTable* uiGetTablePtr(uiPtr ptr); | ||
162 | |||
163 | // ----------------------------------------------------------------------------- | ||
78 | // Widget. | 164 | // Widget. |
79 | 165 | ||
80 | uiWidgetPtr uiMakeButtonPtr(uiButton*); | 166 | uiWidgetType uiWidgetGetType(const uiWidget*); |
81 | uiWidgetPtr uiMakeFramePtr(uiFrame*); | ||
82 | uiWidgetPtr uiMakeLabelPtr(uiLabel*); | ||
83 | uiWidgetPtr uiMakeTablePtr(uiTable*); | ||
84 | 167 | ||
85 | void uiWidgetSetParent(uiWidgetPtr child, uiWidgetPtr parent); | 168 | void 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. |
112 | uiLabel* uiMakeLabel(const char* text); | 195 | uiLabel* uiMakeLabel(const char* text); |
113 | 196 | ||
197 | /// Return the label's text. | ||
198 | const char* uiLabelGetText(const uiLabel*); | ||
199 | |||
114 | // ----------------------------------------------------------------------------- | 200 | // ----------------------------------------------------------------------------- |
115 | // Table. | 201 | // Table. |
116 | 202 | ||
117 | /// Create a table. | 203 | /// Create a table. |
118 | uiTable* uiMakeTable(int rows, int cols, const char** header); | 204 | uiTable* uiMakeTable(int rows, int cols, const char** header); |
119 | 205 | ||
206 | /// Clear the table. | ||
207 | /// This clears the contents, but not the header. | ||
208 | void uiTableClear(uiTable*); | ||
209 | |||
120 | /// Add a row. | 210 | /// Add a row. |
121 | void uiTableAddRow(uiTable*, const char** row); | 211 | void uiTableAddRow(uiTable*, const char** row); |
122 | 212 | ||
123 | /// Set the table's cell. | 213 | /// Set the table's cell. |
124 | void uiTableSet(uiTable*, int row, int col, uiWidgetPtr widget); | 214 | void uiTableSet(uiTable*, int row, int col, uiPtr widget); |
125 | 215 | ||
126 | /// Get the table's cell. | 216 | /// Get the table's cell. |
127 | const uiWidget* uiTableGet(const uiTable*, int row, int col); | 217 | const 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. |
136 | void uiRender(const uiFrame*, uiSurface*); | 226 | void 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. | ||
237 | int 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. | ||
244 | bool uiSendEvent(uiFrame*, const uiInputEvent*); | ||
@@ -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 | |||
10 | static void* uiAlloc(size_t count, size_t size) { | 14 | static 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 | ||
68 | typedef struct uiLibrary { | 73 | typedef 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) { | |||
99 | void uiShutdown(void) {} | 107 | void uiShutdown(void) {} |
100 | 108 | ||
101 | // ----------------------------------------------------------------------------- | 109 | // ----------------------------------------------------------------------------- |
102 | // Widget. | 110 | // Widget pointers. |
111 | |||
112 | uiPtr uiMakeButtonPtr(uiButton* button) { | ||
113 | assert(button); | ||
114 | return (uiPtr){.type = uiTypeButton, .button = button}; | ||
115 | } | ||
116 | |||
117 | uiPtr uiMakeFramePtr(uiFrame* frame) { | ||
118 | assert(frame); | ||
119 | return (uiPtr){.type = uiTypeFrame, .frame = frame}; | ||
120 | } | ||
121 | |||
122 | uiPtr uiMakeLabelPtr(uiLabel* label) { | ||
123 | assert(label); | ||
124 | return (uiPtr){.type = uiTypeLabel, .label = label}; | ||
125 | } | ||
126 | |||
127 | uiPtr uiMakeTablePtr(uiTable* table) { | ||
128 | assert(table); | ||
129 | return (uiPtr){.type = uiTypeTable, .table = table}; | ||
130 | } | ||
131 | |||
132 | static uiPtr uiMakeWidgetPtr(uiWidget* widget) { | ||
133 | assert(widget); | ||
134 | return (uiPtr){.type = widget->type, .widget = widget}; | ||
135 | } | ||
103 | 136 | ||
104 | static uiButton* uiGetButtonPtr(uiWidgetPtr ptr) { | 137 | uiButton* 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 | ||
110 | static uiFrame* uiGetFramePtr(uiWidgetPtr ptr) { | 143 | uiFrame* 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 | ||
116 | static uiLabel* uiGetLabelPtr(uiWidgetPtr ptr) { | 149 | uiLabel* 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 | ||
122 | static uiTable* uiGetTablePtr(uiWidgetPtr ptr) { | 155 | uiTable* 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 | |||
164 | uiWidgetType uiWidgetGetType(const uiWidget* widget) { | ||
165 | assert(widget); | ||
166 | return widget->type; | ||
167 | } | ||
168 | |||
128 | static void DestroyWidget(uiWidget** ppWidget) { | 169 | static 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 | ||
138 | uiWidgetPtr uiMakeButtonPtr(uiButton* button) { | 179 | void uiWidgetSetParent(uiPtr child_, uiPtr parent_) { |
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; | 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 | ||
230 | const 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 | ||
227 | static const uiCell* GetCell(const uiTable* table, int row, int col) { | 257 | static 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 | ||
232 | static uiCell* GetCellMut(uiTable* table, int row, int col) { | 262 | static 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 | ||
237 | static uiCell* GetLastRow(uiTable* table) { | 267 | static 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 | ||
243 | uiTable* uiMakeTable(int rows, int cols, const char** header) { | 273 | uiTable* 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 | ||
294 | void 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 | |||
264 | void uiTableAddRow(uiTable* table, const char** row) { | 318 | void 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 | ||
280 | void uiTableSet(uiTable* table, int row, int col, uiWidgetPtr child) { | 337 | void 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 | |||
753 | static 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 | |||
760 | int 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 | |||
773 | static 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 | |||
778 | static 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 | |||
797 | static 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 | |||
826 | static 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 | |||
842 | static 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 | |||
848 | static 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 | |||
867 | static 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 | |||
886 | bool 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 | } | ||