ui/win32/grid.c

Sun, 07 Dec 2025 20:00:33 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sun, 07 Dec 2025 20:00:33 +0100
changeset 114
3da24640513a
parent 112
c3f2f16fa4b8
permissions
-rw-r--r--

fix settings dialog doesn't open when there is no .dav/config.xml file

/*
* 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 "grid.h"

#include "../../ucx/cx/array_list.h"
#include "../common/context.h"

#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;
}

void ui_grid_add_widget(
    UiGridLayout *grid,
    short x,
    short y,
    W32Widget *widget,
    GridLayoutInfo *layout)
{
    // add the widget
    GridElm elm;
    elm.widget = widget;
    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, int width, int height) {
    if (width == 0 || height == 0) {
        return;
    }

    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;
            }
            child_width -= elm->layout.margin.left + elm->layout.margin.right;
        } else {
            child_width = elm->layout.preferred_width;
        }

        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 - elm->layout.margin.top - elm->layout.margin.bottom;
        } 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);
        if (elm->widget->layout) {
            elm->widget->layout(elm->widget->layoutmanager, child_width, child_height);
        }
    }

    free(cols);
    free(rows);
}

mercurial