diff --git a/example.cpp b/example.cpp index 6e0158d..858b875 100644 --- a/example.cpp +++ b/example.cpp @@ -30,6 +30,10 @@ typedef enum { ST_RADIO = 2, // progress slider ST_SLIDER = 3, + // column + ST_COLUMN = 4, + // row + ST_ROW = 5, } SubType; typedef struct { @@ -66,26 +70,39 @@ void init(NVGcontext *vg) { // the container the item is in has negative spacing, and the item // is first or last element in a sequence of 2 or more elements. int cornerFlags(int item) { - /* int parent = uiParent(item); - int spacing = uiGetSpacing(parent); - if (spacing >= 0) return BND_CORNER_NONE; int numkids = uiGetChildCount(parent); - int numid = uiGetChildId(item); if (numkids < 2) return BND_CORNER_NONE; - UIuvec2 flags = uiGetLayoutFlags(parent); - if (flags.x & UI_LAYOUT_PACK) { - if (!numid) return BND_CORNER_RIGHT; - else if (numid == numkids-1) return BND_CORNER_LEFT; - else return BND_CORNER_ALL; - } else if (flags.y & UI_LAYOUT_PACK) { - if (!numid) return BND_CORNER_DOWN; - else if (numid == numkids-1) return BND_CORNER_TOP; - else return BND_CORNER_ALL; - }*/ + const UIData *head = (const UIData *)uiGetData(parent); + if (head) { + int numid = uiGetChildId(item); + switch(head->subtype) { + case ST_COLUMN: { + if (!numid) return BND_CORNER_DOWN; + else if (numid == numkids-1) return BND_CORNER_TOP; + else return BND_CORNER_ALL; + } break; + case ST_ROW: { + if (!numid) return BND_CORNER_RIGHT; + else if (numid == numkids-1) return BND_CORNER_LEFT; + else return BND_CORNER_ALL; + } break; + default: break; + } + } return BND_CORNER_NONE; } +void testrect(NVGcontext *vg, UIrect rect) { +#if 0 + nvgBeginPath(vg); + nvgRect(vg,rect.x+0.5,rect.y+0.5,rect.w-1,rect.h-1); + nvgStrokeColor(vg,nvgRGBf(1,0,0)); + nvgStrokeWidth(vg,1); + nvgStroke(vg); +#endif +} + void drawUI(NVGcontext *vg, int item, int x, int y) { const UIData *head = (const UIData *)uiGetData(item); UIrect rect = uiGetRect(item); @@ -93,7 +110,9 @@ void drawUI(NVGcontext *vg, int item, int x, int y) { rect.y += y; if (head) { switch(head->subtype) { - default: + default: { + testrect(vg,rect); + } break; case ST_LABEL: { assert(head); const UIButtonData *data = (UIButtonData*)head; @@ -126,11 +145,7 @@ void drawUI(NVGcontext *vg, int item, int x, int y) { } break; } } else { - nvgBeginPath(vg); - nvgRect(vg,rect.x+0.5,rect.y+0.5,rect.w-1,rect.h-1); - nvgStrokeColor(vg,nvgRGBf(1,0,0)); - nvgStrokeWidth(vg,1); - nvgStroke(vg); + testrect(vg,rect); } int kid = uiFirstChild(item); @@ -140,58 +155,92 @@ void drawUI(NVGcontext *vg, int item, int x, int y) { } } -int label(int iconid, const char *label) { +int label(int parent, int iconid, const char *label) { int item = uiItem(); uiSetSize(item, 0, BND_WIDGET_HEIGHT); UIButtonData *data = (UIButtonData *)uiAllocData(item, sizeof(UIButtonData)); data->head.subtype = ST_LABEL; data->iconid = iconid; data->label = label; + uiAppend(parent, item); return item; } -int button(UIhandle handle, int iconid, const char *label, +void demohandler(int item, UIevent event) { + const UIButtonData *data = (const UIButtonData *)uiGetData(item); + printf("clicked: %lld %s\n", uiGetHandle(item), data->label); +} + +int button(int parent, UIhandle handle, int iconid, const char *label, UIhandler handler) { - int item = uiItem(); + // create new ui item + int item = uiItem(); + // set persistent handle for item that is used + // to track activity over time uiSetHandle(item, handle); + // set size of wiget; horizontal size is dynamic, vertical is fixed uiSetSize(item, 0, BND_WIDGET_HEIGHT); + // attach event handler e.g. demohandler above uiSetHandler(item, handler, UI_BUTTON0_HOT_UP); + // store some custom data with the button that we use for styling UIButtonData *data = (UIButtonData *)uiAllocData(item, sizeof(UIButtonData)); data->head.subtype = ST_BUTTON; data->iconid = iconid; data->label = label; + uiAppend(parent, item); return item; } // simple logic for a slider + +// starting offset of the currently active slider static float sliderstart = 0.0; + +// event handler for slider (same handler for all sliders) void sliderhandler(int item, UIevent event) { + // retrieve the custom data we saved with the slider UISliderData *data = (UISliderData *)uiGetData(item); switch(event) { default: break; case UI_BUTTON0_DOWN: { + // button was pressed for the first time; capture initial + // slider value. sliderstart = *data->progress; } break; case UI_BUTTON0_CAPTURE: { + // called for every frame that the button is pressed. + // get the delta between the click point and the current + // mouse position UIvec2 pos = uiGetCursorStartDelta(); + // get the items layouted rectangle UIrect rc = uiGetRect(item); + // calculate our new offset and clamp float value = sliderstart + ((float)pos.x / (float)rc.w); value = (value<0)?0:(value>1)?1:value; + // assign the new value *data->progress = value; } break; } } -int slider(UIhandle handle, const char *label, float *progress) { +int slider(int parent, UIhandle handle, const char *label, float *progress) { + // create new ui item int item = uiItem(); + // set persistent handle for item that is used + // to track activity over time uiSetHandle(item, handle); + // set size of wiget; horizontal size is dynamic, vertical is fixed uiSetSize(item, 0, BND_WIDGET_HEIGHT); + // attach our slider event handler and capture two classes of events uiSetHandler(item, sliderhandler, UI_BUTTON0_DOWN | UI_BUTTON0_CAPTURE); + // store some custom data with the button that we use for styling + // and logic, e.g. the pointer to the data we want to alter. UISliderData *data = (UISliderData *)uiAllocData(item, sizeof(UISliderData)); data->head.subtype = ST_SLIDER; data->label = label; data->progress = progress; + uiAppend(parent, item); return item; } @@ -201,7 +250,7 @@ void radiohandler(int item, UIevent event) { *data->value = uiGetChildId(item); } -int radio(UIhandle handle, int iconid, const char *label, int *value) { +int radio(int parent, UIhandle handle, int iconid, const char *label, int *value) { int item = uiItem(); uiSetHandle(item, handle); uiSetSize(item, label?0:BND_TOOL_WIDTH, BND_WIDGET_HEIGHT); @@ -211,30 +260,82 @@ int radio(UIhandle handle, int iconid, const char *label, int *value) { data->label = label; data->value = value; uiSetHandler(item, radiohandler, UI_BUTTON0_DOWN); + uiAppend(parent, item); return item; } -int addVList(int parent, int item) { - int last = uiLastChild(parent); - uiSetRelativeTo(item, -1, last, -1, -1); +void columnhandler(int parent, UIevent event) { + int item = uiLastChild(parent); + int last = uiPrevSibling(item); + // mark the new item as positioned under the previous item + uiSetRelToTop(item, last); + // fill parent horizontally, anchor to previous item vertically uiSetLayout(item, UI_HFILL|UI_TOP); - uiSetParent(item, parent); + // if not the first item, add a margin of 1 uiSetMargins(item, 0, (last < 0)?0:1, 0, 0); +} + +int column(int parent) { + int item = uiItem(); + uiSetHandler(item, columnhandler, UI_APPEND); + uiAppend(parent, item); + return item; +} + +void vgrouphandler(int parent, UIevent event) { + int item = uiLastChild(parent); + int last = uiPrevSibling(item); + // mark the new item as positioned under the previous item + uiSetRelToTop(item, last); + // fill parent horizontally, anchor to previous item vertically + uiSetLayout(item, UI_HFILL|UI_TOP); + // if not the first item, add a margin + uiSetMargins(item, 0, (last < 0)?0:-2, 0, 0); +} + +int vgroup(int parent) { + int item = uiItem(); + UIData *data = (UIData *)uiAllocData(item, sizeof(UIData)); + data->subtype = ST_COLUMN; + uiSetHandler(item, vgrouphandler, UI_APPEND); + uiAppend(parent, item); return item; } -int addHGroup(int parent, int item) { - int last = uiLastChild(parent); - uiSetRelativeTo(item, last, -1, -1, -1); - uiSetLayout(item, UI_LEFT); - uiSetParent(item, parent); +void hgrouphandler(int parent, UIevent event) { + int item = uiLastChild(parent); + int last = uiPrevSibling(item); + uiSetRelToLeft(item, last); + if (last > 0) + uiSetRelToRight(last, item); + uiSetLayout(item, UI_LEFT|UI_RIGHT); uiSetMargins(item, (last < 0)?0:-1, 0, 0, 0); +} + +int hgroup(int parent) { + int item = uiItem(); + UIData *data = (UIData *)uiAllocData(item, sizeof(UIData)); + data->subtype = ST_ROW; + uiSetHandler(item, hgrouphandler, UI_APPEND); + uiAppend(parent, item); return item; } -void demohandler(int item, UIevent event) { - const UIButtonData *data = (const UIButtonData *)uiGetData(item); - printf("clicked: %lld %s\n", uiGetHandle(item), data->label); +void rowhandler(int parent, UIevent event) { + int item = uiLastChild(parent); + int last = uiPrevSibling(item); + uiSetRelToLeft(item, last); + if (last > 0) + uiSetRelToRight(last, item); + uiSetLayout(item, UI_LEFT|UI_RIGHT); + uiSetMargins(item, (last < 0)?0:8, 0, 0, 0); +} + +int row(int parent) { + int item = uiItem(); + uiSetHandler(item, rowhandler, UI_APPEND); + uiAppend(parent, item); + return item; } void draw(NVGcontext *vg, float w, float h) { @@ -427,31 +528,28 @@ void draw(NVGcontext *vg, float w, float h) { uiClear(); + int root = uiItem(); // position root element uiSetLayout(0,UI_LEFT|UI_TOP); uiSetMargins(0,600,10,0,0); uiSetSize(0,250,400); - int c = button(1, BND_ICONID(6,3), "Item 1", demohandler); - uiSetParent(c, 0); - uiSetSize(c, 100, 100); - uiSetLayout(c, UI_CENTER); - + int col = column(0); + uiSetLayout(col, UI_TOP|UI_HFILL); - addVList(0, button(1, BND_ICONID(6,3), "Item 1", demohandler)); - addVList(0, button(2, BND_ICONID(6,3), "Item 2", demohandler)); + button(col, 1, BND_ICONID(6,3), "Item 1", demohandler); + button(col, 2, BND_ICONID(6,3), "Item 2", demohandler); static int enum1 = 0; { - int h = addVList(0, uiItem()); - addHGroup(h, radio(3, BND_ICONID(6,3), "Item 3.0", &enum1)); - addHGroup(h, radio(4, BND_ICONID(0,10), NULL, &enum1)); - addHGroup(h, radio(5, BND_ICONID(1,10), NULL, &enum1)); - addHGroup(h, radio(6, BND_ICONID(6,3), "Item 3.3", &enum1)); + int h = hgroup(col); + radio(h, 3, BND_ICONID(6,3), "Item 3.0", &enum1); + radio(h, 4, BND_ICONID(0,10), NULL, &enum1); + radio(h, 5, BND_ICONID(1,10), NULL, &enum1); + radio(h, 6, BND_ICONID(6,3), "Item 3.3", &enum1); } - /* static float progress1 = 0.25f; static float progress2 = 0.75f; @@ -470,7 +568,6 @@ void draw(NVGcontext *vg, float w, float h) { } button(col, 11, BND_ICONID(6,3), "Item 5", NULL); - */ uiProcess(); diff --git a/oui.h b/oui.h index 4b2347e..cd7805c 100644 --- a/oui.h +++ b/oui.h @@ -93,6 +93,8 @@ typedef enum UIevent { UI_BUTTON0_HOT_UP = 0x04, // item is being captured (button 0 constantly pressed) UI_BUTTON0_CAPTURE = 0x08, + // item has been added to container + UI_APPEND = 0x10, } UIevent; // handler callback; event is one of UI_EVENT_* @@ -164,10 +166,9 @@ void uiClear(); int uiItem(); // assign an item to a container. -// parent is the item ID of the containing item; an item ID of 0 refers to the -// root item. -// if item is already assigned to a parent, an assertion will be thrown. -void uiSetParent(int item, int parent); +// an item ID of 0 refers to the root item. +// if child is already assigned to a parent, an assertion will be thrown. +int uiAppend(int item, int child); // layout all added items and update the internal state according to the // current cursor position and button states. @@ -203,11 +204,29 @@ int uiGetChildId(int item); // returns an items next sibling in the list of the parent containers children. // if item is 0 or the item is the last child item, -1 will be returned. int uiNextSibling(int item); +// returns an items previous sibling in the list of the parent containers +// children. +// if item is 0 or the item is the first child item, -1 will be returned. +int uiPrevSibling(int item); void uiSetSize(int item, int w, int h); +int uiGetWidth(int item); +int uiGetHeight(int item); void uiSetLayout(int item, int flags); +int uiGetLayout(int item); void uiSetMargins(int item, int t, int r, int b, int l); -void uiSetRelativeTo(int item, int titem, int ritem, int bitem, int litem); +int uiGetMarginLeft(int item); +int uiGetMarginTop(int item); +int uiGetMarginRight(int item); +int uiGetMarginDown(int item); +void uiSetRelToLeft(int item, int other); +int uiGetRelToLeft(int item); +void uiSetRelToTop(int item, int other); +int uiGetRelToTop(int item); +void uiSetRelToRight(int item, int other); +int uiGetRelToRight(int item); +void uiSetRelToDown(int item, int other); +int uiGetRelToDown(int item); // returns the items layout rectangle relative to the parent. If uiGetRect() // is called before uiProcess(), the values of the returned rectangle are @@ -293,6 +312,8 @@ typedef struct UIitem { int kidid; // index of next sibling with same parent int nextitem; + // index of previous sibling with same parent + int previtem; // one or multiple of UIlayoutFlags int layout_flags; @@ -303,6 +324,9 @@ typedef struct UIitem { int margins[4]; // neighbors to position borders to int relto[4]; + + // computed size + UIvec2 computed_size; // relative rect UIrect rect; @@ -439,20 +463,10 @@ UIitem *uiItemPtr(int item) { void uiClear() { assert(ui_context); - ui_context->count = 1; + ui_context->count = 0; ui_context->datasize = 0; ui_context->hot_item = -1; ui_context->active_item = -1; - - // init root object - UIitem *item = ui_context->items; - memset(item, 0, sizeof(UIitem)); - item->parent = -1; - item->firstkid = -1; - item->lastkid = -1; - item->nextitem = -1; - - item->data = -1; } int uiItem() { @@ -464,26 +478,37 @@ int uiItem() { item->firstkid = -1; item->lastkid = -1; item->nextitem = -1; + item->previtem = -1; item->data = -1; for (int i = 0; i < 4; ++i) item->relto[i] = -1; return idx; } -void uiSetParent(int item, int parent) { - assert(item > 0); - assert(uiParent(item) == -1); +void uiNotifyItem(int item, UIevent event) { UIitem *pitem = uiItemPtr(item); - UIitem *pparent = uiItemPtr(parent); - pitem->parent = parent; + if (pitem->handler && (pitem->event_flags & event)) { + pitem->handler(item, event); + } +} + +int uiAppend(int item, int child) { + assert(child > 0); + assert(uiParent(child) == -1); + UIitem *pitem = uiItemPtr(child); + UIitem *pparent = uiItemPtr(item); + pitem->parent = item; pitem->kidid = pparent->numkids++; if (pparent->lastkid < 0) { - pparent->firstkid = item; - pparent->lastkid = item; + pparent->firstkid = child; + pparent->lastkid = child; } else { - uiItemPtr(pparent->lastkid)->nextitem = item; - pparent->lastkid = item; + pitem->previtem = pparent->lastkid; + uiItemPtr(pparent->lastkid)->nextitem = child; + pparent->lastkid = child; } + uiNotifyItem(item, UI_APPEND); + return child; } void uiSetSize(int item, int w, int h) { @@ -492,9 +517,20 @@ void uiSetSize(int item, int w, int h) { pitem->size.y = h; } +int uiGetWidth(int item) { + return uiItemPtr(item)->size.x; +} + +int uiGetHeight(int item) { + return uiItemPtr(item)->size.y; +} + void uiSetLayout(int item, int flags) { - UIitem *pitem = uiItemPtr(item); - pitem->layout_flags = flags; + uiItemPtr(item)->layout_flags = flags; +} + +int uiGetLayout(int item) { + return uiItemPtr(item)->layout_flags; } void uiSetMargins(int item, int l, int t, int r, int b) { @@ -505,14 +541,54 @@ void uiSetMargins(int item, int l, int t, int r, int b) { pitem->margins[3] = b; } -void uiSetRelativeTo(int item, int litem, int titem, int ritem, int bitem) { - UIitem *pitem = uiItemPtr(item); - pitem->relto[0] = litem; - pitem->relto[1] = titem; - pitem->relto[2] = ritem; - pitem->relto[3] = bitem; +int uiGetMarginLeft(int item) { + return uiItemPtr(item)->margins[0]; +} +int uiGetMarginTop(int item) { + return uiItemPtr(item)->margins[1]; +} +int uiGetMarginRight(int item) { + return uiItemPtr(item)->margins[2]; +} +int uiGetMarginDown(int item) { + return uiItemPtr(item)->margins[3]; +} + + +void uiSetRelToLeft(int item, int other) { + assert((other < 0) || (uiParent(other) == uiParent(item))); + uiItemPtr(item)->relto[0] = other; +} + +int uiGetRelToLeft(int item) { + return uiItemPtr(item)->relto[0]; } +void uiSetRelToTop(int item, int other) { + assert((other < 0) || (uiParent(other) == uiParent(item))); + uiItemPtr(item)->relto[1] = other; +} +int uiGetRelToTop(int item) { + return uiItemPtr(item)->relto[1]; +} + +void uiSetRelToRight(int item, int other) { + assert((other < 0) || (uiParent(other) == uiParent(item))); + uiItemPtr(item)->relto[2] = other; +} +int uiGetRelToRight(int item) { + return uiItemPtr(item)->relto[2]; +} + +void uiSetRelToDown(int item, int other) { + assert((other < 0) || (uiParent(other) == uiParent(item))); + uiItemPtr(item)->relto[3] = other; +} +int uiGetRelToDown(int item) { + return uiItemPtr(item)->relto[3]; +} + + UI_INLINE int uiComputeChainSize(UIitem *pkid, int dim) { UIitem *pitem = pkid; int wdim = dim+2; @@ -560,6 +636,7 @@ UI_INLINE void uiComputeSizeDim(UIitem *pitem, int dim) { } pitem->rect.v[wdim] = size; + pitem->computed_size.v[dim] = size; } } @@ -577,28 +654,35 @@ static void uiComputeBestSize(int item) { uiComputeSizeDim(pitem, 1); } -static void uiLayoutChildItem(UIitem *pparent, UIitem *pitem, int dim) { +static void uiLayoutChildItem(UIitem *pparent, UIitem *pitem, int *dyncount, int dim) { if (pitem->visited & (4<visited |= (4<size.v[dim]) { + *dyncount = (*dyncount)+1; + } + int wdim = dim+2; int wl = 0; int wr = pparent->rect.v[wdim]; int flags = pitem->layout_flags>>dim; - if ((flags & UI_LEFT) && (pitem->relto[dim] > 0)) { + int hasl = (flags & UI_LEFT) && (pitem->relto[dim] > 0); + int hasr = (flags & UI_RIGHT) && (pitem->relto[wdim] > 0); + + if (hasl) { UIitem *pl = uiItemPtr(pitem->relto[dim]); - uiLayoutChildItem(pparent, pl, dim); - wl = pl->rect.v[dim]+pl->rect.v[wdim]; + uiLayoutChildItem(pparent, pl, dyncount, dim); + wl = pl->rect.v[dim]+pl->rect.v[wdim]+pl->margins[wdim]; wr -= wl; } - if ((flags & UI_RIGHT) && (pitem->relto[wdim] > 0)) { + if (hasr) { UIitem *pl = uiItemPtr(pitem->relto[wdim]); - uiLayoutChildItem(pparent, pl, dim); - wr = pl->rect.v[dim]-wl; + uiLayoutChildItem(pparent, pl, dyncount, dim); + wr = pl->rect.v[dim]-pl->margins[dim]-wl; } - + switch(flags & UI_HFILL) { default: case UI_HCENTER: { @@ -611,8 +695,28 @@ static void uiLayoutChildItem(UIitem *pparent, UIitem *pitem, int dim) { pitem->rect.v[dim] = wl+wr-pitem->rect.v[wdim]-pitem->margins[wdim]; } break; case UI_HFILL: { - pitem->rect.v[dim] = wl+pitem->margins[dim]; - pitem->rect.v[wdim] = wr-pitem->margins[dim]-pitem->margins[wdim]; + if (pitem->size.v[dim]) { // hard maximum size; can't stretch + if (hasl) + pitem->rect.v[dim] = wl+wr-pitem->rect.v[wdim]-pitem->margins[wdim]; + else + pitem->rect.v[dim] = wl+pitem->margins[dim]; + } else { + if (1) { //!pitem->rect.v[wdim]) { + int width = (pparent->rect.v[wdim] - pparent->computed_size.v[dim]); + int space = width / (*dyncount); + //int rest = width - space*(*dyncount); + if (!hasl) { + pitem->rect.v[dim] = wl+pitem->margins[dim]; + pitem->rect.v[wdim] = wr-pitem->margins[dim]-pitem->margins[wdim]; + } else { + pitem->rect.v[wdim] = space-pitem->margins[dim]-pitem->margins[wdim]; + pitem->rect.v[dim] = wl+wr-pitem->rect.v[wdim]-pitem->margins[wdim]; + } + } else { + pitem->rect.v[dim] = wl+pitem->margins[dim]; + pitem->rect.v[wdim] = wr-pitem->margins[dim]-pitem->margins[wdim]; + } + } } break; } } @@ -621,7 +725,8 @@ UI_INLINE void uiLayoutItemDim(UIitem *pitem, int dim) { int kid = pitem->firstkid; while (kid > 0) { UIitem *pkid = uiItemPtr(kid); - uiLayoutChildItem(pitem, pkid, dim); + int dyncount = 0; + uiLayoutChildItem(pitem, pkid, &dyncount, dim); kid = uiNextSibling(kid); } } @@ -655,6 +760,10 @@ int uiNextSibling(int item) { return uiItemPtr(item)->nextitem; } +int uiPrevSibling(int item) { + return uiItemPtr(item)->previtem; +} + int uiParent(int item) { return uiItemPtr(item)->parent; } @@ -730,14 +839,8 @@ int uiFindItem(int item, int x, int y) { return -1; } -void uiNotifyItem(int item, UIevent event) { - UIitem *pitem = uiItemPtr(item); - if (pitem->handler && (pitem->event_flags & event)) { - pitem->handler(item, event); - } -} - void uiProcess() { + if (!ui_context->count) return; uiComputeBestSize(0); // position root element rect uiItemPtr(0)->rect.x = uiItemPtr(0)->margins[0];