merge

Fri, 10 Oct 2025 09:06:06 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Fri, 10 Oct 2025 09:06:06 +0200
changeset 827
eae5b817aa47
parent 826
e596cfc1ca46 (current diff)
parent 825
1bac7e45712b (diff)
child 828
a952337ae325

merge

--- a/.hgignore	Fri Oct 10 09:05:11 2025 +0200
+++ b/.hgignore	Fri Oct 10 09:06:06 2025 +0200
@@ -11,3 +11,4 @@
 relre:.qmake.stash$
 relre:^.idea/.*
 relre:^compile_commands.json$
+relre:^application/app.res$
--- a/application/Makefile	Fri Oct 10 09:05:11 2025 +0200
+++ b/application/Makefile	Fri Oct 10 09:06:06 2025 +0200
@@ -39,8 +39,10 @@
 
 all: $(APP_BIN)
 
-$(APP_BIN): $(OBJ) $(BUILD_ROOT)/build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)uitk$(LIB_EXT)
-	$(LD) -o $(APP_BIN) $(OBJ) $(BUILD_ROOT)/build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)uitk$(LIB_EXT) $(BUILD_ROOT)/build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)ucx$(LIB_EXT) $(LDFLAGS) $(TK_LDFLAGS)
+include $(SYS_MAKEFILE)
+
+$(APP_BIN): $(OBJ) $(RES_FILE) $(BUILD_ROOT)/build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)uitk$(LIB_EXT)
+	$(LD) -o $(APP_BIN) $(OBJ) $(RES_FILE) $(BUILD_ROOT)/build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)uitk$(LIB_EXT) $(BUILD_ROOT)/build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)ucx$(LIB_EXT) $(LDFLAGS) $(TK_LDFLAGS)
 
 ../build/application/%$(OBJ_EXT): %.c
 	$(CC) $(CFLAGS) $(TK_CFLAGS) -o $@ -c $<
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/application/Makefile.win32	Fri Oct 10 09:06:06 2025 +0200
@@ -0,0 +1,5 @@
+RES_FILE = app.res
+
+$(RES_FILE): app.rc app.manifest
+	llvm-rc app.rc
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/application/app.manifest	Fri Oct 10 09:06:06 2025 +0200
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+  <dependency>
+    <dependentAssembly>
+      <assemblyIdentity
+        type="win32"
+        name="Microsoft.Windows.Common-Controls"
+        version="6.0.0.0"
+        processorArchitecture="*"
+        publicKeyToken="6595b64144ccf1df"
+        language="*"
+      />
+    </dependentAssembly>
+  </dependency>
+</assembly>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/application/app.rc	Fri Oct 10 09:06:06 2025 +0200
@@ -0,0 +1,1 @@
+1 24 "app.manifest"
--- a/application/main.c	Fri Oct 10 09:05:11 2025 +0200
+++ b/application/main.c	Fri Oct 10 09:06:06 2025 +0200
@@ -470,6 +470,7 @@
         case 6: return (void*)(intptr_t)123;
         case 7: return "edit me";
         case 8: return "edit me too";
+        case 9: return (void*)(intptr_t)1;
     }
     return NULL;
 }
@@ -672,7 +673,7 @@
             }
         }
         ui_tab(obj, "Tab 1") {
-            UiModel *model = ui_model(obj->ctx, UI_ICON_TEXT, "col1", UI_INTEGER, "col2", UI_ICON, "col3", UI_ICON_TEXT, "col4", UI_INTEGER, "col5", UI_STRING_EDITABLE, "edit6", UI_STRING_EDITABLE, "edit7", -1);
+            UiModel *model = ui_model(obj->ctx, UI_ICON_TEXT, "col1", UI_INTEGER, "col2", UI_ICON, "col3", UI_ICON_TEXT, "col4", UI_INTEGER, "col5", UI_STRING_EDITABLE, "edit6", UI_STRING_EDITABLE, "edit7", UI_BOOL_EDITABLE, "Check", -1);
             model->columnsize[0] = -1;
             ui_table(obj, .model = model, .list = doc->list2, .colspan = 2, .fill = TRUE, .contextmenu = menubuilder, .multiselection = TRUE, .fill = TRUE,
                     .getvalue = table_getvalue, .getstyle = table_getstyle, .onsave = list_save,
@@ -1075,6 +1076,17 @@
             ui_radiobutton(obj, .label = "Radio 3", .varname = "radio");
             ui_radiobutton(obj, .label = "Radio 4", .varname = "radio");
         }
+        ui_newline(obj);
+        
+        ui_hbox(obj, .colspan = 3) {
+            ui_button(obj, .label = "Margin Test 1", .margin_top = 10);
+            ui_button(obj, .label = "Margin Test 2", .margin_top = 20);
+            UIWIDGET w = ui_button(obj, .label = "Margin Test 3", .margin = 30);
+            //ui_set_visible(w, FALSE);
+            ui_button(obj, .label = "Margin Test 4", .margin_top = 40);
+            ui_button(obj, .label = "Margin Test 5", .margin_top = 50);
+            ui_button(obj, .label = "Margin Test 6", .margin_top = 60);
+        }
     }
     ui_show(obj);
 }
@@ -1123,9 +1135,18 @@
 
 #ifdef UI_WIN32
 
+static void action_button(UiEvent *event, void *data) {
+    printf("button clicked\n");
+}
+
 void application_startup(UiEvent *event, void *data) {
 	UiObject *obj = ui_window("Test w32", NULL);
-	
+    ui_button(obj, .label = "Test", .hfill = TRUE, .hexpand = TRUE, .colspan = 3, .margin = 10);
+    ui_button(obj, .label = "Test 2-1", .margin_left = 10);
+    ui_button(obj, .label = "Test 2-2", .hfill = TRUE, .hexpand = TRUE, .margin_left = 20);
+    ui_button(obj, .label = "Test 2-3", .margin_left = 30);
+    ui_button(obj, .label = "Test 3XX", .colspan = 3, .fill = TRUE, .onclick = action_button);
+	ui_show(obj);
 }
 
 int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {
--- a/configure	Fri Oct 10 09:05:11 2025 +0200
+++ b/configure	Fri Oct 10 09:06:06 2025 +0200
@@ -675,6 +675,36 @@
 done
 while true
 do
+    if notisplatform "unix"; then
+        break
+    fi
+    while true
+    do
+
+        cat >> "$TEMP_DIR/make.mk" << __EOF__
+SYS_MAKEFILE = Makefile.unix
+__EOF__
+        break
+    done
+    break
+done
+while true
+do
+    if notisplatform "windows"; then
+        break
+    fi
+    while true
+    do
+
+        cat >> "$TEMP_DIR/make.mk" << __EOF__
+SYS_MAKEFILE = Makefile.win32
+__EOF__
+        break
+    done
+    break
+done
+while true
+do
     if notisplatform "macos"; then
         break
     fi
--- a/make/project.xml	Fri Oct 10 09:05:11 2025 +0200
+++ b/make/project.xml	Fri Oct 10 09:06:06 2025 +0200
@@ -5,6 +5,13 @@
 		<make>LD = \$(CC)</make>
 	</dependency>
 	
+	<dependency platform="unix">
+		<make>SYS_MAKEFILE = Makefile.unix</make>
+	</dependency>
+	<dependency platform="windows">
+		<make>SYS_MAKEFILE = Makefile.win32</make>
+	</dependency>
+	
 	<dependency name="libadwaita">
 		<pkgconfig>libadwaita-1</pkgconfig>
 		<cflags>-DUI_GTK4 -DUI_LIBADWAITA</cflags>
--- a/ui/common/args.c	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/common/args.c	Fri Oct 10 09:06:06 2025 +0200
@@ -907,11 +907,11 @@
 }
 
 void ui_tabview_args_set_margin_top(UiTabViewArgs *args, int value) {
-    args->margin_top;
+    args->margin_top = value;
 }
 
 void ui_tabview_args_set_margin_bottom(UiTabViewArgs *args, int value) {
-    args->margin_bottom;
+    args->margin_bottom = value;
 }
 
 void ui_tabview_args_set_padding(UiTabViewArgs *args, int value) {
@@ -2367,7 +2367,7 @@
 }
 
 void ui_spinbox_args_set_digits(UiSpinBoxArgs *args, int digits) {
-    args->digits;
+    args->digits = digits;
 }
 
 void ui_spinbox_args_set_varname(UiSpinBoxArgs *args, const char *varname) {
--- a/ui/common/container.c	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/common/container.c	Fri Oct 10 09:06:06 2025 +0200
@@ -75,3 +75,13 @@
         }
     }
 }
+
+void uic_layout_setup_margin(UiLayout *layout) {
+    int margin = layout->margin;
+    if(margin > 0) {
+        layout->margin_left = margin;
+        layout->margin_right = margin;
+        layout->margin_top = margin;
+        layout->margin_bottom = margin;
+    }
+}
--- a/ui/common/container.h	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/common/container.h	Fri Oct 10 09:06:06 2025 +0200
@@ -46,6 +46,11 @@
         UiBool def_hfill,
         UiBool def_vfill);
 
+/*
+ * adjusts margin_* if margin > 0
+ */
+void uic_layout_setup_margin(UiLayout *layout);
+
 #ifdef __cplusplus
 }
 #endif
--- a/ui/common/context.c	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/common/context.c	Fri Oct 10 09:06:06 2025 +0200
@@ -109,7 +109,7 @@
     while(var_ctx) {
         CxMapIterator i = cxMapIterator(var_ctx->vars);
         cx_foreach(CxMapEntry*, entry, i) {
-            printf("attach %.*s\n", (int)entry->key->len, entry->key->data);
+            printf("attach %.*s\n", (int)entry->key->len, (char*)entry->key->data);
             UiVar *var = entry->value;
             UiVar *docvar = cxMapGet(doc_ctx->vars, *entry->key);
             if(docvar) {
--- a/ui/common/toolbar.c	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/common/toolbar.c	Fri Oct 10 09:06:06 2025 +0200
@@ -55,6 +55,7 @@
     newargs.label = nl_strdup(args->label);
     newargs.stockid = nl_strdup(args->stockid);
     newargs.icon = nl_strdup(args->icon);
+    newargs.tooltip = nl_strdup(args->tooltip);
     newargs.onclick = args->onclick;
     newargs.onclickdata = args->onclickdata;
     newargs.groups = uic_copy_groups(args->groups, ngroups);
@@ -74,6 +75,7 @@
     newargs.label = nl_strdup(args->label);
     newargs.stockid = nl_strdup(args->stockid);
     newargs.icon = nl_strdup(args->icon);
+    newargs.tooltip = nl_strdup(args->tooltip);
     newargs.varname = nl_strdup(args->varname);
     newargs.onchange = args->onchange;
     newargs.onchangedata = args->onchangedata;
@@ -93,6 +95,7 @@
     newargs.label = nl_strdup(args->label);
     newargs.stockid = nl_strdup(args->stockid);
     newargs.icon = nl_strdup(args->icon);
+    newargs.tooltip = nl_strdup(args->tooltip);
     return newargs;
 }
 
--- a/ui/gtk/button.c	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/gtk/button.c	Fri Oct 10 09:06:06 2025 +0200
@@ -60,6 +60,7 @@
         UiObject *obj,
         const char *label,
         const char *icon,
+        const char *tooltip,
         ui_callback onclick,
         void *userdata,
         int event_value,
@@ -67,6 +68,9 @@
 {
     GtkWidget *button = gtk_button_new_with_label(label);
     ui_button_set_icon_name(button, icon);
+    if(tooltip) {
+        gtk_widget_set_tooltip_text(button, tooltip);
+    }
     
     if(onclick) {
         UiEventData *event = malloc(sizeof(UiEventData));
@@ -100,7 +104,7 @@
 }
 
 UIWIDGET ui_button_create(UiObject *obj, UiButtonArgs *args) {
-    GtkWidget *button = ui_create_button(obj, args->label, args->icon, args->onclick, args->onclickdata, 0, FALSE);
+    GtkWidget *button = ui_create_button(obj, args->label, args->icon, NULL/*tooltip*/, args->onclick, args->onclickdata, 0, FALSE);
     ui_set_name_and_style(button, args->name, args->style_class);
     ui_set_widget_groups(obj->ctx, button, args->groups);
     UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end;
@@ -181,6 +185,7 @@
         GtkWidget *togglebutton,
         const char *label,
         const char *icon,
+        const char *tooltip,
         const char *varname,
         UiInteger *value,
         ui_callback onchange,
@@ -191,6 +196,9 @@
         gtk_button_set_label(GTK_BUTTON(togglebutton), label);
     }
     ui_button_set_icon_name(togglebutton, icon);
+    if(tooltip) {
+        gtk_widget_set_tooltip_text(togglebutton, tooltip);
+    }
     
     ui_bind_togglebutton(
             obj,
@@ -295,6 +303,7 @@
             widget,
             args->label,
             args->icon,
+            NULL, // tooltip
             args->varname,
             args->value,
             args->onchange,
--- a/ui/gtk/button.h	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/gtk/button.h	Fri Oct 10 09:06:06 2025 +0200
@@ -55,6 +55,7 @@
         UiObject *obj,
         const char *label,
         const char *icon,
+        const char *tooltip,
         ui_callback onclick,
         void *userdata,
         int event_value,
@@ -65,6 +66,7 @@
         GtkWidget *togglebutton,
         const char *label,
         const char *icon,
+        const char *tooltip,
         const char *varname,
         UiInteger *value,
         ui_callback onchange,
--- a/ui/gtk/container.c	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/gtk/container.c	Fri Oct 10 09:06:06 2025 +0200
@@ -1022,7 +1022,7 @@
     GtkWidget *sidebar_vbox = g_object_get_data(G_OBJECT(obj->widget), "ui_sidebar");
     
     GtkWidget *box = ui_gtk_vbox_new(args->spacing);
-    ui_gtk_set_margin(box, args->margin, args->margin-left, args->margin_right, args->margin_top, args->margin_bottom);
+    ui_gtk_set_margin(box, args->margin, args->margin_left, args->margin_right, args->margin_top, args->margin_bottom);
     BOX_ADD_EXPAND(sidebar_vbox, box);
     
     UiContainerX *container = ui_box_container(obj, box, UI_CONTAINER_VBOX);
--- a/ui/gtk/headerbar.c	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/gtk/headerbar.c	Fri Oct 10 09:06:06 2025 +0200
@@ -163,7 +163,7 @@
         UiObject *obj,
         enum UiToolbarPos pos)
 {
-    GtkWidget *button = ui_create_button(obj, item->args.label, item->args.icon, item->args.onclick, item->args.onclickdata, 0, FALSE);
+    GtkWidget *button = ui_create_button(obj, item->args.label, item->args.icon, item->args.tooltip, item->args.onclick, item->args.onclickdata, 0, FALSE);
     ui_set_widget_groups(obj->ctx, button, item->args.groups);
     WIDGET_ADD_CSS_CLASS(button, "flat");
     headerbar_add(headerbar, box, button, pos);
@@ -179,7 +179,7 @@
     GtkWidget *button = gtk_toggle_button_new();
     ui_set_widget_groups(obj->ctx, button, item->args.groups);
     WIDGET_ADD_CSS_CLASS(button, "flat");
-    ui_setup_togglebutton(obj, button, item->args.label, item->args.icon, item->args.varname, NULL, item->args.onchange, item->args.onchangedata, 0);
+    ui_setup_togglebutton(obj, button, item->args.label, item->args.icon, NULL/*tooltip*/, item->args.varname, NULL, item->args.onchange, item->args.onchangedata, 0);
     headerbar_add(headerbar, box, button, pos);
 }
 
--- a/ui/gtk/list.c	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/gtk/list.c	Fri Oct 10 09:06:06 2025 +0200
@@ -245,7 +245,10 @@
         GtkEventController *focus_controller = gtk_event_controller_focus_new();
         g_signal_connect(focus_controller, "leave", G_CALLBACK(cell_entry_leave_focus), entry_data);
         gtk_widget_add_controller(textfield, focus_controller);
-    } else {
+    } else if(type == UI_BOOL_EDITABLE) {
+        GtkWidget *checkbox = gtk_check_button_new();
+        gtk_list_item_set_child(item, checkbox);
+    }else {
         GtkWidget *label = gtk_label_new(NULL);
         gtk_label_set_xalign(GTK_LABEL(label), 0);
         gtk_list_item_set_child(item, label);
@@ -390,6 +393,11 @@
             ENTRY_SET_TEXT(child, data);
             break;
         }
+        case UI_BOOL_EDITABLE: {
+            intptr_t i = (intptr_t)data;
+            gtk_check_button_set_active(GTK_CHECK_BUTTON(child), (gboolean)i);
+            break;
+        }
     }
     
     if(attributes != listview->current_row_attributes) {
@@ -417,6 +425,8 @@
         entry->listview = NULL;
         free(entry->previous_value);
         entry->previous_value = NULL;
+    } else if(GTK_IS_CHECK_BUTTON(child)) {
+        
     }
 }
     
@@ -1509,7 +1519,7 @@
 UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs *args) {
     GtkWidget *combobox = gtk_combo_box_new();
     if(args->width > 0) {
-        gtk_widget_set_size_request(scroll_area, args->width, -1);
+        gtk_widget_set_size_request(combobox, args->width, -1);
     }
     
     ui_set_name_and_style(combobox, args->name, args->style_class);
--- a/ui/gtk/toolbar.c	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/gtk/toolbar.c	Fri Oct 10 09:06:06 2025 +0200
@@ -138,6 +138,9 @@
     } else {
         button = gtk_tool_button_new(NULL, item->args.label);
     }
+    if(item->args.tooltip) {
+        gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(button), item->args.tooltip);
+    }
     
     gtk_tool_item_set_homogeneous(button, FALSE);
     if(item->args.icon) {
@@ -192,6 +195,9 @@
             set_toolbutton_icon(button, item->args.icon);
         }    
     }
+    if(item->args.tooltip) {
+        gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(button), item->args.tooltip);
+    }
     ui_set_widget_ngroups(obj->ctx, GTK_WIDGET(button), item->args.groups, item->ngroups);
     
     UiVar* var = uic_widget_var(obj->ctx, obj->ctx, NULL, item->args.varname, UI_VAR_INTEGER);
@@ -297,6 +303,9 @@
     if(item->args.icon) {
         set_toolbutton_icon(button, item->args.icon);
     }
+    if(item->args.tooltip) {
+        gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(button), item->args.tooltip);
+    }
     gtk_tool_item_set_is_important(button, TRUE);
     
     gtk_toolbar_insert(tb, button, -1);
--- a/ui/gtk/window.c	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/gtk/window.c	Fri Oct 10 09:06:06 2025 +0200
@@ -945,7 +945,7 @@
             }
             
             if(args->lbutton1) {
-                GtkWidget *button = ui_create_button(obj, args->lbutton1, NULL, args->onclick, args->onclickdata, 1, args->default_button == 1);
+                GtkWidget *button = ui_create_button(obj, args->lbutton1, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, 1, args->default_button == 1);
                 gtk_header_bar_pack_start(GTK_HEADER_BAR(headerbar), button);
                 if(args->default_button == 1) {
                     WIDGET_ADD_CSS_CLASS(button, "suggested-action");
@@ -953,7 +953,7 @@
                 }
             }
             if(args->lbutton2) {
-                GtkWidget *button = ui_create_button(obj, args->lbutton2, NULL, args->onclick, args->onclickdata, 2, args->default_button == 2);
+                GtkWidget *button = ui_create_button(obj, args->lbutton2, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, 2, args->default_button == 2);
                 gtk_header_bar_pack_start(GTK_HEADER_BAR(headerbar), button);
                 if(args->default_button == 2) {
                     WIDGET_ADD_CSS_CLASS(button, "suggested-action");
@@ -962,7 +962,7 @@
             }
             
             if(args->rbutton4) {
-                GtkWidget *button = ui_create_button(obj, args->rbutton4, NULL, args->onclick, args->onclickdata, 4, args->default_button == 4);
+                GtkWidget *button = ui_create_button(obj, args->rbutton4, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, 4, args->default_button == 4);
                 gtk_header_bar_pack_end(GTK_HEADER_BAR(headerbar), button);
                 if(args->default_button == 4) {
                     WIDGET_ADD_CSS_CLASS(button, "suggested-action");
@@ -970,7 +970,7 @@
                 }
             }
             if(args->rbutton3) {
-                GtkWidget *button = ui_create_button(obj, args->rbutton3, NULL, args->onclick, args->onclickdata, 3, args->default_button == 3);
+                GtkWidget *button = ui_create_button(obj, args->rbutton3, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, 3, args->default_button == 3);
                 gtk_header_bar_pack_end(GTK_HEADER_BAR(headerbar), button);
                 if(args->default_button == 3) {
                     WIDGET_ADD_CSS_CLASS(button, "suggested-action");
@@ -991,7 +991,7 @@
         gtk_grid_set_column_homogeneous(GTK_GRID(grid), TRUE); 
         
         if(args->lbutton1) {
-            GtkWidget *button = ui_create_button(obj, args->lbutton1, NULL, args->onclick, args->onclickdata, 1, args->default_button == 1);
+            GtkWidget *button = ui_create_button(obj, args->lbutton1, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, 1, args->default_button == 1);
             gtk_grid_attach(GTK_GRID(grid), button, 0, 0, 1, 1);
             if(args->default_button == 1) {
                 WIDGET_ADD_CSS_CLASS(button, "suggested-action");
@@ -999,7 +999,7 @@
             }
         }
         if(args->lbutton2) {
-            GtkWidget *button = ui_create_button(obj, args->lbutton2, NULL, args->onclick, args->onclickdata, 2, args->default_button == 2);
+            GtkWidget *button = ui_create_button(obj, args->lbutton2, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, 2, args->default_button == 2);
             gtk_grid_attach(GTK_GRID(grid), button, 1, 0, 1, 1);
             if(args->default_button == 2) {
                 WIDGET_ADD_CSS_CLASS(button, "suggested-action");
@@ -1010,7 +1010,7 @@
         gtk_widget_set_hexpand(space, TRUE);
         gtk_grid_attach(GTK_GRID(grid), space, 2, 0, 1, 1);
         if(args->rbutton3) {
-            GtkWidget *button = ui_create_button(obj, args->rbutton3, NULL, args->onclick, args->onclickdata, 3, args->default_button == 3);
+            GtkWidget *button = ui_create_button(obj, args->rbutton3, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, 3, args->default_button == 3);
             gtk_grid_attach(GTK_GRID(grid), button, 3, 0, 1, 1);
             if(args->default_button == 3) {
                 WIDGET_ADD_CSS_CLASS(button, "suggested-action");
@@ -1018,7 +1018,7 @@
             }
         }
         if(args->rbutton4) {
-            GtkWidget *button = ui_create_button(obj, args->rbutton4, NULL, args->onclick, args->onclickdata, 4, args->default_button == 4);
+            GtkWidget *button = ui_create_button(obj, args->rbutton4, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, 4, args->default_button == 4);
             gtk_grid_attach(GTK_GRID(grid), button, 4, 0, 1, 1);
             if(args->default_button == 4) {
                 WIDGET_ADD_CSS_CLASS(button, "suggested-action");
--- a/ui/qt/Makefile	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/qt/Makefile	Fri Oct 10 09:06:06 2025 +0200
@@ -35,11 +35,10 @@
 
 $(UI_LIB): $(QT_MAKEFILE) $(OBJ) $(UI_LIB) FORCE
 	$(MAKE) -f $(QT_MAKEFILE)
-	$(AR) $(ARFLAGS) $(OBJ) $(UI_LIB) $(OBJ)
+	$(AR) $(ARFLAGS) $(UI_LIB) $(OBJ)
 
-$(UI_SHLIB): $(QT_MAKEFILE) $(OBJ) $(UI_LIB_SH) FORCE
-	$(MAKE) -f $(QT_MAKEFILE)
-	$(CXX) -o $(UI_SHLIB) $(LDFLAGS) $(SHLIB_LDFLAGS) $(TK_LDFLAGS) $(OBJ) -L../build/lib -lucx
-
+$(UI_SHLIB): $(QT_MAKEFILE) $(OBJ) $(UI_LIB_SH) $(UI_LIB) FORCE
+	$(CXX) -o $(UI_SHLIB) $(LDFLAGS) $(SHLIB_LDFLAGS) $(TK_LDFLAGS) $(OBJ) ../build/ui/qt/*.o -L../build/lib -lucx
+	
 FORCE:
 
--- a/ui/qt/container.cpp	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/qt/container.cpp	Fri Oct 10 09:06:06 2025 +0200
@@ -42,7 +42,7 @@
     delete ct;
 }
 
-void ui_container_add(UiObject *obj, UiContainerPrivate *ct) {
+void ui_obj_add_container(UiObject *obj, UiContainerPrivate *ct) {
     UiContainerX *container = (UiContainerX*)ui_malloc(obj->ctx, sizeof(UiContainerX));
     container->close = 0;
     container->container = ct;
@@ -53,10 +53,27 @@
     uic_object_push_container(obj, container);
 }
 
+/* ------------------------ margin helper ------------------------ */
+
+QWidget* ui_widget_set_margin(QWidget *w, int left, int right, int top, int bottom) {
+    if((left | right | top | bottom) == 0) {
+        return w;
+    }
+    QWidget* wrapper = new QWidget;
+    QVBoxLayout* inner = new QVBoxLayout(wrapper);
+    inner->setContentsMargins(left, top, right, bottom);
+    inner->addWidget(w);
+    
+    // TODO: handle widget visibility changes
+    
+    return wrapper;
+}
+
 /* -------------------- UiBoxContainer -------------------- */
 
 UiBoxContainer::UiBoxContainer(QBoxLayout* box) {
     this->box = box;
+    this->direction = box->direction();
     box->setContentsMargins(QMargins(0,0,0,0));
     box->setSpacing(0);
 }
@@ -68,7 +85,12 @@
         fprintf(stderr, "UiError: container has 2 filled widgets");
     }
     
+    uic_layout_setup_margin(&layout);
+    widget = ui_widget_set_margin(widget, layout.margin_left, layout.margin_right, layout.margin_top, layout.margin_bottom);
     box->addWidget(widget);
+    if(direction == Qt::LeftToRight) {
+        box->setAlignment(widget, Qt::AlignTop);
+    }
     
     if(!hasStretchedWidget) {
         QSpacerItem *newspace = new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
@@ -92,7 +114,7 @@
     widget->setLayout(box);
     ctn->add(widget, layout);
     
-    ui_container_add(obj, new UiBoxContainer(box));
+    ui_obj_add_container(obj, new UiBoxContainer(box));
     
     return widget;
 }
@@ -137,6 +159,7 @@
     }
     
     uic_layout_setup_expand_fill(&layout, def_hexpand, def_vexpand, def_hfill, def_vfill);
+    uic_layout_setup_margin(&layout);
     
     if(layout.hexpand) {
         col_expanding = true;
@@ -163,6 +186,7 @@
     int colspan = layout.colspan > 0 ? layout.colspan : 1;
     int rowspan = layout.rowspan > 0 ? layout.rowspan : 1;
     
+    widget = ui_widget_set_margin(widget, layout.margin_left, layout.margin_right, layout.margin_top, layout.margin_bottom);
     grid->addWidget(widget, y, x, rowspan, colspan, alignment);
     
     if(x > max_x) {
@@ -201,7 +225,7 @@
     widget->setLayout(grid);
     ctn->add(widget, layout);
     
-    ui_container_add(obj, new UiGridContainer(
+    ui_obj_add_container(obj, new UiGridContainer(
             grid,
             args->margin,
             args->columnspacing,
@@ -230,7 +254,7 @@
     widget->setLayout(box);
     dock->setWidget(widget);
     
-    ui_container_add(obj, new UiBoxContainer(box));
+    ui_obj_add_container(obj, new UiBoxContainer(box));
     
     return dock;
 }
--- a/ui/qt/container.h	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/qt/container.h	Fri Oct 10 09:06:06 2025 +0200
@@ -52,9 +52,10 @@
 
 class UiBoxContainer : public UiContainerPrivate {
 public:
-    QBoxLayout  *box;
-    bool        hasStretchedWidget = false;
-    QSpacerItem *space;
+    QBoxLayout            *box;
+    bool                  hasStretchedWidget = false;
+    QSpacerItem           *space;
+    QBoxLayout::Direction direction;
     
     UiBoxContainer(QBoxLayout *box);
     
@@ -89,7 +90,9 @@
     virtual void end();
 };
 
-void ui_container_add(UiObject *obj, UiContainerPrivate *ct);
+void ui_obj_add_container(UiObject *obj, UiContainerPrivate *ct);
+
+QWidget* ui_widget_set_margin(QWidget *w, int left, int right, int top, int bottom);
 
 
 #endif	/* CONTAINER_H */
--- a/ui/qt/qt5.pro	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/qt/qt5.pro	Fri Oct 10 09:06:06 2025 +0200
@@ -26,7 +26,7 @@
 # POSSIBILITY OF SUCH DAMAGE.
 #
 
-TARGET = uitk_qt
+TARGET = uitk
 TEMPLATE = lib
 CONFIG += staticlib warn_off debug
 DESTDIR = ../build/lib
--- a/ui/qt/window.cpp	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/qt/window.cpp	Fri Oct 10 09:06:06 2025 +0200
@@ -62,7 +62,7 @@
     QWidget *boxWidget = new QWidget();
     boxWidget->setLayout(box);
     window->setCentralWidget(boxWidget);
-    ui_container_add(obj, new UiBoxContainer(box));
+    ui_obj_add_container(obj, new UiBoxContainer(box));
     if(sidebar) {
         QDockWidget *dock = new QDockWidget();
         window->addDockWidget(Qt::LeftDockWidgetArea, dock);
--- a/ui/ui/toolbar.h	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/ui/toolbar.h	Fri Oct 10 09:06:06 2025 +0200
@@ -37,9 +37,10 @@
 #endif
 
 typedef struct UiToolbarItemArgs {
-    const char* label;
-    const char* stockid;
-    const char* icon;
+    const char *label;
+    const char *stockid;
+    const char *icon;
+    const char *tooltip;
 
     ui_callback onclick;
     void* onclickdata;
@@ -48,21 +49,23 @@
 } UiToolbarItemArgs;
 
 typedef struct UiToolbarToggleItemArgs {
-    const char* label;
-    const char* stockid;
-    const char* icon;
+    const char *label;
+    const char *stockid;
+    const char *icon;
+    const char *tooltip;
 
-    const char* varname;
+    const char *varname;
     ui_callback onchange;
-    void* onchangedata;
+    void *onchangedata;
 
     const int *groups;
 } UiToolbarToggleItemArgs;
 
 typedef struct UiToolbarMenuArgs {
-    const char* label;
-    const char* stockid;
-    const char* icon;
+    const char *label;
+    const char *stockid;
+    const char *icon;
+    const char *tooltip;
 } UiToolbarMenuArgs;
 
 enum UiToolbarPos {
--- a/ui/ui/toolkit.h	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/ui/toolkit.h	Fri Oct 10 09:06:06 2025 +0200
@@ -88,13 +88,7 @@
 
 #elif UI_WIN32
 
-#include <Windows.h>
-
-#define UIEXPORT __declspec(dllexport)
-
-typedef struct W32Widget {
-    HWND hwnd;
-} W32Widget;
+#include "win32.h"
 
 #define UIWIDGET W32Widget*
 #define UIWINDOW void*
--- a/ui/ui/tree.h	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/ui/tree.h	Fri Oct 10 09:06:06 2025 +0200
@@ -52,13 +52,15 @@
     UI_ICON,
     UI_ICON_TEXT,
     UI_ICON_TEXT_FREE,
-    UI_STRING_EDITABLE
+    UI_STRING_EDITABLE,
+    UI_BOOL_EDITABLE
 } UiModelType;
 
 typedef struct UiCellValue {
     union {
         const char *string;
         int64_t i;
+        UiBool b;
     };
     UiModelType type;
 } UiCellValue;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/ui/win32.h	Fri Oct 10 09:06:06 2025 +0200
@@ -0,0 +1,67 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_WIN32_H
+#define UI_WIN32_H
+
+#include <Windows.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define UIEXPORT __declspec(dllexport)
+
+typedef struct W32WidgetClass W32WidgetClass;
+typedef struct W32Widget W32Widget;
+typedef struct W32Size W32Size;
+
+struct W32Size {
+    int width;
+    int height;
+};
+
+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);
+    void (*destroy)(W32Widget *widget);
+};
+
+struct W32Widget {
+    W32WidgetClass *wclass;
+    HWND hwnd;
+    void *userdata;
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_WIN32_H */
--- a/ui/win32/button.c	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/win32/button.c	Fri Oct 10 09:06:06 2025 +0200
@@ -27,7 +27,67 @@
  */
 
 #include "button.h"
+#include "widget.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+static W32WidgetClass button_widget_class = {
+    .eventproc = ui_button_eventproc,
+    .enable = w32_widget_default_enable,
+    .show = w32_widget_default_show,
+    .get_preferred_size = ui_button_get_preferred_size,
+    .destroy  = w32_widget_default_destroy
+};
 
 UIWIDGET ui_button_create(UiObject *obj, UiButtonArgs *args) {
-    return NULL;
+    HINSTANCE hInstance = GetModuleHandle(NULL);
+    UiContainerPrivate *container = ui_obj_container(obj);
+    HWND parent = ui_container_get_parent(container);
+    UiLayout layout = UI_ARGS2LAYOUT(args);
+
+    HWND hwnd = CreateWindow(
+            "BUTTON",
+            args->label,
+            WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,
+            0, 0, 100, 30,
+            parent,
+            (HMENU)0,
+            hInstance,
+            NULL);
+    ui_win32_set_ui_font(hwnd);
+
+    W32Widget *widget = w32_widget_create(&button_widget_class, hwnd, sizeof(UiWidget));
+    ui_container_add(container, widget, &layout);
+
+    UiWidget *btn = (UiWidget*)widget;
+    btn->obj = obj;
+    btn->callback = args->onclick;
+    btn->callbackdata = args->onclickdata;
+
+    return widget;
 }
+
+W32Size ui_button_get_preferred_size(W32Widget *widget) {
+    W32Size size;
+    size.width = 100;
+    size.height = 30;
+    return size;
+}
+
+void ui_button_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
+    UiWidget *w = (UiWidget*)widget;
+
+    UiEvent e;
+    e.obj = w->obj;
+    e.document = e.obj->ctx->document;
+    e.window = e.obj->window;
+    e.eventdata = NULL;
+    e.eventdatatype = 0;
+    e.intval = 0;
+    e.set = ui_get_setop();
+
+    if (w->callback) {
+        w->callback(&e, w->callbackdata);
+    }
+}
--- a/ui/win32/button.h	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/win32/button.h	Fri Oct 10 09:06:06 2025 +0200
@@ -32,4 +32,8 @@
 #include "../ui/button.h"
 #include "container.h"
 
+W32Size ui_button_get_preferred_size(W32Widget *widget);
+
+void ui_button_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
 #endif //BUTTON_H
--- a/ui/win32/container.c	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/win32/container.c	Fri Oct 10 09:06:06 2025 +0200
@@ -27,6 +27,30 @@
  */
 
 #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;
+}
+
+HWND ui_container_get_parent(UiContainerPrivate *ctn) {
+    return ctn->parent ? ctn->parent(ctn) : ctn->hwnd;
+}
+
+void ui_container_add(UiContainerPrivate *ctn, W32Widget *widget, UiLayout *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;
+}
 
 
 /* ---------------------------- Box Container ---------------------------- */
@@ -45,4 +69,73 @@
     return box_create(obj, args, UI_BOX_HORIZONTAL);
 }
 
+UiContainerX* ui_box_container_create(UiObject *obj, HWND hwnd, UiBoxOrientation orientation, short spacing, GridEdgeInsets padding) {
+    UiBoxContainer *container = cxZalloc(obj->ctx->allocator, sizeof(UiBoxContainer));
+    container->container.hwnd = hwnd;
+    container->container.add = ui_box_container_add;
+    container->layout = ui_grid_layout_create(obj->ctx->allocator, spacing, spacing);
+    container->layout->padding = padding;
+    container->orientation = orientation;
+    return (UiContainerX*)container;
+}
 
+void ui_box_container_add(UiContainerPrivate *ctn, W32Widget *widget, UiLayout *layout) {
+    UiBoxContainer *box = (UiBoxContainer*)ctn;
+    GridLayoutInfo gridLayout = (GridLayoutInfo) {
+        .margin = (GridEdgeInsets) { layout->margin_top, layout->margin_bottom, layout->margin_left, layout->margin_right },
+    };
+    if (box->orientation == UI_BOX_HORIZONTAL) {
+        gridLayout.vexpand = TRUE;
+        gridLayout.vfill = TRUE;
+        gridLayout.hexpand = layout->fill;
+        gridLayout.hfill = layout->fill;
+    } else {
+        gridLayout.hexpand = TRUE;
+        gridLayout.hfill = TRUE;
+        gridLayout.vexpand = layout->fill;
+        gridLayout.vfill = layout->fill;
+    }
+    ui_grid_add_widget(box->layout, box->x, box->y, widget, &gridLayout);
+    if (box->orientation == UI_BOX_HORIZONTAL) {
+        box->x++;
+    } else {
+        box->y++;
+    }
+}
+
+/* ---------------------------- Grid Container ---------------------------- */
+
+UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs *args) {
+    return NULL;
+}
+
+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, columnspacing, rowspacing);
+    container->layout->padding = padding;
+    return (UiContainerX*)container;
+}
+
+void ui_grid_container_add(UiContainerPrivate *ctn, W32Widget *widget, UiLayout *layout) {
+    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	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/win32/container.h	Fri Oct 10 09:06:06 2025 +0200
@@ -1,5 +1,5 @@
 /*
-* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
  * Copyright 2025 Olaf Wintermann. All rights reserved.
  *
@@ -27,36 +27,19 @@
  */
 
 #ifndef CONTAINER_H
-
-#include "../ui/container.h"
-
 #define CONTAINER_H
 
-#define UI_APPLY_LAYOUT(layout, args) \
-    layout.fill = args->fill; \
-    layout.hexpand = args->hexpand; \
-    layout.vexpand = args->vexpand; \
-    layout.hfill = args->hfill; \
-    layout.vfill = args->vfill; \
-    layout.override_defaults = args->override_defaults; \
-    layout.colspan = args->colspan; \
-    layout.rowspan = args->rowspan
-
-typedef struct UiLayout UiLayout;
+#include "../ui/container.h"
+#include "toolkit.h"
+#include "grid.h"
 
-struct UiLayout {
-    UiBool       fill;
-    UiBool       newline;
-    char         *label;
-    UiBool       hexpand;
-    UiBool       vexpand;
-    UiBool       hfill;
-    UiBool       vfill;
-    UiBool       override_defaults;
-    int          width;
-    int          colspan;
-    int          rowspan;
-};
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiContainerPrivate    UiContainerPrivate;
+typedef struct UiGridLayoutContainer UiGridLayoutContainer;
+typedef struct UiBoxContainer        UiBoxContainer;
 
 enum UiBoxOrientation {
     UI_BOX_VERTICAL = 0,
@@ -70,8 +53,6 @@
 };
 typedef enum UiContainerType UiContainerType;
 
-typedef struct UiContainerPrivate UiContainerPrivate;
-
 typedef struct UiRect {
     int x;
     int y;
@@ -82,10 +63,48 @@
 
 struct UiContainerPrivate {
     UiContainerX    container;
-    void            (*prepare)(UiContainerPrivate*, UiRect*);
-    void            (*add)(UiContainerPrivate*, UiRect*, W32Widget*);
+    HWND            (*parent)(UiContainerPrivate*);
+    void            (*add)(UiContainerPrivate*, W32Widget*, UiLayout*);
     UiContainerType type;
-    UiLayout        layout;
+    HWND            hwnd;
+};
+
+struct UiBoxContainer {
+    UiContainerPrivate container;
+    UiGridLayout *layout;
+    UiBoxOrientation orientation;
+    int x;
+    int y;
 };
 
+struct UiGridLayoutContainer {
+    UiContainerPrivate container;
+    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_box_container_create(UiObject *obj, HWND hwnd, UiBoxOrientation orientation, short spacing, GridEdgeInsets padding);
+void ui_box_container_add(UiContainerPrivate *ctn, W32Widget *widget, UiLayout *layout);
+
+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
+}
+#endif
+
 #endif //CONTAINER_H
--- a/ui/win32/grid.c	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/win32/grid.c	Fri Oct 10 09:06:06 2025 +0200
@@ -31,11 +31,12 @@
 #include "../../ucx/cx/array_list.h"
 #include "../common/context.h"
 
-UiGridLayout* ui_grid_container(UiObject *obj, HWND control, short padding, short columnspacing, short rowspacing) {
-    UiGridLayout *grid = cxZalloc(obj->ctx->allocator, sizeof(UiGridLayout));
-    grid->hwnd = control;
-    grid->widgets = cxArrayListCreate(obj->ctx->allocator, NULL, sizeof(GridElm), 32);
-    grid->padding = padding;
+#include <stdio.h>
+#include <stdlib.h>
+
+UiGridLayout* ui_grid_layout_create(const CxAllocator *a, short columnspacing, short rowspacing) {
+    UiGridLayout *grid = cxZalloc(a, sizeof(UiGridLayout));
+    grid->widgets = cxArrayListCreate(a, NULL, sizeof(GridElm), 32);
     grid->columnspacing = columnspacing;
     grid->rowspacing = rowspacing;
     return grid;
@@ -48,14 +49,249 @@
     W32Widget *widget,
     GridLayoutInfo *layout)
 {
+    // add the widget
     GridElm elm;
     elm.widget = widget;
-    elm.x = x;
-    elm.y = y;
+    elm.gridx = x;
+    elm.gridy = y;
     elm.layout = *layout;
-    cxListAdd(grid->widgets, elm);
+    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	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/win32/grid.h	Fri Oct 10 09:06:06 2025 +0200
@@ -28,47 +28,64 @@
 #ifndef GRID_H
 #define GRID_H
 
-#include "container.h"
+#include "../ui/win32.h"
 #include <stdbool.h>
 #include <cx/array_list.h>
 
-typedef struct GridElm {
-    W32Widget *widget;
-    short x;
-    short y;
-    GridLayoutInfo layout;
-} GridElm;
+#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;
-    short preferred_width;
-    short preferred_height;
+    int preferred_width;
+    int preferred_height;
     bool hexpand;
     bool vexpand;
     bool hfill;
     bool vfill;
 } GridLayoutInfo;
 
+typedef struct GridElm {
+    W32Widget *widget;
+    short gridx;
+    short gridy;
+    GridLayoutInfo layout;
+} GridElm;
+
 typedef struct UiGridLayout {
-    HWND hwnd;
-
-    short padding;
+    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(UiObject *obj, 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, short columnspacing, short rowspacing);
 
 void ui_grid_add_widget(
     UiGridLayout *grid,
@@ -77,6 +94,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/objs.mk	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/win32/objs.mk	Fri Oct 10 09:06:06 2025 +0200
@@ -30,10 +30,13 @@
 WIN32_OBJPRE = $(OBJ_DIR)$(WIN32_SRC_DIR)
 
 WIN32OBJ  = toolkit.obj
+WIN32OBJ += win32.obj
+WIN32OBJ += widget.obj
 WIN32OBJ += window.obj
 WIN32OBJ += image.obj
 WIN32OBJ += container.obj
 WIN32OBJ += button.obj
+WIN32OBJ += grid.obj
 
 TOOLKITOBJS += $(WIN32OBJ:%=$(WIN32_OBJPRE)%)
 TOOLKITSOURCE += $(WIN32OBJ:%.obj=win32/%.c)
--- a/ui/win32/toolkit.c	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/win32/toolkit.c	Fri Oct 10 09:06:06 2025 +0200
@@ -36,9 +36,13 @@
 #include "../common/document.h"
 #include "../common/properties.h"
 
+#include "../ui/widget.h"
+
 #include <stdio.h>
 #include <stdlib.h>
 
+#include <commctrl.h>
+
 static const char *application_name;
 
 static ui_callback   startup_func;
@@ -48,16 +52,36 @@
 static ui_callback   exit_func;
 void                 *exit_data;
 
+static HFONT ui_font = NULL;
+
 void ui_init(const char *appname, int argc, char **argv) {
     application_name = appname;
 
     uic_init_global_context();
-    uic_docmgr_init();
     uic_menu_init();
     uic_toolbar_init();
     uic_load_app_properties();
 
     ui_window_init();
+
+    INITCOMMONCONTROLSEX icex = { sizeof(icex), ICC_WIN95_CLASSES };
+    InitCommonControlsEx(&icex);
+
+    SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
+
+    NONCLIENTMETRICS ncm = { sizeof(ncm) };
+    SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, FALSE);
+    ui_font = CreateFontIndirect(&ncm.lfMessageFont);
+}
+
+HFONT ui_win32_get_font(void) {
+    return ui_font;
+}
+
+void ui_win32_set_ui_font(HWND control) {
+    if (ui_font) {
+        SendMessage(control, WM_SETFONT, (WPARAM)ui_font, TRUE);
+    }
 }
 
 const char* ui_appname() {
@@ -96,3 +120,7 @@
     }
     uic_store_app_properties();
 }
+
+void ui_show(UiObject *obj) {
+    ui_set_visible(obj->widget, TRUE);
+}
\ No newline at end of file
--- a/ui/win32/toolkit.h	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/win32/toolkit.h	Fri Oct 10 09:06:06 2025 +0200
@@ -34,10 +34,27 @@
 #include "../common/context.h"
 #include "../common/object.h"
 
+#include "win32.h"
+
 #ifdef	__cplusplus
 extern "C" {
 #endif
 
+/*
+ * widget struct that can be used for most primitive widgets,
+ * like buttons, checkboxes
+ */
+typedef struct UiWidget {
+    W32Widget widget;
+    UiObject *obj;
+    UiVar *var;
+    ui_callback callback;
+    void *callbackdata;
+    int64_t intvalue;
+} UiWidget;
+
+HFONT ui_win32_get_font(void);
+void ui_win32_set_ui_font(HWND control);
 
 
 #ifdef	__cplusplus
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/win32/widget.c	Fri Oct 10 09:06:06 2025 +0200
@@ -0,0 +1,43 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "widget.h"
+
+void ui_set_enabled(UIWIDGET widget, UiBool enable) {
+    W32Widget *w = (W32Widget *)widget;
+    if (w->wclass->enable) {
+        w->wclass->enable(w, enable);
+    }
+}
+
+void ui_set_visible(UIWIDGET widget, UiBool visible) {
+    W32Widget *w = (W32Widget *)widget;
+    if (w->wclass->show) {
+        w->wclass->show(w, visible);
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/win32/widget.h	Fri Oct 10 09:06:06 2025 +0200
@@ -0,0 +1,44 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef WIDGET_H
+#define WIDGET_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //WIDGET_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/win32/win32.c	Fri Oct 10 09:06:06 2025 +0200
@@ -0,0 +1,67 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+
+#include "win32.h"
+
+W32Widget* w32_widget_new(W32WidgetClass *wclass, HWND hwnd) {
+    return w32_widget_create(wclass, hwnd, sizeof(W32Widget));
+}
+
+void* w32_widget_create(W32WidgetClass *wclass, HWND hwnd, size_t obj_size) {
+    W32Widget *w = calloc(obj_size, 1);
+    w->wclass = wclass;
+    w->hwnd = hwnd;
+    SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)w);
+    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);
+}
+
+void w32_widget_default_show(W32Widget *w, BOOLEAN show) {
+    ShowWindow(w->hwnd, show ? SW_SHOW : SW_HIDE);
+}
+
+void w32_widget_default_enable(W32Widget *w, BOOLEAN enable) {
+    // TODO
+}
+
+W32Size w32_widget_default_get_preferred_size(W32Widget *widget) {
+    return (W32Size){0,0};
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/win32/win32.h	Fri Oct 10 09:06:06 2025 +0200
@@ -0,0 +1,61 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_TK_WIN32_H
+#define UI_TK_WIN32_H
+
+#include "../ui/win32.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+/*
+ * Creates a standard W32Widget object for an HWND handle and stores the widget object as
+ * GWLP_USERDATA in the window.
+ */
+W32Widget* w32_widget_new(W32WidgetClass *wclass, HWND hwnd);
+
+/*
+ * Same as w32_widget_new, but uses obj_size for allocation, allowing to create objects
+ * derived from W32Widget.
+ */
+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);
+W32Size w32_widget_default_get_preferred_size(W32Widget *widget);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //UI_TK_WIN32_H
--- a/ui/win32/window.c	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/win32/window.c	Fri Oct 10 09:06:06 2025 +0200
@@ -27,7 +27,9 @@
  */
 
 #include "window.h"
-#include "Windows.h"
+#include <Windows.h>
+
+#include "container.h"
 
 #include "../common/object.h"
 
@@ -36,18 +38,38 @@
 #include <stdio.h>
 #include <stdlib.h>
 
+#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,
+	.destroy =  w32_widget_default_destroy
+};
 
 static HINSTANCE hInstance;
 
 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);
-            break;
-        default:
-            return DefWindowProc(hwnd, uMsg, wParam, lParam);
+        case WM_DESTROY: {
+	        PostQuitMessage(0);
+        	break;
+        }
+		case WM_COMMAND: {
+        	HWND hwndCtrl = (HWND)lParam;
+        	W32Widget *cmdWidget = (W32Widget*)GetWindowLongPtr(hwndCtrl, GWLP_USERDATA);
+        	if (cmdWidget && cmdWidget->wclass->eventproc) {
+        		cmdWidget->wclass->eventproc(cmdWidget, hwnd, uMsg, wParam, lParam);
+        	}
+        }
+        default: return DefWindowProc(hwnd, uMsg, wParam, lParam);
     }
     return 0;
 }
@@ -60,7 +82,7 @@
     wc.hInstance = hInstance;
     wc.lpszClassName = mainWindowClass;
     wc.hCursor = LoadCursor(NULL, IDC_ARROW);
-    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
+    wc.hbrBackground = GetSysColorBrush(COLOR_BTNFACE);
 
     if(!RegisterClassExA(&wc)) {
         MessageBox(NULL, "RegisterClassEx failed", "Error", MB_ICONERROR);
@@ -71,12 +93,12 @@
 static UiObject* create_window(const char *title, void *window_data, bool simple) {
     UiObject *obj = uic_object_new_toplevel();
     obj->window = window_data;
-	
+
 	HWND hwnd = CreateWindowExA(
 			0,
 			"UiMainWindow",
 			title,
-			WS_OVERLAPPEDWINDOW | WS_VISIBLE,
+			WS_OVERLAPPEDWINDOW,
             CW_USEDEFAULT,
 			CW_USEDEFAULT,
 			800,
@@ -85,10 +107,18 @@
 			NULL,
 			hInstance,
 			NULL);
-	
-	ShowWindow(hwnd, SW_SHOWNORMAL);
+
     UpdateWindow(hwnd);
-	
+
+	UiContainerX *container = ui_box_container_create(obj, hwnd, UI_BOX_VERTICAL, 0, INSETS_ZERO);
+	uic_object_push_container(obj, container);
+
+	UiWindow *widget = w32_widget_create(&w32_toplevel_widget_class, hwnd, sizeof(UiWindow));
+	widget->obj = obj;
+	widget->container = (UiBoxContainer *)container;
+	obj->widget = (W32Widget*)widget;
+	obj->ref = 1;
+
 	return obj;
 }
 
@@ -96,3 +126,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	Fri Oct 10 09:05:11 2025 +0200
+++ b/ui/win32/window.h	Fri Oct 10 09:06:06 2025 +0200
@@ -35,14 +35,23 @@
 #include "../common/object.h"
 
 #include "toolkit.h"
-
+#include "container.h"
 
 #ifdef	__cplusplus
 extern "C" {
 #endif
 
+typedef struct UiWindow {
+    W32Widget widget;
+    UiObject *obj;
+    UiBoxContainer *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
 }
 #endif

mercurial