diff --git a/blendish.h b/blendish.h index cc66bf6..159a2ec 100644 --- a/blendish.h +++ b/blendish.h @@ -192,6 +192,13 @@ typedef enum BNDcornerFlags { // height of horizontal scrollbar #define BND_SCROLLBAR_HEIGHT 14 +// default vertical spacing +#define BND_VSPACING 1 +// default vertical spacing between groups +#define BND_VSPACING_GROUP 8 +// default horizontal spacing +#define BND_HSPACING 8 + //////////////////////////////////////////////////////////////////////////////// // set the current theme all widgets will be drawn with. diff --git a/example.cpp b/example.cpp index c1026c5..1acc4df 100644 --- a/example.cpp +++ b/example.cpp @@ -24,7 +24,28 @@ #ifndef _UI_H_ #define _UI_H_ +#define UI_COLD 0x0000 +#define UI_HOT 0x0001 +#define UI_ACTIVE 0x0002 + +// containers +#define UI_ROOT 0x0100 +#define UI_COLUMN 0x0101 +#define UI_ROW 0x0102 + +// controls +#define UI_BUTTON 0x0203 + +#define UI_MAX_ITEMS 4096 +#define UI_MAX_BUFFERSIZE 1048576 +#define UI_MAX_DEPTH 64 + typedef struct UIcontext UIcontext; +typedef unsigned long long UIhandle; + +typedef struct UIrect { + int x, y, w, h; +} UIrect; UIcontext *uiCreateContext(); void uiMakeCurrent(UIcontext *ctx); @@ -36,24 +57,83 @@ int uiGetButton(int button); void uiSetCursor(int x, int y); void uiGetCursor(int *x, int *y); +void uiClear(); + +int uiItem(int parent, UIhandle handle, int kind, + int w, int h, void *data, int size); +int uiColumn(int parent, int spacing); +int uiRow(int parent, int spacing); + +void uiLayout(); + +int uiFirstChild(int item); +int uiLastChild(int item); +int uiNextSibling(int item); +int uiParent(int item); + +void uiSetSpacing(int item, int spacing); +int uiGetSpacing(int item); + +int uiGetKind(int item); +UIrect uiGetRect(int item); +UIrect uiGetScreenRect(int item); +void uiSetRect(int item, int x, int y, int w, int h); +void *uiGetData(int item); +int uiGetChildId(int item); +int uiGetChildCount(int item); +int uiGetHandle(int item); +int uiGetState(int item); + #endif // _UI_H_ #ifdef UI_IMPLEMENTATION +#define UI_MAX_KIND 16 + +typedef struct UIitem { + UIhandle handle; + int parent; + // number of kids + int numkids; + // index of kid relative to parent + int kidid; + UIrect rect; + int absx,absy; + int kind; + int data; + int spacing; + // index of first kid + int firstkid; + // index of last kid + int lastkid; + // index of next sibling with same parent + int nextitem; +} UIitem; + struct UIcontext { - int cx, cy; unsigned long long buttons; + int cx, cy; + + UIhandle hot; + UIhandle active; + + int count; + UIitem items[UI_MAX_ITEMS]; + int datasize; + unsigned char data[UI_MAX_BUFFERSIZE]; }; static UIcontext *ui_context = NULL; UIcontext *uiCreateContext() { UIcontext *ctx = (UIcontext *)malloc(sizeof(UIcontext)); + memset(ctx, 0, sizeof(UIcontext)); return ctx; } void uiMakeCurrent(UIcontext *ctx) { ui_context = ctx; + uiClear(); } void uiDestroyContext(UIcontext *ctx) { @@ -85,17 +165,358 @@ void uiGetCursor(int *x, int *y) { *y = ui_context->cy; } +UIitem *uiItemPtr(int item) { + assert(ui_context && (item >= 0) && (item < ui_context->count)); + return ui_context->items + item; +} + +void uiClear() { + assert(ui_context); + ui_context->count = 1; + ui_context->datasize = 0; + ui_context->hot = 0; + + // 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->kind = UI_ROOT; + item->data = -1; +} + +int uiAllocItem(int parent, UIhandle handle, int kind, void *data, int size) { + assert(ui_context && (ui_context->count < UI_MAX_ITEMS)); + assert((size >= 0) && ((ui_context->datasize+size) <= UI_MAX_BUFFERSIZE)); + assert(parent >= 0); + int idx = ui_context->count++; + UIitem *item = uiItemPtr(idx); + memset(item, 0, sizeof(UIitem)); + item->parent = parent; + item->handle = handle; + item->firstkid = -1; + item->lastkid = -1; + item->nextitem = -1; + item->kidid = ui_context->items[parent].numkids++; + if (ui_context->items[parent].lastkid < 0) { + ui_context->items[parent].firstkid = idx; + ui_context->items[parent].lastkid = idx; + } else { + ui_context->items[ui_context->items[parent].lastkid].nextitem = idx; + ui_context->items[parent].lastkid = idx; + } + item->kind = kind; + item->data = -1; + if (data && size) { + item->data = ui_context->datasize; + ui_context->datasize += size; + memcpy(ui_context->data + item->data, data, size); + } + return idx; +} + +int uiItem( + int parent, UIhandle handle, int kind, int w, int h, void *data, int size) { + int idx = uiAllocItem(parent, handle, kind, data, size); + UIitem *item = uiItemPtr(idx); + item->rect.w = w; + item->rect.h = h; + return idx; +} + +void uiSetSpacing(int item, int spacing) { + uiItemPtr(item)->spacing = spacing; +} + +int uiGetSpacing(int item) { + return uiItemPtr(item)->spacing; +} + +int uiColumn(int parent, int spacing) { + int idx = uiItem(parent, 0, UI_COLUMN, 0, 0, NULL, 0); + uiSetSpacing(idx, spacing); + return idx; +} + +int uiRow(int parent, int spacing) { + int idx = uiItem(parent, 0, UI_ROW, 0, 0, NULL, 0); + uiSetSpacing(idx, spacing); + return idx; +} + +UIrect uiGetRect(int item) { + return uiItemPtr(item)->rect; +} + +UIrect uiGetScreenRect(int item) { + UIitem *pitem = uiItemPtr(item); + UIrect rect = pitem->rect; + rect.x = pitem->absx; + rect.y = pitem->absy; + return rect; +} + +void uiSetRect(int item, int x, int y, int w, int h) { + UIitem *pitem = uiItemPtr(item); + pitem->rect.w = w; + pitem->rect.h = h; + pitem->rect.x = x; + pitem->rect.y = y; +} + +int uiFirstChild(int item) { + return uiItemPtr(item)->firstkid; +} + +int uiLastChild(int item) { + return uiItemPtr(item)->lastkid; +} + +int uiNextSibling(int item) { + return uiItemPtr(item)->nextitem; +} + +int uiParent(int item) { + return uiItemPtr(item)->parent; +} + +int uiGetKind(int item) { + return uiItemPtr(item)->kind; +} + +void *uiGetData(int item) { + UIitem *pitem = uiItemPtr(item); + if (pitem->data < 0) return NULL; + return ui_context->data + pitem->data; +} + +int uiGetHandle(int item) { + return uiItemPtr(item)->handle; +} + +int uiGetChildId(int item) { + return uiItemPtr(item)->kidid; +} + +int uiGetChildCount(int item) { + return uiItemPtr(item)->numkids; +} + +void uiLayoutItem(int item, int x, int y); +void uiLayoutChildren(int item, int x, int y) { + int kid = uiFirstChild(item); + while (kid > 0) { + uiLayoutItem(kid, x, y); + kid = uiNextSibling(kid); + } +} + +void uiLayoutItem(int item, int x, int y) { + UIrect rect = uiGetRect(item); + rect.x += x; + rect.y += y; + + switch(uiGetKind(item)) { + case UI_COLUMN: + case UI_ROOT: { + int kid = uiFirstChild(item); + while (kid > 0) { + UIitem *pitem = uiItemPtr(kid); + if (!pitem->rect.w || (pitem->rect.w > rect.w)) { + pitem->rect.w = rect.w; + } + kid = uiNextSibling(kid); + } + + uiLayoutChildren(item, rect.x, rect.y); + + int spacing = uiItemPtr(item)->spacing; + int h = -spacing; + // stack kids vertically + kid = uiFirstChild(item); + while (kid > 0) { + UIitem *pitem = uiItemPtr(kid); + h += spacing; + pitem->rect.y = h; + h += pitem->rect.h; + kid = uiNextSibling(kid); + } + + if (h > rect.h) { + uiItemPtr(item)->rect.h = h; + } + + } break; + case UI_ROW: { + uiLayoutChildren(item, rect.x, rect.y); + + int spacing = uiItemPtr(item)->spacing; + + // figure out height of row + // also see how much horizontal space is used + int lastkidid = uiGetChildCount(item)-1; + lastkidid = (lastkidid<0)?0:lastkidid; + int w = spacing*lastkidid; + // how many children have no explicit width? + int numdynkids = 0; + int kid = uiFirstChild(item); + while (kid > 0) { + UIitem *pitem = uiItemPtr(kid); + if (pitem->rect.h > rect.h) + rect.h = pitem->rect.h; + if (!pitem->rect.w) + numdynkids++; + else + w += pitem->rect.w; + kid = uiNextSibling(kid); + } + uiItemPtr(item)->rect.h = rect.h; + + int space = rect.w - w; + // distribute remaining width across children + int dw = numdynkids?(space / numdynkids):0; + dw = (dw<0)?0:dw; + // rounding error + space -= numdynkids*dw; + + w = -spacing; + // stack kids horizontally + kid = uiFirstChild(item); + while (kid > 0) { + UIitem *pitem = uiItemPtr(kid); + w += spacing; + pitem->rect.x = w; + if (!pitem->rect.w) { + --numdynkids; + // round last kid + if (!numdynkids) + dw += space; + pitem->rect.w = dw; + } + w += pitem->rect.w; + kid = uiNextSibling(kid); + } + } break; + default: break; + } +} + +void uiUpdateItemState(int item, int x, int y) { + UIitem *pitem = uiItemPtr(item); + pitem->absx = pitem->rect.x+x; + pitem->absy = pitem->rect.y+y; + + if (pitem->handle) { + int cx = ui_context->cx - pitem->absx; + int cy = ui_context->cy - pitem->absy; + if ((cx>=0) + && (cy>=0) + && (cxrect.w) + && (cyrect.h)) { + ui_context->hot = pitem->handle; + + if (!ui_context->active && uiGetButton(0)) { + ui_context->active = pitem->handle; + } + } + } + + int kid = uiFirstChild(item); + while (kid > 0) { + uiUpdateItemState(kid, pitem->absx, pitem->absy); + kid = uiNextSibling(kid); + } + +} + +void uiLayout() { + uiLayoutItem(0, 0, 0); + uiUpdateItemState(0, 0, 0); + if (!uiGetButton(0)) { + ui_context->active = 0; + } +} + +int uiIsActive(int item) { + assert(ui_context); + return (ui_context->active == uiItemPtr(item)->handle)?1:0; +} + +int uiIsHot(int item) { + assert(ui_context); + return (ui_context->hot == uiItemPtr(item)->handle)?1:0; +} + +int uiGetState(int item) { + return (!uiIsHot(item))?UI_COLD:(uiIsActive(item))?UI_ACTIVE:UI_HOT; + +} + #endif // UI_IMPLEMENTATION //////////////////////////////////////////////////////////////////////////////// +typedef struct UIData { + int iconid; + const char *label; +} UIData; + +//////////////////////////////////////////////////////////////////////////////// + void init(NVGcontext *vg) { bndSetFont(nvgCreateFont(vg, "system", "../droidsans.ttf")); bndSetIconImage(nvgCreateImage(vg, "../blender_icons16.png")); } -void draw(NVGcontext *vg, float w, float h) { +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 == 0) return BND_CORNER_NONE; + switch(uiGetKind(parent)) { + case UI_COLUMN: { + if (!numid) return BND_CORNER_DOWN; + else if (numid == numkids-1) return BND_CORNER_TOP; + else return BND_CORNER_ALL; + } break; + case UI_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 drawUI(NVGcontext *vg, int item, int x, int y) { + UIData *data = (UIData *)uiGetData(item); + UIrect rect = uiGetRect(item); + rect.x += x; + rect.y += y; + + switch(uiGetKind(item)) { + case UI_BUTTON: { + assert(data); + bndToolButton(vg,rect.x,rect.y,rect.w,rect.h, + cornerFlags(item),(BNDwidgetState)uiGetState(item), + data->iconid,data->label); + } break; + default: break; + } + + int kid = uiFirstChild(item); + while (kid > 0) { + drawUI(vg, kid, rect.x, rect.y); + kid = uiNextSibling(kid); + } +} + +void draw(NVGcontext *vg, float w, float h) { bndBackground(vg, 0, 0, w, h); int x = 10; @@ -280,9 +701,46 @@ void draw(NVGcontext *vg, float w, float h) { x += BND_TOOL_WIDTH-1; bndRadioButton(vg,x,y,BND_TOOL_WIDTH,BND_WIDGET_HEIGHT,BND_CORNER_LEFT, BND_DEFAULT,BND_ICONID(5,11),NULL); - - + uiClear(); + + uiSetRect(0,600,10,250,400); + + int col = uiColumn(0,1); + UIData data = { BND_ICONID(6,3), "Item 1" }; + uiItem(col, 1, UI_BUTTON, 0, BND_WIDGET_HEIGHT, &data, sizeof(data)); + data.label = "Item 2"; + uiItem(col, 2, UI_BUTTON, 0, BND_WIDGET_HEIGHT, &data, sizeof(data)); + { // nested row + int row = uiRow(col,-1); + data.label = "Item 3.0"; + uiItem(row, 3, UI_BUTTON, 0, BND_WIDGET_HEIGHT, &data, sizeof(data)); + data.label = NULL; + data.iconid = BND_ICONID(0,10); + uiItem(row, 4, UI_BUTTON, BND_TOOL_WIDTH, BND_WIDGET_HEIGHT, &data, sizeof(data)); + data.label = NULL; + data.iconid = BND_ICONID(1,10); + uiItem(row, 5, UI_BUTTON, BND_TOOL_WIDTH, BND_WIDGET_HEIGHT, &data, sizeof(data)); + data.iconid = BND_ICONID(6,3); + data.label = "Item 3.3"; + uiItem(row, 6, UI_BUTTON, 0, BND_WIDGET_HEIGHT, &data, sizeof(data)); + } + + { // nested column + int col2 = uiColumn(col,-2); + data.label = "Item 4.0"; + uiItem(col2, 7, UI_BUTTON, 0, BND_WIDGET_HEIGHT, &data, sizeof(data)); + data.label = "Item 4.1"; + uiItem(col2, 8, UI_BUTTON, 0, BND_WIDGET_HEIGHT, &data, sizeof(data)); + } + + data.label = "Item 5"; + uiItem(col, 9, UI_BUTTON, 0, BND_WIDGET_HEIGHT, &data, sizeof(data)); + + uiLayout(); + + + drawUI(vg, 0, 0, 0); } ////////////////////////////////////////////////////////////////////////////////