aboutsummaryrefslogtreecommitdiff
path: root/src/libui_sdl/libui/windows/grid.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/libui_sdl/libui/windows/grid.cpp')
-rw-r--r--src/libui_sdl/libui/windows/grid.cpp658
1 files changed, 658 insertions, 0 deletions
diff --git a/src/libui_sdl/libui/windows/grid.cpp b/src/libui_sdl/libui/windows/grid.cpp
new file mode 100644
index 0000000..c63cd1e
--- /dev/null
+++ b/src/libui_sdl/libui/windows/grid.cpp
@@ -0,0 +1,658 @@
+// 10 june 2016
+#include "uipriv_windows.hpp"
+
+// TODO compare with GTK+:
+// - what happens if you call InsertAt() twice?
+// - what happens if you call Append() twice?
+
+// TODOs
+// - the Assorted page has clipping and repositioning issues
+
+struct gridChild {
+ uiControl *c;
+ int left;
+ int top;
+ int xspan;
+ int yspan;
+ int hexpand;
+ uiAlign halign;
+ int vexpand;
+ uiAlign valign;
+
+ // have these here so they don't need to be reallocated each relayout
+ int finalx, finaly;
+ int finalwidth, finalheight;
+ int minwidth, minheight;
+};
+
+struct uiGrid {
+ uiWindowsControl c;
+ HWND hwnd;
+ std::vector<struct gridChild *> *children;
+ std::map<uiControl *, size_t> *indexof;
+ int padded;
+
+ int xmin, ymin;
+ int xmax, ymax;
+};
+
+static bool gridRecomputeMinMax(uiGrid *g)
+{
+ bool first = true;
+
+ for (struct gridChild *gc : *(g->children)) {
+ // this is important; we want g->xmin/g->ymin to satisfy gridLayoutData::visibleRow()/visibleColumn()
+ if (!uiControlVisible(gc->c))
+ continue;
+ if (first) {
+ g->xmin = gc->left;
+ g->ymin = gc->top;
+ g->xmax = gc->left + gc->xspan;
+ g->ymax = gc->top + gc->yspan;
+ first = false;
+ continue;
+ }
+ if (g->xmin > gc->left)
+ g->xmin = gc->left;
+ if (g->ymin > gc->top)
+ g->ymin = gc->top;
+ if (g->xmax < (gc->left + gc->xspan))
+ g->xmax = gc->left + gc->xspan;
+ if (g->ymax < (gc->top + gc->yspan))
+ g->ymax = gc->top + gc->yspan;
+ }
+ return first != false;
+}
+
+#define xcount(g) ((g)->xmax - (g)->xmin)
+#define ycount(g) ((g)->ymax - (g)->ymin)
+#define toxindex(g, x) ((x) - (g)->xmin)
+#define toyindex(g, y) ((y) - (g)->ymin)
+
+class gridLayoutData {
+ int ycount;
+public:
+ int **gg; // topological map gg[y][x] = control index
+ int *colwidths;
+ int *rowheights;
+ bool *hexpand;
+ bool *vexpand;
+ int nVisibleRows;
+ int nVisibleColumns;
+
+ bool noVisible;
+
+ gridLayoutData(uiGrid *g)
+ {
+ size_t i;
+ int x, y;
+
+ this->noVisible = gridRecomputeMinMax(g);
+
+ this->gg = new int *[ycount(g)];
+ for (y = 0; y < ycount(g); y++) {
+ this->gg[y] = new int[xcount(g)];
+ for (x = 0; x < xcount(g); x++)
+ this->gg[y][x] = -1;
+ }
+
+ for (i = 0; i < g->children->size(); i++) {
+ struct gridChild *gc;
+
+ gc = (*(g->children))[i];
+ if (!uiControlVisible(gc->c))
+ continue;
+ for (y = gc->top; y < gc->top + gc->yspan; y++)
+ for (x = gc->left; x < gc->left + gc->xspan; x++)
+ this->gg[toyindex(g, y)][toxindex(g, x)] = i;
+ }
+
+ this->colwidths = new int[xcount(g)];
+ ZeroMemory(this->colwidths, xcount(g) * sizeof (int));
+ this->rowheights = new int[ycount(g)];
+ ZeroMemory(this->rowheights, ycount(g) * sizeof (int));
+ this->hexpand = new bool[xcount(g)];
+ ZeroMemory(this->hexpand, xcount(g) * sizeof (bool));
+ this->vexpand = new bool[ycount(g)];
+ ZeroMemory(this->vexpand, ycount(g) * sizeof (bool));
+
+ this->ycount = ycount(g);
+
+ // if a row or column only contains emptys and spanning cells of a opposite-direction spannings, it is invisible and should not be considered for padding amount calculations
+ // note that the first row and column will always be visible because gridRecomputeMinMax() computed a smallest fitting rectangle
+ if (this->noVisible)
+ return;
+ this->nVisibleRows = 0;
+ for (y = 0; y < this->ycount; y++)
+ if (this->visibleRow(g, y))
+ this->nVisibleRows++;
+ this->nVisibleColumns = 0;
+ for (x = 0; x < xcount(g); x++)
+ if (this->visibleColumn(g, x))
+ this->nVisibleColumns++;
+ }
+
+ ~gridLayoutData()
+ {
+ size_t y;
+
+ delete[] this->hexpand;
+ delete[] this->vexpand;
+ delete[] this->colwidths;
+ delete[] this->rowheights;
+ for (y = 0; y < this->ycount; y++)
+ delete[] this->gg[y];
+ delete[] this->gg;
+ }
+
+ bool visibleRow(uiGrid *g, int y)
+ {
+ int x;
+ struct gridChild *gc;
+
+ for (x = 0; x < xcount(g); x++)
+ if (this->gg[y][x] != -1) {
+ gc = (*(g->children))[this->gg[y][x]];
+ if (gc->yspan == 1 || gc->top - g->ymin == y)
+ return true;
+ }
+ return false;
+ }
+
+ bool visibleColumn(uiGrid *g, int x)
+ {
+ int y;
+ struct gridChild *gc;
+
+ for (y = 0; y < this->ycount; y++)
+ if (this->gg[y][x] != -1) {
+ gc = (*(g->children))[this->gg[y][x]];
+ if (gc->xspan == 1 || gc->left - g->xmin == x)
+ return true;
+ }
+ return false;
+ }
+};
+
+static void gridPadding(uiGrid *g, int *xpadding, int *ypadding)
+{
+ uiWindowsSizing sizing;
+
+ *xpadding = 0;
+ *ypadding = 0;
+ if (g->padded) {
+ uiWindowsGetSizing(g->hwnd, &sizing);
+ uiWindowsSizingStandardPadding(&sizing, xpadding, ypadding);
+ }
+}
+
+static void gridRelayout(uiGrid *g)
+{
+ RECT r;
+ int x, y, width, height;
+ gridLayoutData *ld;
+ int xpadding, ypadding;
+ int ix, iy;
+ int iwidth, iheight;
+ int i;
+ struct gridChild *gc;
+ int nhexpand, nvexpand;
+
+ if (g->children->size() == 0)
+ return; // nothing to do
+
+ uiWindowsEnsureGetClientRect(g->hwnd, &r);
+ x = r.left;
+ y = r.top;
+ width = r.right - r.left;
+ height = r.bottom - r.top;
+
+ gridPadding(g, &xpadding, &ypadding);
+ ld = new gridLayoutData(g);
+ if (ld->noVisible) { // nothing to do
+ delete ld;
+ return;
+ }
+
+ // 0) discount padding from width/height
+ width -= (ld->nVisibleColumns - 1) * xpadding;
+ height -= (ld->nVisibleRows - 1) * ypadding;
+
+ // 1) compute colwidths and rowheights before handling expansion
+ // we only count non-spanning controls to avoid weirdness
+ for (iy = 0; iy < ycount(g); iy++)
+ for (ix = 0; ix < xcount(g); ix++) {
+ i = ld->gg[iy][ix];
+ if (i == -1)
+ continue;
+ gc = (*(g->children))[i];
+ uiWindowsControlMinimumSize(uiWindowsControl(gc->c), &iwidth, &iheight);
+ if (gc->xspan == 1)
+ if (ld->colwidths[ix] < iwidth)
+ ld->colwidths[ix] = iwidth;
+ if (gc->yspan == 1)
+ if (ld->rowheights[iy] < iheight)
+ ld->rowheights[iy] = iheight;
+ // save these for step 6
+ gc->minwidth = iwidth;
+ gc->minheight = iheight;
+ }
+
+ // 2) figure out which rows/columns expand but not span
+ // we need to know which expanding rows/columns don't span before we can handle the ones that do
+ for (i = 0; i < g->children->size(); i++) {
+ gc = (*(g->children))[i];
+ if (!uiControlVisible(gc->c))
+ continue;
+ if (gc->hexpand && gc->xspan == 1)
+ ld->hexpand[toxindex(g, gc->left)] = true;
+ if (gc->vexpand && gc->yspan == 1)
+ ld->vexpand[toyindex(g, gc->top)] = true;
+ }
+
+ // 3) figure out which rows/columns expand that do span
+ // the way we handle this is simple: if none of the spanned rows/columns expand, make all rows/columns expand
+ for (i = 0; i < g->children->size(); i++) {
+ gc = (*(g->children))[i];
+ if (!uiControlVisible(gc->c))
+ continue;
+ if (gc->hexpand && gc->xspan != 1) {
+ bool doit = true;
+
+ for (ix = gc->left; ix < gc->left + gc->xspan; ix++)
+ if (ld->hexpand[toxindex(g, ix)]) {
+ doit = false;
+ break;
+ }
+ if (doit)
+ for (ix = gc->left; ix < gc->left + gc->xspan; ix++)
+ ld->hexpand[toxindex(g, ix)] = true;
+ }
+ if (gc->vexpand && gc->yspan != 1) {
+ bool doit = true;
+
+ for (iy = gc->top; iy < gc->top + gc->yspan; iy++)
+ if (ld->vexpand[toyindex(g, iy)]) {
+ doit = false;
+ break;
+ }
+ if (doit)
+ for (iy = gc->top; iy < gc->top + gc->yspan; iy++)
+ ld->vexpand[toyindex(g, iy)] = true;
+ }
+ }
+
+ // 4) compute and assign expanded widths/heights
+ nhexpand = 0;
+ nvexpand = 0;
+ for (i = 0; i < xcount(g); i++)
+ if (ld->hexpand[i])
+ nhexpand++;
+ else
+ width -= ld->colwidths[i];
+ for (i = 0; i < ycount(g); i++)
+ if (ld->vexpand[i])
+ nvexpand++;
+ else
+ height -= ld->rowheights[i];
+ for (i = 0; i < xcount(g); i++)
+ if (ld->hexpand[i])
+ ld->colwidths[i] = width / nhexpand;
+ for (i = 0; i < ycount(g); i++)
+ if (ld->vexpand[i])
+ ld->rowheights[i] = height / nvexpand;
+
+ // 5) reset the final coordinates for the next step
+ for (i = 0; i < g->children->size(); i++) {
+ gc = (*(g->children))[i];
+ if (!uiControlVisible(gc->c))
+ continue;
+ gc->finalx = 0;
+ gc->finaly = 0;
+ gc->finalwidth = 0;
+ gc->finalheight = 0;
+ }
+
+ // 6) compute cell positions and sizes
+ for (iy = 0; iy < ycount(g); iy++) {
+ int curx;
+ int prev;
+
+ curx = 0;
+ prev = -1;
+ for (ix = 0; ix < xcount(g); ix++) {
+ if (!ld->visibleColumn(g, ix))
+ continue;
+ i = ld->gg[iy][ix];
+ if (i != -1) {
+ gc = (*(g->children))[i];
+ if (iy == toyindex(g, gc->top)) { // don't repeat this step if the control spans vertically
+ if (i != prev)
+ gc->finalx = curx;
+ else
+ gc->finalwidth += xpadding;
+ gc->finalwidth += ld->colwidths[ix];
+ }
+ }
+ curx += ld->colwidths[ix] + xpadding;
+ prev = i;
+ }
+ }
+ for (ix = 0; ix < xcount(g); ix++) {
+ int cury;
+ int prev;
+
+ cury = 0;
+ prev = -1;
+ for (iy = 0; iy < ycount(g); iy++) {
+ if (!ld->visibleRow(g, iy))
+ continue;
+ i = ld->gg[iy][ix];
+ if (i != -1) {
+ gc = (*(g->children))[i];
+ if (ix == toxindex(g, gc->left)) { // don't repeat this step if the control spans horizontally
+ if (i != prev)
+ gc->finaly = cury;
+ else
+ gc->finalheight += ypadding;
+ gc->finalheight += ld->rowheights[iy];
+ }
+ }
+ cury += ld->rowheights[iy] + ypadding;
+ prev = i;
+ }
+ }
+
+ // 7) everything as it stands now is set for xalign == Fill yalign == Fill; set the correct alignments
+ // this is why we saved minwidth/minheight above
+ for (i = 0; i < g->children->size(); i++) {
+ gc = (*(g->children))[i];
+ if (!uiControlVisible(gc->c))
+ continue;
+ if (gc->halign != uiAlignFill) {
+ switch (gc->halign) {
+ case uiAlignEnd:
+ gc->finalx += gc->finalwidth - gc->minwidth;
+ break;
+ case uiAlignCenter:
+ gc->finalx += (gc->finalwidth - gc->minwidth) / 2;
+ break;
+ }
+ gc->finalwidth = gc->minwidth; // for all three
+ }
+ if (gc->valign != uiAlignFill) {
+ switch (gc->valign) {
+ case uiAlignEnd:
+ gc->finaly += gc->finalheight - gc->minheight;
+ break;
+ case uiAlignCenter:
+ gc->finaly += (gc->finalheight - gc->minheight) / 2;
+ break;
+ }
+ gc->finalheight = gc->minheight; // for all three
+ }
+ }
+
+ // 8) and FINALLY we resize
+ for (iy = 0; iy < ycount(g); iy++)
+ for (ix = 0; ix < xcount(g); ix++) {
+ i = ld->gg[iy][ix];
+ if (i != -1) { // treat empty cells like spaces
+ gc = (*(g->children))[i];
+ uiWindowsEnsureMoveWindowDuringResize(
+ (HWND) uiControlHandle(gc->c),
+ gc->finalx,//TODO + x,
+ gc->finaly,//TODO + y,
+ gc->finalwidth,
+ gc->finalheight);
+ }
+ }
+
+ delete ld;
+}
+
+static void uiGridDestroy(uiControl *c)
+{
+ uiGrid *g = uiGrid(c);
+
+ for (struct gridChild *gc : *(g->children)) {
+ uiControlSetParent(gc->c, NULL);
+ uiControlDestroy(gc->c);
+ uiFree(gc);
+ }
+ delete g->indexof;
+ delete g->children;
+ uiWindowsEnsureDestroyWindow(g->hwnd);
+ uiFreeControl(uiControl(g));
+}
+
+uiWindowsControlDefaultHandle(uiGrid)
+uiWindowsControlDefaultParent(uiGrid)
+uiWindowsControlDefaultSetParent(uiGrid)
+uiWindowsControlDefaultToplevel(uiGrid)
+uiWindowsControlDefaultVisible(uiGrid)
+uiWindowsControlDefaultShow(uiGrid)
+uiWindowsControlDefaultHide(uiGrid)
+uiWindowsControlDefaultEnabled(uiGrid)
+uiWindowsControlDefaultEnable(uiGrid)
+uiWindowsControlDefaultDisable(uiGrid)
+
+static void uiGridSyncEnableState(uiWindowsControl *c, int enabled)
+{
+ uiGrid *g = uiGrid(c);
+
+ if (uiWindowsShouldStopSyncEnableState(uiWindowsControl(g), enabled))
+ return;
+ for (const struct gridChild *gc : *(g->children))
+ uiWindowsControlSyncEnableState(uiWindowsControl(gc->c), enabled);
+}
+
+uiWindowsControlDefaultSetParentHWND(uiGrid)
+
+static void uiGridMinimumSize(uiWindowsControl *c, int *width, int *height)
+{
+ uiGrid *g = uiGrid(c);
+ int xpadding, ypadding;
+ gridLayoutData *ld;
+ int x, y;
+ int i;
+ struct gridChild *gc;
+ int minwid, minht;
+ int colwidth, rowheight;
+
+ *width = 0;
+ *height = 0;
+ if (g->children->size() == 0)
+ return; // nothing to do
+
+ gridPadding(g, &xpadding, &ypadding);
+ ld = new gridLayoutData(g);
+ if (ld->noVisible) { // nothing to do; return 0x0
+ delete ld;
+ return;
+ }
+
+ // 1) compute colwidths and rowheights before handling expansion
+ // TODO put this in its own function (but careful about the spanning calculation in gridRelayout())
+ for (y = 0; y < ycount(g); y++)
+ for (x = 0; x < xcount(g); x++) {
+ i = ld->gg[y][x];
+ if (i == -1)
+ continue;
+ gc = (*(g->children))[i];
+ uiWindowsControlMinimumSize(uiWindowsControl(gc->c), &minwid, &minht);
+ // allot equal space in the presence of spanning to keep things sane
+ if (ld->colwidths[x] < minwid / gc->xspan)
+ ld->colwidths[x] = minwid / gc->xspan;
+ if (ld->rowheights[y] < minht / gc->yspan)
+ ld->rowheights[y] = minht / gc->yspan;
+ // save these for step 6
+ gc->minwidth = minwid;
+ gc->minheight = minht;
+ }
+
+ // 2) compute total column width/row height
+ colwidth = 0;
+ rowheight = 0;
+ for (x = 0; x < xcount(g); x++)
+ colwidth += ld->colwidths[x];
+ for (y = 0; y < ycount(g); y++)
+ rowheight += ld->rowheights[y];
+
+ // and that's it; just account for padding
+ *width = colwidth + (ld->nVisibleColumns - 1) * xpadding;
+ *height = rowheight + (ld->nVisibleRows - 1) * ypadding;
+}
+
+static void uiGridMinimumSizeChanged(uiWindowsControl *c)
+{
+ uiGrid *g = uiGrid(c);
+
+ if (uiWindowsControlTooSmall(uiWindowsControl(g))) {
+ uiWindowsControlContinueMinimumSizeChanged(uiWindowsControl(g));
+ return;
+ }
+ gridRelayout(g);
+}
+
+uiWindowsControlDefaultLayoutRect(uiGrid)
+uiWindowsControlDefaultAssignControlIDZOrder(uiGrid)
+
+static void uiGridChildVisibilityChanged(uiWindowsControl *c)
+{
+ // TODO eliminate the redundancy
+ uiWindowsControlMinimumSizeChanged(c);
+}
+
+// must have called gridRecomputeMinMax() first
+static void gridArrangeChildren(uiGrid *g)
+{
+ LONG_PTR controlID;
+ HWND insertAfter;
+ gridLayoutData *ld;
+ bool *visited;
+ int x, y;
+ int i;
+ struct gridChild *gc;
+
+ if (g->children->size() == 0)
+ return; // nothing to do
+ ld = new gridLayoutData(g);
+ controlID = 100;
+ insertAfter = NULL;
+ visited = new bool[g->children->size()];
+ ZeroMemory(visited, g->children->size() * sizeof (bool));
+ for (y = 0; y < ycount(g); y++)
+ for (x = 0; x < xcount(g); x++) {
+ i = ld->gg[y][x];
+ if (i == -1)
+ continue;
+ if (visited[i])
+ continue;
+ visited[i] = true;
+ gc = (*(g->children))[i];
+ uiWindowsControlAssignControlIDZOrder(uiWindowsControl(gc->c), &controlID, &insertAfter);
+ }
+ delete[] visited;
+ delete ld;
+}
+
+static struct gridChild *toChild(uiControl *c, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign)
+{
+ struct gridChild *gc;
+
+ if (xspan < 0)
+ userbug("You cannot have a negative xspan in a uiGrid cell.");
+ if (yspan < 0)
+ userbug("You cannot have a negative yspan in a uiGrid cell.");
+ gc = uiNew(struct gridChild);
+ gc->c = c;
+ gc->xspan = xspan;
+ gc->yspan = yspan;
+ gc->hexpand = hexpand;
+ gc->halign = halign;
+ gc->vexpand = vexpand;
+ gc->valign = valign;
+ return gc;
+}
+
+static void add(uiGrid *g, struct gridChild *gc)
+{
+ uiControlSetParent(gc->c, uiControl(g));
+ uiWindowsControlSetParentHWND(uiWindowsControl(gc->c), g->hwnd);
+ g->children->push_back(gc);
+ (*(g->indexof))[gc->c] = g->children->size() - 1;
+ gridRecomputeMinMax(g);
+ gridArrangeChildren(g);
+ uiWindowsControlMinimumSizeChanged(uiWindowsControl(g));
+}
+
+void uiGridAppend(uiGrid *g, uiControl *c, int left, int top, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign)
+{
+ struct gridChild *gc;
+
+ gc = toChild(c, xspan, yspan, hexpand, halign, vexpand, valign);
+ gc->left = left;
+ gc->top = top;
+ add(g, gc);
+}
+
+// TODO decide what happens if existing is NULL
+void uiGridInsertAt(uiGrid *g, uiControl *c, uiControl *existing, uiAt at, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign)
+{
+ struct gridChild *gc;
+ struct gridChild *other;
+
+ gc = toChild(c, xspan, yspan, hexpand, halign, vexpand, valign);
+ other = (*(g->children))[(*(g->indexof))[existing]];
+ switch (at) {
+ case uiAtLeading:
+ gc->left = other->left - gc->xspan;
+ gc->top = other->top;
+ break;
+ case uiAtTop:
+ gc->left = other->left;
+ gc->top = other->top - gc->yspan;
+ break;
+ case uiAtTrailing:
+ gc->left = other->left + other->xspan;
+ gc->top = other->top;
+ break;
+ case uiAtBottom:
+ gc->left = other->left;
+ gc->top = other->top + other->yspan;
+ break;
+ // TODO add error checks to ALL enums
+ }
+ add(g, gc);
+}
+
+int uiGridPadded(uiGrid *g)
+{
+ return g->padded;
+}
+
+void uiGridSetPadded(uiGrid *g, int padded)
+{
+ g->padded = padded;
+ uiWindowsControlMinimumSizeChanged(uiWindowsControl(g));
+}
+
+static void onResize(uiWindowsControl *c)
+{
+ gridRelayout(uiGrid(c));
+}
+
+uiGrid *uiNewGrid(void)
+{
+ uiGrid *g;
+
+ uiWindowsNewControl(uiGrid, g);
+
+ g->hwnd = uiWindowsMakeContainer(uiWindowsControl(g), onResize);
+
+ g->children = new std::vector<struct gridChild *>;
+ g->indexof = new std::map<uiControl *, size_t>;
+
+ return g;
+}