ui/gtk/container.c

changeset 0
804d8803eade
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/container.c	Wed Dec 09 11:32:01 2020 +0100
@@ -0,0 +1,627 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 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 <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include "container.h"
+#include "toolkit.h"
+
+#include "../common/context.h"
+#include "../common/object.h"
+
+
+void ui_container_begin_close(UiObject *obj) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->close = 1;
+}
+
+int ui_container_finish(UiObject *obj) {
+    UiContainer *ct = uic_get_current_container(obj);
+    if(ct->close) {
+        ui_end(obj);
+        return 0;
+    }
+    return 1;
+}
+
+GtkWidget* ui_gtk_vbox_new(int spacing) {
+#ifdef UI_GTK3
+    return gtk_box_new(GTK_ORIENTATION_VERTICAL, spacing);
+#else
+    return gtk_vbox_new(FALSE, spacing);
+#endif
+}
+
+GtkWidget* ui_gtk_hbox_new(int spacing) {
+#ifdef UI_GTK3
+    return gtk_box_new(GTK_ORIENTATION_HORIZONTAL, spacing);
+#else
+    return gtk_hbox_new(FALSE, spacing);
+#endif
+}
+
+/* -------------------- Frame Container (deprecated) -------------------- */
+UiContainer* ui_frame_container(UiObject *obj, GtkWidget *frame) {
+    UiContainer *ct = ucx_mempool_calloc(
+            obj->ctx->mempool,
+            1,
+            sizeof(UiContainer));
+    ct->widget = frame;
+    ct->add = ui_frame_container_add;
+    return ct;
+}
+
+void ui_frame_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    gtk_container_add(GTK_CONTAINER(ct->widget), widget);
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+
+
+/* -------------------- Box Container -------------------- */
+UiContainer* ui_box_container(UiObject *obj, GtkWidget *box) {
+    UiBoxContainer *ct = ucx_mempool_calloc(
+            obj->ctx->mempool,
+            1,
+            sizeof(UiBoxContainer));
+    ct->container.widget = box;
+    ct->container.add = ui_box_container_add;
+    return (UiContainer*)ct;
+}
+
+void ui_box_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    UiBoxContainer *bc = (UiBoxContainer*)ct;
+    if(ct->layout.fill != UI_LAYOUT_UNDEFINED) {
+        fill = ui_lb2bool(ct->layout.fill);
+    }
+    
+    if(bc->has_fill && fill) {
+        fprintf(stderr, "UiError: container has 2 filled widgets");
+        fill = FALSE;
+    }
+    if(fill) {
+        bc->has_fill = TRUE;
+    }
+    
+    UiBool expand = fill;
+    gtk_box_pack_start(GTK_BOX(ct->widget), widget, expand, fill, 0);
+    
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+
+UiContainer* ui_grid_container(UiObject *obj, GtkWidget *grid) {
+    UiGridContainer *ct = ucx_mempool_calloc(
+            obj->ctx->mempool,
+            1,
+            sizeof(UiGridContainer));
+    ct->container.widget = grid;
+    ct->container.add = ui_grid_container_add;
+#ifdef UI_GTK2
+    ct->width = 0;
+    ct->height = 1;
+#endif
+    return (UiContainer*)ct;
+}
+
+#ifdef UI_GTK3
+void ui_grid_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    UiGridContainer *grid = (UiGridContainer*)ct;
+    
+    if(ct->layout.newline) {
+        grid->x = 0;
+        grid->y++;
+        ct->layout.newline = FALSE;
+    }
+    
+    int hexpand = FALSE;
+    int vexpand = FALSE;
+    if(ct->layout.hexpand != UI_LAYOUT_UNDEFINED) {
+        hexpand = ct->layout.hexpand;
+    }
+    if(ct->layout.vexpand != UI_LAYOUT_UNDEFINED) {
+        vexpand = ct->layout.vexpand;
+    }
+    
+    if(hexpand) {
+        gtk_widget_set_hexpand(widget, TRUE);
+    }
+    if(vexpand) {
+        gtk_widget_set_vexpand(widget, TRUE);
+    }
+    
+    int gwidth = ct->layout.gridwidth > 0 ? ct->layout.gridwidth : 1;
+    
+    gtk_grid_attach(GTK_GRID(ct->widget), widget, grid->x, grid->y, gwidth, 1);
+    grid->x += gwidth;
+    
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+#endif
+#ifdef UI_GTK2
+void ui_grid_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    UiGridContainer *grid = (UiGridContainer*)ct;
+    
+    if(ct->layout.newline) {
+        grid->x = 0;
+        grid->y++;
+        ct->layout.newline = FALSE;
+    }
+    
+    int hexpand = FALSE;
+    int vexpand = FALSE;
+    if(ct->layout.hexpand != UI_LAYOUT_UNDEFINED) {
+        hexpand = ct->layout.hexpand;
+    }
+    if(ct->layout.vexpand != UI_LAYOUT_UNDEFINED) {
+        vexpand = ct->layout.vexpand;
+    }
+    GtkAttachOptions xoptions = hexpand ? GTK_FILL | GTK_EXPAND : GTK_FILL;
+    GtkAttachOptions yoptions = vexpand ? GTK_FILL | GTK_EXPAND : GTK_FILL;
+    
+    gtk_table_attach(GTK_TABLE(ct->widget), widget, grid->x, grid->x+1, grid->y, grid->y+1, xoptions, yoptions, 0, 0);
+    grid->x++;
+    int nw = grid->x > grid->width ? grid->x : grid->width;
+    if(grid->x > grid->width || grid->y > grid->height) {
+        grid->width = nw;
+        grid->height = grid->y + 1;
+        gtk_table_resize(GTK_TABLE(ct->widget), grid->width, grid->height);
+    }
+    
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+#endif
+
+UiContainer* ui_scrolledwindow_container(UiObject *obj, GtkWidget *scrolledwindow) {
+    UiContainer *ct = ucx_mempool_calloc(
+            obj->ctx->mempool,
+            1,
+            sizeof(UiContainer));
+    ct->widget = scrolledwindow;
+    ct->add = ui_scrolledwindow_container_add;
+    return ct;
+}
+
+void ui_scrolledwindow_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    // TODO: check if the widget implements GtkScrollable
+#ifdef UI_GTK3
+    gtk_container_add(GTK_CONTAINER(ct->widget), widget);
+#else
+    gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(ct->widget), widget);
+#endif
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+
+UiContainer* ui_tabview_container(UiObject *obj, GtkWidget *tabview) {
+    UiTabViewContainer *ct = ucx_mempool_calloc(
+            obj->ctx->mempool,
+            1,
+            sizeof(UiTabViewContainer));
+    ct->container.widget = tabview;
+    ct->container.add = ui_tabview_container_add;
+    return (UiContainer*)ct;
+}
+
+void ui_tabview_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    gtk_notebook_append_page(
+            GTK_NOTEBOOK(ct->widget),
+            widget,
+            gtk_label_new(ct->layout.label));
+    
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+
+
+UIWIDGET ui_vbox(UiObject *obj) {
+    return ui_vbox_sp(obj, 0, 0);
+}
+
+UIWIDGET ui_hbox(UiObject *obj) {
+    return ui_hbox_sp(obj, 0, 0);
+}
+
+static GtkWidget* box_set_margin(GtkWidget *box, int margin) {
+    GtkWidget *ret = box;
+#ifdef UI_GTK3
+#if GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION >= 12
+    gtk_widget_set_margin_start(box, margin);
+    gtk_widget_set_margin_end(box, margin);
+#else
+    gtk_widget_set_margin_left(box, margin);
+    gtk_widget_set_margin_right(box, margin);
+#endif
+    gtk_widget_set_margin_top(box, margin);
+    gtk_widget_set_margin_bottom(box, margin);
+#elif defined(UI_GTK2)
+    GtkWidget *a = gtk_alignment_new(0.5, 0.5, 1, 1);
+    gtk_alignment_set_padding(GTK_ALIGNMENT(a), margin, margin, margin, margin);
+    gtk_container_add(GTK_CONTAINER(a), box);
+    ret = a;
+#endif
+    return ret;
+}
+
+UIWIDGET ui_vbox_sp(UiObject *obj, int margin, int spacing) {
+    UiContainer *ct = uic_get_current_container(obj);
+    
+    GtkWidget *vbox = ui_gtk_vbox_new(spacing);   
+    GtkWidget *widget = margin > 0 ? box_set_margin(vbox, margin) : vbox;
+    ct->add(ct, widget, TRUE);
+    
+    UiObject *newobj = uic_object_new(obj, vbox);
+    newobj->container = ui_box_container(obj, vbox);
+    uic_obj_add(obj, newobj);
+    
+    return widget;
+}
+
+UIWIDGET ui_hbox_sp(UiObject *obj, int margin, int spacing) {
+    UiContainer *ct = uic_get_current_container(obj);
+    
+    GtkWidget *hbox = ui_gtk_hbox_new(spacing);
+    GtkWidget *widget = margin > 0 ? box_set_margin(hbox, margin) : hbox;
+    ct->add(ct, widget, TRUE);
+    
+    UiObject *newobj = uic_object_new(obj, hbox);
+    newobj->container = ui_box_container(obj, hbox);
+    uic_obj_add(obj, newobj);
+    
+    return widget;
+}
+
+UIWIDGET ui_grid(UiObject *obj) {
+    return ui_grid_sp(obj, 0, 0, 0);
+}
+
+UIWIDGET ui_grid_sp(UiObject *obj, int margin, int columnspacing, int rowspacing) {
+    UiContainer *ct = uic_get_current_container(obj);
+    GtkWidget *widget;
+    
+#ifdef UI_GTK3
+    GtkWidget *grid = gtk_grid_new();
+    gtk_grid_set_column_spacing(GTK_GRID(grid), columnspacing);
+    gtk_grid_set_row_spacing(GTK_GRID(grid), rowspacing);
+#if GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION >= 12
+    gtk_widget_set_margin_start(grid, margin);
+    gtk_widget_set_margin_end(grid, margin);
+#else
+    gtk_widget_set_margin_left(grid, margin);
+    gtk_widget_set_margin_right(grid, margin);
+#endif
+    gtk_widget_set_margin_top(grid, margin);
+    gtk_widget_set_margin_bottom(grid, margin);
+    
+    widget = grid;
+#elif defined(UI_GTK2)
+    GtkWidget *grid = gtk_table_new(1, 1, FALSE);
+    
+    gtk_table_set_col_spacings(GTK_TABLE(grid), columnspacing);
+    gtk_table_set_row_spacings(GTK_TABLE(grid), rowspacing);
+    
+    if(margin > 0) {
+        GtkWidget *a = gtk_alignment_new(0.5, 0.5, 1, 1);
+        gtk_alignment_set_padding(GTK_ALIGNMENT(a), margin, margin, margin, margin);
+        gtk_container_add(GTK_CONTAINER(a), grid);
+        widget = a;
+    } else {
+        widget = grid;
+    }
+#endif
+    ct->add(ct, widget, TRUE);
+    
+    UiObject *newobj = uic_object_new(obj, grid);
+    newobj->container = ui_grid_container(obj, grid);
+    uic_obj_add(obj, newobj);
+    
+    return widget;
+}
+
+UIWIDGET ui_scrolledwindow(UiObject *obj) {
+    UiContainer *ct = uic_get_current_container(obj);
+    GtkWidget *sw = gtk_scrolled_window_new(NULL, NULL);
+    ct->add(ct, sw, TRUE);
+    
+    UiObject *newobj = uic_object_new(obj, sw);
+    newobj->container = ui_scrolledwindow_container(obj, sw);
+    uic_obj_add(obj, newobj);
+    
+    return sw;
+}
+
+UIWIDGET ui_tabview(UiObject *obj) {
+    GtkWidget *tabview = gtk_notebook_new();
+    gtk_notebook_set_show_border(GTK_NOTEBOOK(tabview), FALSE);
+    gtk_notebook_set_show_tabs(GTK_NOTEBOOK(tabview), FALSE);
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, tabview, TRUE);
+    
+    UiObject *tabviewobj = uic_object_new(obj, tabview);
+    tabviewobj->container = ui_tabview_container(obj, tabview);
+    uic_obj_add(obj, tabviewobj);
+    
+    return tabview;
+}
+
+void ui_tab(UiObject *obj, char *title) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->layout.label = title;
+    ui_vbox(obj);
+}
+
+void ui_select_tab(UIWIDGET tabview, int tab) {
+    gtk_notebook_set_current_page(GTK_NOTEBOOK(tabview), tab);
+}
+
+/* -------------------- Splitpane -------------------- */
+
+static GtkWidget* create_paned(UiOrientation orientation) {
+#ifdef UI_GTK3
+    switch(orientation) {
+        case UI_HORIZONTAL: return gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
+        case UI_VERTICAL: return gtk_paned_new(GTK_ORIENTATION_VERTICAL);
+    }
+#else
+    switch(orientation) {
+        case UI_HORIZONTAL: return gtk_hpaned_new();
+        case UI_VERTICAL: return gtk_vpaned_new();
+    }
+#endif
+    return NULL;
+}
+
+UIWIDGET ui_splitpane(UiObject *obj, int max, UiOrientation orientation) {
+    GtkWidget *paned = create_paned(orientation);
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, paned, TRUE);
+    
+    if(max <= 0) max = INT_MAX;
+    
+    UiPanedContainer *pctn = ucx_mempool_calloc(
+            obj->ctx->mempool,
+            1,
+            sizeof(UiPanedContainer));
+    pctn->container.widget = paned;
+    pctn->container.add = ui_paned_container_add;
+    pctn->current_pane = paned;
+    pctn->orientation = orientation;
+    pctn->max = max;
+    pctn->cur = 0;
+    
+    UiObject *pobj = uic_object_new(obj, paned);
+    pobj->container = (UiContainer*)pctn;
+    
+    uic_obj_add(obj, pobj);
+    
+    return paned;
+}
+
+UIWIDGET ui_hsplitpane(UiObject *obj, int max) {
+    return ui_splitpane(obj, max, UI_HORIZONTAL);
+}
+
+UIWIDGET ui_vsplitpane(UiObject *obj, int max) {
+    return ui_splitpane(obj, max, UI_VERTICAL);
+}
+
+void ui_paned_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    UiPanedContainer *pctn = (UiPanedContainer*)ct;
+    
+    gboolean resize = (ct->layout.hexpand || ct->layout.vexpand) ? TRUE : FALSE;
+    int width = ct->layout.width;
+    ui_reset_layout(ct->layout);
+    
+    if(pctn->cur == 0) {
+        gtk_paned_pack1(GTK_PANED(pctn->current_pane), widget, resize, resize);
+    } else if(pctn->cur < pctn->max-1) {
+        GtkWidget *nextPane = create_paned(pctn->orientation);
+        gtk_paned_pack2(GTK_PANED(pctn->current_pane), nextPane, TRUE, TRUE);
+        gtk_paned_pack1(GTK_PANED(nextPane), widget, resize, resize);
+        pctn->current_pane = nextPane;
+    } else if(pctn->cur == pctn->max-1) {
+        gtk_paned_pack2(GTK_PANED(pctn->current_pane), widget, resize, resize);
+        width = 0; // disable potential call of gtk_paned_set_position
+    } else {
+        fprintf(stderr, "Splitpane max reached: %d\n", pctn->max);
+        return;
+    }
+    
+    if(width > 0) {
+        gtk_paned_set_position(GTK_PANED(pctn->current_pane), width);
+    }
+    
+    pctn->cur++;
+}
+
+
+/* -------------------- Sidebar (deprecated) -------------------- */
+UIWIDGET ui_sidebar(UiObject *obj) {
+#ifdef UI_GTK3
+    GtkWidget *paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
+#else
+    GtkWidget *paned = gtk_hpaned_new();
+#endif
+    gtk_paned_set_position(GTK_PANED(paned), 200);
+    
+    GtkWidget *sidebar = ui_gtk_vbox_new(0);
+    gtk_paned_pack1(GTK_PANED(paned), sidebar, TRUE, FALSE);
+    
+    UiObject *left = uic_object_new(obj, sidebar);
+    UiContainer *ct1 = ui_box_container(obj, sidebar);
+    left->container = ct1;
+    
+    UiObject *right = uic_object_new(obj, sidebar);
+    UiContainer *ct2 = ucx_mempool_malloc(
+            obj->ctx->mempool,
+            sizeof(UiContainer));
+    ct2->widget = paned;
+    ct2->add = ui_split_container_add2;
+    right->container = ct2;
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, paned, TRUE);
+    
+    uic_obj_add(obj, right);
+    uic_obj_add(obj, left);
+    
+    return sidebar;
+}
+
+void ui_split_container_add1(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    // TODO: remove
+    gtk_paned_pack1(GTK_PANED(ct->widget), widget, TRUE, FALSE);
+    
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+
+void ui_split_container_add2(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+    gtk_paned_pack2(GTK_PANED(ct->widget), widget, TRUE, FALSE);
+    
+    ui_reset_layout(ct->layout);
+    ct->current = widget;
+}
+
+
+/* -------------------- Document Tabview -------------------- */
+static void page_change(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer data) {
+    GQuark q = g_quark_from_static_string("ui.tab.object");
+    UiObject *tab = g_object_get_qdata(G_OBJECT(page), q);
+    if(!tab) {
+        return;
+    }
+    
+    //printf("page_change: %d\n", page_num);
+    UiContext *ctx = tab->ctx;
+    uic_context_detach_all(ctx->parent); // TODO: fix?
+    ctx->parent->attach_document(ctx->parent, ctx->document);
+}
+
+UiTabbedPane* ui_tabbed_document_view(UiObject *obj) {
+    GtkWidget *tabview = gtk_notebook_new();
+    gtk_notebook_set_show_border(GTK_NOTEBOOK(tabview), FALSE);
+    
+    g_signal_connect(
+                tabview,
+                "switch-page",
+                G_CALLBACK(page_change),
+                NULL);
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, tabview, TRUE);
+    
+    UiTabbedPane *tabbedpane = ui_malloc(obj->ctx, sizeof(UiTabbedPane));
+    tabbedpane->ctx = uic_current_obj(obj)->ctx;
+    tabbedpane->widget = tabview;
+    tabbedpane->document = NULL;
+    
+    return tabbedpane;
+}
+
+UiObject* ui_document_tab(UiTabbedPane *view) {
+    GtkWidget *frame = gtk_frame_new(NULL);
+    gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE);
+    // TODO: label
+    gtk_notebook_append_page(GTK_NOTEBOOK(view->widget), frame, NULL);
+    
+    UiObject *tab = ui_malloc(view->ctx, sizeof(UiObject));
+    tab->widget = NULL; // initialization for uic_context()
+    tab->ctx = uic_context(tab, view->ctx->mempool);
+    tab->ctx->parent = view->ctx;
+    tab->ctx->attach_document = uic_context_attach_document;
+    tab->ctx->detach_document2 = uic_context_detach_document2;
+    tab->widget = frame;
+    tab->window = view->ctx->obj->window;
+    tab->container = ui_frame_container(tab, frame);
+    tab->next = NULL;
+    
+    GQuark q = g_quark_from_static_string("ui.tab.object");
+    g_object_set_qdata(G_OBJECT(frame), q, tab);
+    
+    return tab;
+}
+
+void ui_tab_set_document(UiContext *ctx, void *document) {
+    // TODO: remove?
+    if(ctx->parent->document) {
+        //ctx->parent->detach_document(ctx->parent, ctx->parent->document);
+    }
+    //uic_context_set_document(ctx, document);
+    //uic_context_set_document(ctx->parent, document);
+    //ctx->parent->document = document;
+}
+
+void ui_tab_detach_document(UiContext *ctx) {
+    // TODO: remove?
+    //uic_context_detach_document(ctx->parent);
+}
+
+
+/*
+ * -------------------- Layout Functions --------------------
+ * 
+ * functions for setting layout attributes for the current container
+ *
+ */
+
+void ui_layout_fill(UiObject *obj, UiBool fill) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->layout.fill = ui_bool2lb(fill);
+}
+
+void ui_layout_hexpand(UiObject *obj, UiBool expand) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->layout.hexpand = expand;
+}
+
+void ui_layout_vexpand(UiObject *obj, UiBool expand) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->layout.vexpand = expand;
+}
+
+void ui_layout_width(UiObject *obj, int width) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->layout.width = width;
+}
+
+void ui_layout_gridwidth(UiObject *obj, int width) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->layout.gridwidth = width;
+}
+
+void ui_newline(UiObject *obj) {
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->layout.newline = TRUE;
+}
+

mercurial