Wed, 08 Oct 2025 12:36:16 +0200
implement grid layout (win32)
| application/main.c | file | annotate | diff | comparison | revisions | |
| ui/ui/win32.h | file | annotate | diff | comparison | revisions | |
| ui/win32/button.c | file | annotate | diff | comparison | revisions | |
| ui/win32/button.h | file | annotate | diff | comparison | revisions | |
| ui/win32/container.c | file | annotate | diff | comparison | revisions | |
| ui/win32/container.h | file | annotate | diff | comparison | revisions | |
| ui/win32/grid.c | file | annotate | diff | comparison | revisions | |
| ui/win32/grid.h | file | annotate | diff | comparison | revisions | |
| ui/win32/win32.c | file | annotate | diff | comparison | revisions | |
| ui/win32/win32.h | file | annotate | diff | comparison | revisions | |
| ui/win32/window.c | file | annotate | diff | comparison | revisions | |
| ui/win32/window.h | file | annotate | diff | comparison | revisions |
--- a/application/main.c Wed Oct 08 10:41:35 2025 +0200 +++ b/application/main.c Wed Oct 08 12:36:16 2025 +0200 @@ -1125,7 +1125,13 @@ void application_startup(UiEvent *event, void *data) { UiObject *obj = ui_window("Test w32", NULL); - ui_button(obj, .label = "Test"); + ui_button(obj, .label = "Test", .hfill = TRUE, .hexpand = TRUE, .colspan = 3, .margin = 10); + ui_newline(obj); + ui_button(obj, .label = "Test 2-1", .margin_top = 10); + ui_button(obj, .label = "Test 2-2", .hfill = TRUE, .hexpand = TRUE, .margin_top = 20); + ui_button(obj, .label = "Test 2-3", .margin_top = 30); + ui_newline(obj); + ui_button(obj, .label = "Test 3", .colspan = 3, .fill = TRUE); ui_show(obj); }
--- a/ui/ui/win32.h Wed Oct 08 10:41:35 2025 +0200 +++ b/ui/ui/win32.h Wed Oct 08 12:36:16 2025 +0200 @@ -47,6 +47,7 @@ }; struct W32WidgetClass { + void (*eventproc)(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); void (*show)(W32Widget *widget, BOOLEAN show); void (*enable)(W32Widget *widget, BOOLEAN enable); W32Size (*get_preferred_size)(W32Widget *widget);
--- a/ui/win32/button.c Wed Oct 08 10:41:35 2025 +0200 +++ b/ui/win32/button.c Wed Oct 08 12:36:16 2025 +0200 @@ -52,7 +52,7 @@ hInstance, NULL); - W32Widget *widget = w32_widget_create(&button_widget_class, hwnd, sizeof(W32Button)); + W32Widget *widget = w32_widget_create(&button_widget_class, hwnd, sizeof(UiButton)); ui_container_add(container, widget, &layout); return widget;
--- a/ui/win32/button.h Wed Oct 08 10:41:35 2025 +0200 +++ b/ui/win32/button.h Wed Oct 08 12:36:16 2025 +0200 @@ -32,9 +32,9 @@ #include "../ui/button.h" #include "container.h" -typedef struct W32Button { +typedef struct UiButton { W32Widget widget; -} W32Button; +} UiButton; W32Size ui_button_get_preferred_size(W32Widget *widget);
--- a/ui/win32/container.c Wed Oct 08 10:41:35 2025 +0200 +++ b/ui/win32/container.c Wed Oct 08 12:36:16 2025 +0200 @@ -27,8 +27,10 @@ */ #include "container.h" +#include "grid.h" #include "../common/context.h" +#include "../common/container.h" UiContainerPrivate* ui_obj_container(UiObject *obj) { return (UiContainerPrivate*)obj->container_end; @@ -39,7 +41,14 @@ } void ui_container_add(UiContainerPrivate *ctn, W32Widget *widget, UiLayout *layout) { - ctn->add(ctn, widget, layout); + UiLayout layout2 = *layout; + if (layout2.margin > 0) { + layout2.margin_left = layout2.margin; + layout2.margin_right = layout2.margin; + layout2.margin_top = layout2.margin; + layout2.margin_bottom = layout2.margin; + } + ctn->add(ctn, widget, &layout2); ctn->container.newline = FALSE; } @@ -67,13 +76,33 @@ return NULL; } -UiContainerX* ui_grid_container_create(UiObject *obj, HWND hwnd, short padding_top, short padding_bottom, short padding_left, short padding_right) { +UiContainerX* ui_grid_container_create(UiObject *obj, HWND hwnd, short columnspacing, short rowspacing, GridEdgeInsets padding) { UiGridLayoutContainer *container = cxZalloc(obj->ctx->allocator, sizeof(UiGridLayoutContainer)); container->container.hwnd = hwnd; container->container.add = ui_grid_container_add; + container->layout = ui_grid_layout_create(obj->ctx->allocator, hwnd, columnspacing, rowspacing); + container->layout->padding = padding; return (UiContainerX*)container; } void ui_grid_container_add(UiContainerPrivate *ctn, W32Widget *widget, UiLayout *layout) { - // TODO + UiGridLayoutContainer *grid = (UiGridLayoutContainer*)ctn; + if (ctn->container.newline) { + grid->y++; + grid->x = 0; + } + + uic_layout_setup_expand_fill(layout, grid->def_hexpand, grid->def_vexpand, grid->def_hfill, grid->def_vfill); + GridLayoutInfo gridLayout = (GridLayoutInfo) { + .margin = (GridEdgeInsets) { layout->margin_top, layout->margin_bottom, layout->margin_left, layout->margin_right }, + .colspan = layout->colspan, + .rowspan = layout->rowspan, + .hexpand = layout->hexpand, + .vexpand = layout->vexpand, + .hfill = layout->hfill, + .vfill = layout->vfill, + }; + ui_grid_add_widget(grid->layout, grid->x, grid->y, widget, &gridLayout); + + grid->x++; }
--- a/ui/win32/container.h Wed Oct 08 10:41:35 2025 +0200 +++ b/ui/win32/container.h Wed Oct 08 12:36:16 2025 +0200 @@ -72,16 +72,25 @@ struct UiGridLayoutContainer { UiContainerPrivate container; - UiGridLayout layout; + UiGridLayout *layout; int x; int y; + UiBool def_hexpand; + UiBool def_vexpand; + UiBool def_hfill; + UiBool def_vfill; }; UiContainerPrivate* ui_obj_container(UiObject *obj); HWND ui_container_get_parent(UiContainerPrivate *ctn); void ui_container_add(UiContainerPrivate *ctn, W32Widget *widget, UiLayout *layout); -UiContainerX* ui_grid_container_create(UiObject *obj, HWND hwnd, short padding_top, short padding_bottom, short padding_left, short padding_right); +UiContainerX* ui_grid_container_create( + UiObject *obj, + HWND hwnd, + short columnspacing, + short rowspacing, + GridEdgeInsets padding); void ui_grid_container_add(UiContainerPrivate *ctn, W32Widget *widget, UiLayout *layout); #ifdef __cplusplus
--- a/ui/win32/grid.c Wed Oct 08 10:41:35 2025 +0200 +++ b/ui/win32/grid.c Wed Oct 08 12:36:16 2025 +0200 @@ -31,11 +31,13 @@ #include "../../ucx/cx/array_list.h" #include "../common/context.h" -UiGridLayout* ui_grid_container(const CxAllocator *a, HWND control, short padding, short columnspacing, short rowspacing) { +#include <stdio.h> +#include <stdlib.h> + +UiGridLayout* ui_grid_layout_create(const CxAllocator *a, HWND control, short columnspacing, short rowspacing) { UiGridLayout *grid = cxZalloc(a, sizeof(UiGridLayout)); grid->hwnd = control; grid->widgets = cxArrayListCreate(a, NULL, sizeof(GridElm), 32); - grid->padding = padding; grid->columnspacing = columnspacing; grid->rowspacing = rowspacing; return grid; @@ -48,18 +50,249 @@ W32Widget *widget, GridLayoutInfo *layout) { + // add the widget GridElm elm; elm.widget = widget; - elm.posx = 0; - elm.posy = 0; - elm.width = 0; - elm.height = 0; elm.gridx = x; elm.gridy = y; elm.layout = *layout; cxListAdd(grid->widgets, &elm); + + // adjust max col/row count + if (x > grid->max_column) { + grid->max_column = x; + } + if (y > grid->max_row) { + grid->max_row = y; + } } -void ui_grid_layout(UiGridLayout *grid) { - // TODO +void ui_grid_layout(UiGridLayout *grid, int width, int height) { + int ncols = grid->max_column+1; + int nrows = grid->max_row+1; + + GridDef *cols = calloc(ncols, sizeof(GridDef)); + GridDef *rows = calloc(nrows, sizeof(GridDef)); + + int colspacing = grid->columnspacing; + int rowspacing = grid->rowspacing; + + int span_max = 1; + for(int r=0;r<2;r++) { + CxIterator i = cxListIterator(grid->widgets); + cx_foreach(GridElm *, elm, i) { + int x = elm->gridx; + int y = elm->gridy; + GridDef *col = &cols[x]; + GridDef *row = &rows[y]; + + W32Size size = w32_widget_get_preferred_size(elm->widget); + elm->layout.preferred_width = size.width; + elm->layout.preferred_height = size.height; + + int elm_width = size.width + elm->layout.margin.left + elm->layout.margin.right; + if(elm_width > cols[elm->gridx].preferred_size && elm->layout.colspan <= 1 && span_max == 1) { + cols[elm->gridx].preferred_size = elm_width; + } + int elm_height = size.height + elm->layout.margin.top + elm->layout.margin.bottom; + if(elm_height > rows[elm->gridy].preferred_size && elm->layout.rowspan <= 1 && span_max == 1) { + rows[elm->gridy].preferred_size = elm_height; + } + + if(elm->layout.rowspan > span_max || elm->layout.colspan > span_max) { + continue; + } + + int end_col = x+elm->layout.colspan; + if(end_col > ncols) { + end_col = ncols; + } + int end_row = y+elm->layout.rowspan; + if(end_row > nrows) { + end_row = nrows; + } + + // are all columns in the span > preferred_width? + if(elm->layout.colspan > 1) { + int span_width = 0; + GridDef *last_col = col; + for(int c=x;c<end_col;c++) { + span_width += cols[c].size; + last_col = &cols[c]; + } + if(span_width < elm->layout.preferred_width) { + last_col->size += elm->layout.preferred_width - span_width; + } + } + + // are all rows in the span > preferred_height? + if(elm->layout.rowspan > 1) { + int span_height = 0; + GridDef *last_row = row; + for(int c=x;c<end_row;c++) { + span_height += rows[c].size; + last_row = &rows[c]; + } + if(span_height < elm->layout.preferred_height) { + last_row->size += elm->layout.preferred_height - span_height; + } + } + + if(elm->layout.hexpand) { + if(elm->layout.colspan > 1) { + // check if any column in the span is expanding + // if not, make the last column expanding + GridDef *last_col = col; + for(int c=x;c<end_col;c++) { + last_col = &cols[c]; + if(last_col->expand) { + break; + } + } + last_col->expand = TRUE; + } else { + col->expand = TRUE; + } + } + if(elm->layout.vexpand) { + if(elm->layout.rowspan > 1) { + // same as colspan + GridDef *last_row = row; + for(int c=x;c<nrows;c++) { + last_row = &rows[c]; + if(last_row->expand) { + break; + } + } + last_row->expand = TRUE; + } else { + row->expand = TRUE; + } + } + } + span_max = 50000; // not sure if this is unreasonable low or high + } + + int col_ext = 0; + int row_ext = 0; + + int preferred_width = 0; + int preferred_height = 0; + for(int j=0;j<ncols;j++) { + preferred_width += cols[j].preferred_size; + if(cols[j].expand) { + col_ext++; + } + } + for(int j=0;j<nrows;j++) { + preferred_height += rows[j].preferred_size; + if(rows[j].expand) { + row_ext++; + } + } + if(ncols > 0) { + preferred_width += (ncols-1) * colspacing; + } + if(nrows > 0) { + preferred_height += (nrows-1) * rowspacing; + } + + grid->preferred_width = preferred_width; + grid->preferred_height = preferred_height; + + int hremaining = width - preferred_width; + int vremaining = height - preferred_height; + int hext = col_ext > 0 ? hremaining/col_ext : 0; + int vext = row_ext > 0 ? vremaining/row_ext : 0; + + for(int j=0;j<ncols;j++) { + GridDef *col = &cols[j]; + if(col->expand) { + col->size = col->preferred_size + hext; + } else { + col->size = col->preferred_size; + } + } + for(int j=0;j<nrows;j++) { + GridDef *row = &rows[j]; + if(row->expand) { + row->size = row->preferred_size + vext; + } else { + row->size = row->preferred_size; + } + } + + int pos = 0; + for(int j=0;j<ncols;j++) { + cols[j].pos = pos; + pos += cols[j].size + colspacing; + } + pos = 0; + for(int j=0;j<nrows;j++) { + rows[j].pos = pos; + pos += rows[j].size + rowspacing; + } + + CxIterator i = cxListIterator(grid->widgets); + cx_foreach(GridElm *, elm, i) { + GridDef *col = &cols[elm->gridx]; + GridDef *row = &rows[elm->gridy]; + + int child_width = 0; + int child_height = 0; + int child_x = 0; + int child_y = 0; + if(elm->layout.hfill) { + if(elm->layout.colspan > 1) { + int cwidth = 0; + int end_col = elm->gridx + elm->layout.colspan; + if(end_col > ncols) { + end_col = ncols; + } + int real_span = 0; + for(int c=elm->gridx;c<end_col;c++) { + cwidth += cols[c].size; + real_span++; + } + if(real_span > 0) { + cwidth += (real_span-1) * colspacing; + } + child_width = cwidth; + } else { + child_width = col->size; + } + } else { + child_width = elm->layout.preferred_width; + } + child_width -= elm->layout.margin.left + elm->layout.margin.right; + + if(elm->layout.vfill) { + if(elm->layout.rowspan > 1) { + int rheight = 0; + int end_row = elm->gridy + elm->layout.rowspan; + if(end_row > nrows) { + end_row = nrows; + } + int real_span = 0; + for(int r=elm->gridy;r<end_row;r++) { + rheight += rows[r].size; + real_span++; + } + if(real_span > 0) { + rheight += (real_span-1) * rowspacing; + } + child_height = rheight; + } + child_height = row->size; + } else { + child_height = elm->layout.preferred_height; + } + + child_x = col->pos + elm->layout.margin.left; + child_y = row->pos + elm->layout.margin.top; + SetWindowPos(elm->widget->hwnd, NULL, child_x, child_y, child_width, child_height, SWP_NOZORDER); + } + + free(cols); + free(rows); }
--- a/ui/win32/grid.h Wed Oct 08 10:41:35 2025 +0200 +++ b/ui/win32/grid.h Wed Oct 08 12:36:16 2025 +0200 @@ -32,11 +32,18 @@ #include <stdbool.h> #include <cx/array_list.h> +#include "win32.h" + +#define INSETS_ZERO (GridEdgeInsets){0} +typedef struct GridEdgeInsets { + short top; + short bottom; + short left; + short right; +} GridEdgeInsets; + typedef struct GridLayoutInfo { - short margin_left; - short margin_right; - short margin_top; - short margin_bottom; + GridEdgeInsets margin; short colspan; short rowspan; int preferred_width; @@ -49,10 +56,6 @@ typedef struct GridElm { W32Widget *widget; - int posx; - int posy; - int width; - int height; short gridx; short gridy; GridLayoutInfo layout; @@ -61,21 +64,31 @@ typedef struct UiGridLayout { HWND hwnd; - short padding_top; - short padding_bottom; - short padding_left; - short padding_right; + GridEdgeInsets padding; short columnspacing; short rowspacing; + int preferred_width; + int preferred_height; + /* * list element type: GridElm */ CxList *widgets; + int max_column; + int max_row; + } UiGridLayout; -UiGridLayout* ui_grid_container(const CxAllocator *a, HWND control, short padding, short columnspacing, short rowspacing); +typedef struct GridDef { + int size; + int pos; + int preferred_size; + BOOLEAN expand; +} GridDef; + +UiGridLayout* ui_grid_layout_create(const CxAllocator *a, HWND control, short columnspacing, short rowspacing); void ui_grid_add_widget( UiGridLayout *grid, @@ -84,6 +97,6 @@ W32Widget *widget, GridLayoutInfo *layout); -void ui_grid_layout(UiGridLayout *grid); +void ui_grid_layout(UiGridLayout *grid, int width, int height); #endif //GRID_H
--- a/ui/win32/win32.c Wed Oct 08 10:41:35 2025 +0200 +++ b/ui/win32/win32.c Wed Oct 08 12:36:16 2025 +0200 @@ -42,6 +42,13 @@ return w; } +W32Size w32_widget_get_preferred_size(W32Widget *w) { + if (w->wclass->get_preferred_size) { + return w->wclass->get_preferred_size(w); + } + return (W32Size){0,0}; +} + void w32_widget_default_destroy(W32Widget *w) { free(w); }
--- a/ui/win32/win32.h Wed Oct 08 10:41:35 2025 +0200 +++ b/ui/win32/win32.h Wed Oct 08 12:36:16 2025 +0200 @@ -47,6 +47,8 @@ */ void* w32_widget_create(W32WidgetClass *wclass, HWND hwnd, size_t obj_size); +W32Size w32_widget_get_preferred_size(W32Widget *w); + void w32_widget_default_destroy(W32Widget *w); void w32_widget_default_show(W32Widget *w, BOOLEAN show); void w32_widget_default_enable(W32Widget *w, BOOLEAN enable);
--- a/ui/win32/window.c Wed Oct 08 10:41:35 2025 +0200 +++ b/ui/win32/window.c Wed Oct 08 12:36:16 2025 +0200 @@ -41,6 +41,7 @@ #include "win32.h" static W32WidgetClass w32_toplevel_widget_class = { + .eventproc = ui_window_widget_event, .show = ui_window_widget_show, .enable = NULL, .get_preferred_size = NULL, @@ -52,6 +53,10 @@ static const char *mainWindowClass = "UiMainWindow"; LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + W32Widget *widget = (W32Widget*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if (widget && widget->wclass->eventproc) { + widget->wclass->eventproc(widget, hwnd, uMsg, wParam, lParam); + } switch(uMsg) { case WM_DESTROY: PostQuitMessage(0); @@ -98,13 +103,15 @@ UpdateWindow(hwnd); - W32Widget *widget = w32_widget_new(&w32_toplevel_widget_class, hwnd); - obj->widget = widget; - obj->ref = 1; + // TODO: switch to box container + UiContainerX *container = ui_grid_container_create(obj, hwnd, 0, 0, INSETS_ZERO); + uic_object_push_container(obj, container); - // TODO: switch to box container - UiContainerX *container = ui_grid_container_create(obj, hwnd, 0, 0, 0, 0); - uic_object_push_container(obj, container); + UiWindow *widget = w32_widget_create(&w32_toplevel_widget_class, hwnd, sizeof(UiWindow)); + widget->obj = obj; + widget->container = (UiGridLayoutContainer *)container; + obj->widget = (W32Widget*)widget; + obj->ref = 1; return obj; } @@ -113,6 +120,19 @@ return create_window(title, window_data, FALSE); } + +void ui_window_widget_event(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + UiWindow *window = (UiWindow*)widget; + switch (uMsg) { + case WM_SIZE: { + int width = LOWORD(lParam); + int height = HIWORD(lParam); + ui_grid_layout(window->container->layout, width, height); + break; + } + } +} + void ui_window_widget_show(W32Widget *w, BOOLEAN show) { ShowWindow(w->hwnd, show ? SW_SHOWNORMAL : SW_HIDE); }
--- a/ui/win32/window.h Wed Oct 08 10:41:35 2025 +0200 +++ b/ui/win32/window.h Wed Oct 08 12:36:16 2025 +0200 @@ -35,14 +35,21 @@ #include "../common/object.h" #include "toolkit.h" - +#include "container.h" #ifdef __cplusplus extern "C" { #endif +typedef struct UiWindow { + W32Widget widget; + UiObject *obj; + UiGridLayoutContainer *container; +} UiWindow; + void ui_window_init(void); +void ui_window_widget_event(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); void ui_window_widget_show(W32Widget *w, BOOLEAN show); #ifdef __cplusplus