implement grid layout (win32)

Wed, 08 Oct 2025 12:36:16 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Wed, 08 Oct 2025 12:36:16 +0200
changeset 815
7ddf5fb7ec2a
parent 814
bcb3c7d486f3
child 816
f5690594b240

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

mercurial