From 94b617340fe9a042b946cc83598a3ef051cdc900 Mon Sep 17 00:00:00 2001 From: Leonard Ritter Date: Tue, 23 Sep 2014 00:52:51 +0200 Subject: [PATCH] added UI_WRAP mode and example --- example.cpp | 57 +++++++++- oui.h | 311 +++++++++++++++++++++++++++++++++++----------------- 2 files changed, 267 insertions(+), 101 deletions(-) diff --git a/example.cpp b/example.cpp index 79fb3cf..fa424b4 100644 --- a/example.cpp +++ b/example.cpp @@ -861,6 +861,59 @@ void build_columndemo(int parent) { } } +void fill_wrap_box(int box) { + const int M = 5; + const int S = 100; + const int T = 50; + + srand(303); + for (int i = 0; i < 20; ++i) { + float hue = (float)(rand()%360)/360.0f; + int width = 10 + (rand()%5)*10; + + switch(rand()%4) { + default: break; + case 0: { + demorect(box, "Layout( UI_TOP )", hue, 0, UI_TOP, width, T, M, M, M, M); + } break; + case 1: { + demorect(box, "Layout( UI_VCENTER )", hue, 0, UI_VCENTER, width, T/2, M, 0, M, 0); + } break; + case 2: { + demorect(box, "Layout( UI_VFILL )", hue, 0, UI_VFILL, width, T, M, M, M, M); + } break; + case 3: { + demorect(box, "Layout( UI_DOWN )", hue, 0, UI_DOWN, width, T/2, M, M, M, M); + } break; + } + } + +} + +void build_wrapdemo(int parent) { + int col = uiItem(); + uiAppend(parent, col); + uiSetBox(col, UI_COLUMN); + uiSetLayout(col, UI_HFILL|UI_TOP); + + const int M = 5; + const int S = 100; + const int T = 50; + + int box; + box = demorect(col, "Box( UI_ROW | UI_WRAP | UI_START )\nLayout( UI_HFILL | UI_TOP )", 0.6f, UI_ROW | UI_WRAP | UI_START, UI_HFILL | UI_TOP, 0, 0, M, M, M, M); + fill_wrap_box(box); + + box = demorect(col, "Box( UI_ROW | UI_WRAP | UI_MIDDLE )\nLayout( UI_HFILL | UI_TOP )", 0.6f, UI_ROW | UI_WRAP, UI_HFILL | UI_TOP, 0, 0, M, M, M, M); + fill_wrap_box(box); + + box = demorect(col, "Box( UI_ROW | UI_WRAP | UI_END )\nLayout( UI_HFILL | UI_TOP )", 0.6f, UI_ROW | UI_WRAP | UI_END, UI_HFILL | UI_TOP, 0, 0, M, M, M, M); + fill_wrap_box(box); + + box = demorect(col, "Box( UI_ROW | UI_WRAP | UI_JUSTIFY )\nLayout( UI_HFILL | UI_TOP )", 0.6f, UI_ROW | UI_WRAP | UI_JUSTIFY, UI_HFILL | UI_TOP, 0, 0, M, M, M, M); + fill_wrap_box(box); +} + int add_menu_option(int parent, const char *name, int *choice) { int opt = radio(-1, name, choice); @@ -898,7 +951,7 @@ void draw(NVGcontext *vg, float w, float h) { int opt_column = add_menu_option(menu, "UI_COLUMN", &choice); int opt_wrap = add_menu_option(menu, "UI_WRAP", &choice); if (choice < 0) - choice = opt_blendish_demo; + choice = opt_wrap; int content = uiItem(); uiSetLayout(content, UI_FILL); @@ -925,6 +978,8 @@ void draw(NVGcontext *vg, float w, float h) { build_rowdemo(content); } else if (choice == opt_column) { build_columndemo(content); + } else if (choice == opt_wrap) { + build_wrapdemo(content); } uiLayout(); diff --git a/oui.h b/oui.h index 85b7236..6af0f80 100644 --- a/oui.h +++ b/oui.h @@ -251,9 +251,16 @@ typedef enum UIboxFlags { // multi-line, wrap left to right UI_WRAP = 0x004, + // justify-content (start, end, center, space-between) - // can be implemented by putting a flex container in a layout container, - // then using UI_LEFT, UI_RIGHT, UI_HFILL, UI_HCENTER, etc. + // at start of row/column + UI_START = 0x008, + // at center of row/column + UI_MIDDLE = 0x000, + // at end of row/column + UI_END = 0x010, + // insert spacing to stretch across whole row/column + UI_JUSTIFY = 0x018, // align-items // can be implemented by putting a flex container in a layout container, @@ -641,7 +648,7 @@ OUI_EXPORT short uiGetMarginDown(int item); // extra item flags enum { // bit 0-2 - UI_ITEM_BOX_MASK = 0x000007, + UI_ITEM_BOX_MASK = 0x00001F, // bit 5-8 UI_ITEM_LAYOUT_MASK = 0x0001E0, // bit 9-18 @@ -652,6 +659,8 @@ enum { UI_ITEM_DATA = 0x100000, // item has been inserted UI_ITEM_INSERTED = 0x200000, + // item is on a new line (wrap marker) + UI_ITEM_NEWLINE = 0x400000, }; typedef struct UIitem { @@ -1049,15 +1058,14 @@ short uiGetMarginDown(int item) { } // compute bounding box of all items super-imposed -UI_INLINE void uiComputeImposedSizeDim(UIitem *pitem, int dim) { +UI_INLINE void uiComputeImposedSize(UIitem *pitem, int dim) { int wdim = dim+2; - if (pitem->size[dim]) - return; // largest size is required size short need_size = 0; int kid = pitem->firstkid; while (kid >= 0) { UIitem *pkid = uiItemPtr(kid); + // width = start margin + calculated width + end margin int kidsize = pkid->margins[dim] + pkid->size[dim] + pkid->margins[wdim]; need_size = ui_max(need_size, kidsize); @@ -1067,10 +1075,8 @@ UI_INLINE void uiComputeImposedSizeDim(UIitem *pitem, int dim) { } // compute bounding box of all items stacked -UI_INLINE void uiComputeStackedSizeDim(UIitem *pitem, int dim) { +UI_INLINE void uiComputeStackedSize(UIitem *pitem, int dim) { int wdim = dim+2; - if (pitem->size[dim]) - return; short need_size = 0; int kid = pitem->firstkid; while (kid >= 0) { @@ -1082,102 +1088,164 @@ UI_INLINE void uiComputeStackedSizeDim(UIitem *pitem, int dim) { pitem->size[dim] = need_size; } -static void uiComputeBestSize(int item, int dim) { +// compute bounding box of all items stacked + wrapped +UI_INLINE void uiComputeWrappedSize(UIitem *pitem, int dim) { + int wdim = dim+2; + + short need_size = 0; + short need_size2 = 0; + int kid = pitem->firstkid; + while (kid >= 0) { + UIitem *pkid = uiItemPtr(kid); + + // if next position moved back, we assume a new line + if (pkid->flags & UI_ITEM_NEWLINE) { + need_size2 += need_size; + // newline + need_size = 0; + } + + // width = start margin + calculated width + end margin + int kidsize = pkid->margins[dim] + pkid->size[dim] + pkid->margins[wdim]; + need_size = ui_max(need_size, kidsize); + kid = uiNextSibling(kid); + } + pitem->size[dim] = need_size2 + need_size; +} + +static void uiComputeSize(int item, int dim) { UIitem *pitem = uiItemPtr(item); // children expand the size - int kid = uiFirstChild(item); + int kid = pitem->firstkid; while (kid >= 0) { - uiComputeBestSize(kid, dim); + uiComputeSize(kid, dim); kid = uiNextSibling(kid); } - if(pitem->flags & UI_FLEX) { - // flex model - if ((pitem->flags & 1) == (unsigned int)dim) // direction - uiComputeStackedSizeDim(pitem, dim); - else - uiComputeImposedSizeDim(pitem, dim); + if (pitem->size[dim]) { + return; + } else if(pitem->flags & UI_FLEX) { + if (pitem->flags & UI_WRAP) { + // flex model + if ((pitem->flags & 1) == (unsigned int)dim) // direction + uiComputeStackedSize(pitem, dim); + else + uiComputeWrappedSize(pitem, dim); + } else { + // flex model + if ((pitem->flags & 1) == (unsigned int)dim) // direction + uiComputeStackedSize(pitem, dim); + else + uiComputeImposedSize(pitem, dim); + } } else { // layout model - uiComputeImposedSizeDim(pitem, dim); + uiComputeImposedSize(pitem, dim); } } // stack all items according to their alignment -UI_INLINE void uiLayoutStackedItemDim(UIitem *pitem, int dim) { +UI_INLINE void uiArrangeStacked(UIitem *pitem, int dim, bool wrap) { int wdim = dim+2; short space = pitem->size[dim]; - short used = 0; - int count = 0; - int total = 0; - // first pass: count items that need to be expanded, - // and the space that is used - int kid = pitem->firstkid; - while (kid >= 0) { - UIitem *pkid = uiItemPtr(kid); - int flags = (pkid->flags & UI_ITEM_LAYOUT_MASK) >> dim; - total++; - if ((flags & UI_HFILL) == UI_HFILL) { // grow - count++; - used += pkid->margins[dim] + pkid->margins[wdim]; - } else { - used += pkid->margins[dim] + pkid->size[dim] + pkid->margins[wdim]; - } - kid = uiNextSibling(kid); - } + int start_kid = pitem->firstkid; + while (start_kid >= 0) { + short used = 0; - int extra_space = ui_max(space - used,0); - float filler = 0.0f; - float spacer = 0.0f; + int count = 0; + int total = 0; + // first pass: count items that need to be expanded, + // and the space that is used + int kid = start_kid; + int end_kid = -1; + while (kid >= 0) { + UIitem *pkid = uiItemPtr(kid); + int flags = (pkid->flags & UI_ITEM_LAYOUT_MASK) >> dim; + total++; + short extend = used; + if ((flags & UI_HFILL) == UI_HFILL) { // grow + count++; + extend += pkid->margins[dim] + pkid->margins[wdim]; + } else { + extend += pkid->margins[dim] + pkid->size[dim] + pkid->margins[wdim]; + } + if (wrap && (extend > space) && (total>1)) { + end_kid = kid; + // add marker for subsequent queries + pkid->flags |= UI_ITEM_NEWLINE; + break; + } else { + used = extend; + kid = uiNextSibling(kid); + } + } - if (extra_space) { - if (count) { - filler = (float)extra_space / (float)count; - } else if (total) { - spacer = (float)extra_space / (float)(total-1); + int extra_space = ui_max(space - used,0); + float filler = 0.0f; + float spacer = 0.0f; + float extra_margin = 0.0f; + + if (extra_space) { + if (count) { + filler = (float)extra_space / (float)count; + } else if (total) { + switch(pitem->flags & UI_JUSTIFY) { + default: { + extra_margin = extra_space / 2.0f; + } break; + case UI_JUSTIFY: { + if (!wrap || (end_kid != -1)) + spacer = (float)extra_space / (float)(total-1); + } break; + case UI_START: { + } break; + case UI_END: { + extra_margin = extra_space; + } break; + } + } } - } - // distribute width among items - float x = (float)pitem->margins[dim]; - float x1; - float extra_margin = 0.0f; - // second pass: distribute and rescale - kid = pitem->firstkid; - while (kid >= 0) { - short ix0,ix1; - UIitem *pkid = uiItemPtr(kid); - int flags = (pkid->flags & UI_ITEM_LAYOUT_MASK) >> dim; + // distribute width among items + float x = (float)pitem->margins[dim]; + float x1; + // second pass: distribute and rescale + kid = start_kid; + while (kid != end_kid) { + short ix0,ix1; + UIitem *pkid = uiItemPtr(kid); + int flags = (pkid->flags & UI_ITEM_LAYOUT_MASK) >> dim; + + x += (float)pkid->margins[dim] + extra_margin; + if ((flags & UI_HFILL) == UI_HFILL) { // grow + x1 = x+filler; + } else { + x1 = x+(float)pkid->size[dim]; + } + ix0 = (short)x; + ix1 = (short)x1; + pkid->margins[dim] = ix0; + pkid->size[dim] = ix1-ix0; + x = x1 + (float)pkid->margins[wdim]; - x += (float)pkid->margins[dim] + extra_margin; - if ((flags & UI_HFILL) == UI_HFILL) { // grow - x1 = x+filler; - } else { - x1 = x+(float)pkid->size[dim]; + kid = uiNextSibling(kid); + extra_margin = spacer; } - ix0 = (short)x; - ix1 = (short)x1; - pkid->margins[dim] = ix0; - pkid->size[dim] = ix1-ix0; - x = x1 + (float)pkid->margins[wdim]; - kid = uiNextSibling(kid); - extra_margin = spacer; + start_kid = end_kid; } } // superimpose all items according to their alignment -UI_INLINE void uiLayoutImposedItemDim(UIitem *pitem, int dim) { +UI_INLINE void uiArrangeImposedRange(UIitem *pitem, int dim, + int start_kid, int end_kid, short offset, short space) { int wdim = dim+2; - short space = pitem->size[dim]; - short offset = pitem->margins[dim]; - - int kid = pitem->firstkid; - while (kid >= 0) { + int kid = start_kid; + while (kid != end_kid) { UIitem *pkid = uiItemPtr(kid); int flags = (pkid->flags & UI_ITEM_LAYOUT_MASK) >> dim; @@ -1200,27 +1268,87 @@ UI_INLINE void uiLayoutImposedItemDim(UIitem *pitem, int dim) { } } -static void uiLayoutItem(int item, int dim) { +UI_INLINE void uiArrangeImposed(UIitem *pitem, int dim) { + uiArrangeImposedRange(pitem, dim, pitem->firstkid, -1, pitem->margins[dim], pitem->size[dim]); +} + +// superimpose all items according to their alignment +UI_INLINE void uiArrangeWrappedImposed(UIitem *pitem, int dim) { + int wdim = dim+2; + + short offset = pitem->margins[dim]; + + short need_size = 0; + int kid = pitem->firstkid; + int start_kid = kid; + while (kid >= 0) { + UIitem *pkid = uiItemPtr(kid); + + if (pkid->flags & UI_ITEM_NEWLINE) { + uiArrangeImposedRange(pitem, dim, start_kid, kid, offset, need_size); + offset += need_size; + start_kid = kid; + // newline + need_size = 0; + } + + // width = start margin + calculated width + end margin + int kidsize = pkid->margins[dim] + pkid->size[dim] + pkid->margins[wdim]; + need_size = ui_max(need_size, kidsize); + kid = uiNextSibling(kid); + } + + uiArrangeImposedRange(pitem, dim, start_kid, -1, offset, need_size); +} + +static void uiArrange(int item, int dim) { UIitem *pitem = uiItemPtr(item); if(pitem->flags & UI_FLEX) { - // flex model - if ((pitem->flags & 1) == (unsigned int)dim) // direction - uiLayoutStackedItemDim(pitem, dim); - else - uiLayoutImposedItemDim(pitem, dim); + if (pitem->flags & UI_WRAP) { + if ((pitem->flags & 1) == (unsigned int)dim) { // direction + uiArrangeStacked(pitem, dim, true); + } else { + uiArrangeWrappedImposed(pitem, dim); + } + } else { + // flex model + if ((pitem->flags & 1) == (unsigned int)dim) // direction + uiArrangeStacked(pitem, dim, false); + else + uiArrangeImposed(pitem, dim); + } } else { // layout model - uiLayoutImposedItemDim(pitem, dim); + uiArrangeImposed(pitem, dim); } int kid = uiFirstChild(item); while (kid >= 0) { - uiLayoutItem(kid, dim); + uiArrange(kid, dim); kid = uiNextSibling(kid); } } +static void uiNestedLayout(int item, int dim) { + uiComputeSize(item,dim); + uiArrange(item,dim); + + uiComputeSize(item,1-dim); + uiArrange(item,1-dim); +} + +void uiLayout() { + assert(ui_context); + if (!ui_context->count) return; + + uiNestedLayout(0,0); + + uiValidateStateItems(); + // drawing routines may require this to be set already + uiUpdateHotItem(); +} + UIrect uiGetRect(int item) { UIitem *pitem = uiItemPtr(item); UIrect rc = {{{ @@ -1314,23 +1442,6 @@ int uiFindItemForEvent(int item, UIevent event, int x, int y) { return -1; } -void uiLayout() { - assert(ui_context); - if (!ui_context->count) return; - - // compute widths - uiComputeBestSize(0,0); - uiLayoutItem(0,0); - - // compute heights - uiComputeBestSize(0,1); - uiLayoutItem(0,1); - - uiValidateStateItems(); - // drawing routines may require this to be set already - uiUpdateHotItem(); -} - void uiUpdateHotItem() { assert(ui_context); if (!ui_context->count) return;