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 | } | ||
