diff options
| author | 3gg <3gg@shellblade.net> | 2026-03-12 15:29:23 -0700 |
|---|---|---|
| committer | 3gg <3gg@shellblade.net> | 2026-03-12 15:29:23 -0700 |
| commit | 92978a10576d52a0f6c9983d3b6afae7c40eff40 (patch) | |
| tree | bf73faed8aa1ecd71b9f61c37a549faf4cd30372 /src/widget | |
| parent | 58c0f40df5947b3933bf7b6564b2ba5dc39fbd92 (diff) | |
Support scrolling by dragging scrollbars
Diffstat (limited to 'src/widget')
| -rw-r--r-- | src/widget/scrollbar.c | 8 | ||||
| -rw-r--r-- | src/widget/table.c | 58 | ||||
| -rw-r--r-- | src/widget/widget.c | 21 | ||||
| -rw-r--r-- | src/widget/widget.h | 49 |
4 files changed, 98 insertions, 38 deletions
diff --git a/src/widget/scrollbar.c b/src/widget/scrollbar.c new file mode 100644 index 0000000..9cece5d --- /dev/null +++ b/src/widget/scrollbar.c | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | #include "widget.h" | ||
| 2 | |||
| 3 | int ScrollbarScroll(uiScrollbar* scrollbar, int y) { | ||
| 4 | assert(scrollbar); | ||
| 5 | scrollbar->handle_y = | ||
| 6 | Max(0, Min(scrollbar->height - scrollbar->handle_height, y)); | ||
| 7 | return scrollbar->handle_y; | ||
| 8 | } | ||
diff --git a/src/widget/table.c b/src/widget/table.c index 76a0413..e7d412e 100644 --- a/src/widget/table.c +++ b/src/widget/table.c | |||
| @@ -1,20 +1,7 @@ | |||
| 1 | #include "widget.h" | 1 | #include "widget.h" |
| 2 | 2 | ||
| 3 | const uiCell* GetCell(const uiTable* table, int row, int col) { | 3 | #define Min(a, b) ((a) < (b) ? (a) : (b)) |
| 4 | assert(table); | 4 | #define Max(a, b) ((a) > (b) ? (a) : (b)) |
| 5 | return &table->cells[row][col]; | ||
| 6 | } | ||
| 7 | |||
| 8 | uiCell* GetCellMut(uiTable* table, int row, int col) { | ||
| 9 | assert(table); | ||
| 10 | return (uiCell*)GetCell(table, row, col); | ||
| 11 | } | ||
| 12 | |||
| 13 | uiCell** GetLastRow(uiTable* table) { | ||
| 14 | assert(table); | ||
| 15 | assert(table->rows > 0); | ||
| 16 | return &table->cells[table->rows - 1]; | ||
| 17 | } | ||
| 18 | 5 | ||
| 19 | uiTable* uiMakeTable(int rows, int cols, const char** header) { | 6 | uiTable* uiMakeTable(int rows, int cols, const char** header) { |
| 20 | uiTable* table = UI_NEW(uiTable); | 7 | uiTable* table = UI_NEW(uiTable); |
| @@ -73,7 +60,7 @@ void uiTableAddRow(uiTable* table, const char** row) { | |||
| 73 | ASSERT(cells); | 60 | ASSERT(cells); |
| 74 | table->cells = cells; | 61 | table->cells = cells; |
| 75 | 62 | ||
| 76 | uiCell** pLastRow = GetLastRow(table); | 63 | uiCell** pLastRow = TableGetLastRow(table); |
| 77 | *pLastRow = calloc(table->cols, sizeof(uiCell)); | 64 | *pLastRow = calloc(table->cols, sizeof(uiCell)); |
| 78 | ASSERT(*pLastRow); | 65 | ASSERT(*pLastRow); |
| 79 | uiCell* lastRow = *pLastRow; | 66 | uiCell* lastRow = *pLastRow; |
| @@ -86,10 +73,45 @@ void uiTableAddRow(uiTable* table, const char** row) { | |||
| 86 | void uiTableSet(uiTable* table, int row, int col, const char* text) { | 73 | void uiTableSet(uiTable* table, int row, int col, const char* text) { |
| 87 | assert(table); | 74 | assert(table); |
| 88 | assert(text); | 75 | assert(text); |
| 89 | GetCellMut(table, row, col)->text = string_new(text); | 76 | TableGetCellMut(table, row, col)->text = string_new(text); |
| 90 | } | 77 | } |
| 91 | 78 | ||
| 92 | const char* uiTableGet(const uiTable* table, int row, int col) { | 79 | const char* uiTableGet(const uiTable* table, int row, int col) { |
| 93 | assert(table); | 80 | assert(table); |
| 94 | return string_data(GetCell(table, row, col)->text); | 81 | return string_data(TableGetCell(table, row, col)->text); |
| 82 | } | ||
| 83 | |||
| 84 | void uiTableScroll(uiTable* table, int row) { | ||
| 85 | assert(table); | ||
| 86 | table->offset = Min(table->rows - table->num_visible_rows, Max(0, row)); | ||
| 87 | SyncScrollbarToTable(table); | ||
| 88 | } | ||
| 89 | |||
| 90 | void SyncScrollbarToTable(uiTable* table) { | ||
| 91 | assert(table); | ||
| 92 | ScrollbarScroll( | ||
| 93 | &table->scrollbar, (int)((double)table->offset / (double)table->rows * | ||
| 94 | (double)table->height)); | ||
| 95 | } | ||
| 96 | |||
| 97 | void SyncTableToScrollbar(uiTable* table) { | ||
| 98 | assert(table); | ||
| 99 | table->offset = (int)((double)table->scrollbar.handle_y / | ||
| 100 | (double)table->height * (double)table->rows); | ||
| 101 | } | ||
| 102 | |||
| 103 | const uiCell* TableGetCell(const uiTable* table, int row, int col) { | ||
| 104 | assert(table); | ||
| 105 | return &table->cells[row][col]; | ||
| 106 | } | ||
| 107 | |||
| 108 | uiCell* TableGetCellMut(uiTable* table, int row, int col) { | ||
| 109 | assert(table); | ||
| 110 | return (uiCell*)TableGetCell(table, row, col); | ||
| 111 | } | ||
| 112 | |||
| 113 | uiCell** TableGetLastRow(uiTable* table) { | ||
| 114 | assert(table); | ||
| 115 | assert(table->rows > 0); | ||
| 116 | return &table->cells[table->rows - 1]; | ||
| 95 | } | 117 | } |
diff --git a/src/widget/widget.c b/src/widget/widget.c index ef79ac4..ebcaf10 100644 --- a/src/widget/widget.c +++ b/src/widget/widget.c | |||
| @@ -63,11 +63,28 @@ uiPtr uiMakeTablePtr(uiTable* table) { | |||
| 63 | return (uiPtr){.type = uiTypeTable, .table = table}; | 63 | return (uiPtr){.type = uiTypeTable, .table = table}; |
| 64 | } | 64 | } |
| 65 | 65 | ||
| 66 | static uiPtr uiMakeWidgetPtr(uiWidget* widget) { | 66 | uiPtr uiMakeWidgetPtr(uiWidget* widget) { |
| 67 | assert(widget); | 67 | assert(widget); |
| 68 | return (uiPtr){.type = widget->type, .widget = widget}; | 68 | switch (widget->type) { |
| 69 | case uiTypeButton: | ||
| 70 | return uiMakeButtonPtr((uiButton*)widget); | ||
| 71 | case uiTypeFrame: | ||
| 72 | return uiMakeFramePtr((uiFrame*)widget); | ||
| 73 | case uiTypeLabel: | ||
| 74 | return uiMakeLabelPtr((uiLabel*)widget); | ||
| 75 | case uiTypeTable: | ||
| 76 | return uiMakeTablePtr((uiTable*)widget); | ||
| 77 | default: | ||
| 78 | ASSERT(false); | ||
| 79 | break; | ||
| 80 | } | ||
| 81 | return (uiPtr){0}; | ||
| 69 | } | 82 | } |
| 70 | 83 | ||
| 84 | uiPtr uiNullptr(void) { return (uiPtr){0}; } | ||
| 85 | |||
| 86 | bool uiIsNullptr(uiPtr ptr) { return ptr.widget == 0; } | ||
| 87 | |||
| 71 | uiButton* uiGetButtonPtr(uiPtr ptr) { | 88 | uiButton* uiGetButtonPtr(uiPtr ptr) { |
| 72 | assert(ptr.type == uiTypeButton); | 89 | assert(ptr.type == uiTypeButton); |
| 73 | assert(ptr.button); | 90 | assert(ptr.button); |
diff --git a/src/widget/widget.h b/src/widget/widget.h index 63f3d94..db11164 100644 --- a/src/widget/widget.h +++ b/src/widget/widget.h | |||
| @@ -16,39 +16,42 @@ typedef struct uiWidget { | |||
| 16 | Widget_list children; | 16 | Widget_list children; |
| 17 | } uiWidget; | 17 | } uiWidget; |
| 18 | 18 | ||
| 19 | /// Button. | ||
| 20 | typedef struct uiButton { | 19 | typedef struct uiButton { |
| 21 | uiWidget widget; | 20 | uiWidget widget; |
| 22 | string text; | 21 | string text; |
| 23 | } uiButton; | 22 | } uiButton; |
| 24 | 23 | ||
| 25 | /// Frame. | ||
| 26 | typedef struct uiFrame { | 24 | typedef struct uiFrame { |
| 27 | uiWidget widget; | 25 | uiWidget widget; |
| 28 | } uiFrame; | 26 | } uiFrame; |
| 29 | 27 | ||
| 30 | /// Label. | ||
| 31 | typedef struct uiLabel { | 28 | typedef struct uiLabel { |
| 32 | uiWidget widget; | 29 | uiWidget widget; |
| 33 | string text; | 30 | string text; |
| 34 | } uiLabel; | 31 | } uiLabel; |
| 35 | 32 | ||
| 36 | /// Table cell. | 33 | typedef struct uiScrollbar { |
| 34 | int width; | ||
| 35 | int height; // Total height: handle plus scrollable area. | ||
| 36 | int handle_height; // Height of the scroll handle. | ||
| 37 | int handle_y; // Starting y-coordinate of the handle. | ||
| 38 | } uiScrollbar; | ||
| 39 | |||
| 37 | typedef struct uiCell { | 40 | typedef struct uiCell { |
| 38 | string text; | 41 | string text; |
| 39 | } uiCell; | 42 | } uiCell; |
| 40 | 43 | ||
| 41 | /// Table. | ||
| 42 | typedef struct uiTable { | 44 | typedef struct uiTable { |
| 43 | uiWidget widget; | 45 | uiWidget widget; |
| 44 | int rows; | 46 | int rows; |
| 45 | int cols; | 47 | int cols; |
| 46 | int height; // Height in pixels. | 48 | int height; // Height in pixels. |
| 47 | int* widths; // Width, in pixels, for each column. | 49 | int* widths; // Width, in pixels, for each column. |
| 48 | uiCell* header; // If non-null, row of 'cols' header cells. | 50 | uiCell* header; // If non-null, row of 'cols' header cells. |
| 49 | uiCell** cells; // Array of 'rows' rows, each of 'cols' cells. | 51 | uiCell** cells; // Array of 'rows' rows, each of 'cols' cells. |
| 50 | int offset; // Offset into the rows of the table. Units: rows. | 52 | int offset; // Offset into the rows of the table. Units: rows. |
| 51 | int num_visible_rows; // The number of rows that are visible at once. | 53 | int num_visible_rows; // The number of rows that are visible at once. |
| 54 | uiScrollbar scrollbar; | ||
| 52 | struct { | 55 | struct { |
| 53 | bool vertical_overflow : 1; // True if contents overflow vertically. | 56 | bool vertical_overflow : 1; // True if contents overflow vertically. |
| 54 | } flags; | 57 | } flags; |
| @@ -56,14 +59,24 @@ typedef struct uiTable { | |||
| 56 | 59 | ||
| 57 | void DestroyWidget(uiWidget** ppWidget); | 60 | void DestroyWidget(uiWidget** ppWidget); |
| 58 | 61 | ||
| 59 | const uiCell* GetCell(const uiTable* table, int row, int col); | 62 | /// Set the scrollbar handle's y-coordinate, which is clipped to the scrollbar's |
| 60 | uiCell* GetCellMut(uiTable* table, int row, int col); | 63 | /// rectangle. |
| 61 | uiCell** GetLastRow(uiTable* table); | 64 | /// |
| 65 | /// Return the handle's y-coordinate after clipping. | ||
| 66 | int ScrollbarScroll(uiScrollbar*, int y); | ||
| 62 | 67 | ||
| 63 | #define UI_NEW(TYPE) (TYPE*)uiAlloc(1, sizeof(TYPE)) | 68 | const uiCell* TableGetCell(const uiTable*, int row, int col); |
| 69 | uiCell* TableGetCellMut(uiTable*, int row, int col); | ||
| 70 | uiCell** TableGetLastRow(uiTable*); | ||
| 71 | void SyncScrollbarToTable(uiTable*); | ||
| 72 | void SyncTableToScrollbar(uiTable*); | ||
| 73 | |||
| 74 | static inline int Min(int a, int b) { return a < b ? a : b; } | ||
| 75 | static inline int Max(int a, int b) { return a > b ? a : b; } | ||
| 64 | 76 | ||
| 65 | static inline void* uiAlloc(size_t count, size_t size) { | 77 | static inline void* uiAlloc(size_t count, size_t size) { |
| 66 | void* mem = calloc(count, size); | 78 | void* mem = calloc(count, size); |
| 67 | ASSERT(mem); | 79 | ASSERT(mem); |
| 68 | return mem; | 80 | return mem; |
| 69 | } | 81 | } |
| 82 | #define UI_NEW(TYPE) (TYPE*)uiAlloc(1, sizeof(TYPE)) | ||
