From 49d006bf226e1874e9d1cfa49b931016e84096c5 Mon Sep 17 00:00:00 2001 From: Leonard Ritter Date: Sun, 13 Jul 2014 06:01:17 +0200 Subject: [PATCH] oui: check-in first final revision --- oui.h | 395 +++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 308 insertions(+), 87 deletions(-) diff --git a/oui.h b/oui.h index cd7805c..9077c98 100644 --- a/oui.h +++ b/oui.h @@ -22,21 +22,163 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef _UI_H_ -#define _UI_H_ +#ifndef _OUI_H_ +#define _OUI_H_ + +/* +Revision 1 (2014-07-13) +*/ /* -OUI (spoken like the french "oui" for "yes") is a single-header library for -layouting GUI elements and handling their user input. +OUI (short for "Open UI", spoken like the french "oui" for "yes") is a +platform agnostic single-header C library for layouting GUI elements and +handling their user input. Together with a set of widget drawing and logic +routines it can be used to build complex user interfaces. + +OUI is a semi-immediate GUI. Widget declarations are persistent for the duration +of the setup and evaluation, but do not need to be kept around longer than one +frame. OUI has no widget types; instead, it provides only one kind of element, "Items", -which can be expanded to behave as containers, buttons, sliders, radio +which can be taylored to the application by the user and expanded with custom +buffers and event handlers to behave as containers, buttons, sliders, radio buttons, and so on. -Together with a set of widget drawing routines it can be used to build flowing -user interfaces; the intended use is for bootstrap situations where only basic -UI services are needed. +OUI also does not draw anything; Instead it provides a set of functions to +iterate and query the layouted items in order to allow client code to render +each widget with its current state using a preferred graphics library. + +A basic setup for OUI usage looks like this: + +void app_main(...) { + UIcontext *context = uiCreateContext(); + uiMakeCurrent(context); + + while (app_running()) { + // update position of mouse cursor; the ui can also be updated + // from received events. + uiSetCursor(app_get_mouse_x(), app_get_mouse_y()); + // update button state + for (int i = 0; i < 3; ++i) + uiSetButton(i, app_get_button_state(i)); + + // begin new UI declarations + uiClear(); + + // - UI setup code goes here - + app_setup_ui(); + + // layout UI, update states and fire handlers + uiProcess(); + + // draw UI + app_draw_ui(render_context,0,0,0); + } + + uiDestroyContext(context); +} + +Here's an example setup for a checkbox control: + +typedef struct CheckBoxData { + int type; + const char *label; + bool *checked; +} CheckBoxData; + +// called when the item is clicked (see checkbox()) +void app_checkbox_handler(int item, UIevent event) { + + // retrieve custom data (see checkbox()) + const CheckBoxData *data = (const CheckBoxData *)uiGetData(item); + + // toggle value + *data->checked = !(*data->checked); +} + +// creates a checkbox control for a pointer to a boolean and attaches it to +// a parent item. +int checkbox(int parent, UIhandle handle, const char *label, bool *checked) { + + // 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, APP_WIDGET_HEIGHT); + + // attach checkbox handler, set to fire as soon as the left button is + // pressed; UI_BUTTON0_HOT_UP is also a popular alternative. + uiSetHandler(item, app_checkbox_handler, UI_BUTTON0_DOWN); + + // store some custom data with the checkbox that we use for rendering + // and value changes. + CheckBoxData *data = (CheckBoxData *)uiAllocData(item, sizeof(CheckBoxData)); + // assign a custom typeid to the data so the renderer knows how to + // render this control. + data->type = APP_WIDGET_CHECKBOX; + data->label = label; + data->checked = checked; + + // append to parent + uiAppend(parent, item); + + return item; +} + +A simple recursive drawing routine can look like this: + +void app_draw_ui(AppRenderContext *ctx, int item, int x, int y) { + // retrieve custom data and cast it to an int; we assume the first member + // of every widget data item to be an "int type" field. + const int *type = (const int *)uiGetData(item); + + // get the widgets relative rectangle and offset by the parents + // absolute position. + UIrect rect = uiGetRect(item); + rect.x += x; + rect.y += y; + + // if a type is set, this is a specialized widget + if (type) { + switch(*type) { + default: break; + case APP_WIDGET_LABEL: { + // ... + } break; + case APP_WIDGET_BUTTON: { + // ... + } break; + case APP_WIDGET_CHECKBOX: { + // cast to the full data type + const CheckBoxData *data = (CheckBoxData*)type; + + // get the widgets current state + int state = uiGetState(item); + + // if the value is set, the state is always active + if (*data->checked) + state = UI_ACTIVE; + + // draw the checkbox + app_draw_checkbox(ctx, rect, state, data->label); + } break; + } + } + + // iterate through all children and draw + int kid = uiFirstChild(item); + while (kid > 0) { + app_draw_ui(ctx, kid, rect.x, rect.y); + kid = uiNextSibling(kid); + } +} + +See example.cpp in the repository for a full usage example. */ @@ -51,6 +193,8 @@ UI services are needed. // the item is unresponsive #define UI_FROZEN 0x0003 +// limits + // maximum number of items that may be added #define UI_MAX_ITEMS 4096 // maximum size in bytes reserved for storage of application dependent data @@ -71,15 +215,25 @@ typedef unsigned long long UIhandle; // layout flags typedef enum UIlayoutFlags { + // anchor to left item or left side of parent UI_LEFT = 1, + // anchor to top item or top side of parent UI_TOP = 2, + // anchor to right item or right side of parent UI_RIGHT = 4, + // anchor to bottom item or bottom side of parent UI_DOWN = 8, + // anchor to both left and right item or parent borders UI_HFILL = 5, + // anchor to both top and bottom item or parent borders UI_VFILL = 10, + // center horizontally, with left margin as offset UI_HCENTER = 0, + // center vertically, with top margin as offset UI_VCENTER = 0, + // center in both directions, with left/top margin as offset UI_CENTER = 0, + // anchor to all four directions UI_FILL = 15, } UIlayoutFlags; @@ -88,12 +242,21 @@ typedef enum UIevent { // on button 0 down UI_BUTTON0_DOWN = 0x01, // on button 0 up + // when this event has a handler, uiGetState() will return UI_ACTIVE as + // long as button 0 is down. UI_BUTTON0_UP = 0x02, // on button 0 up while item is hovered + // when this event has a handler, uiGetState() will return UI_ACTIVE + // when the cursor is hovering the items rectangle; this is the + // behavior expected for buttons. UI_BUTTON0_HOT_UP = 0x04, - // item is being captured (button 0 constantly pressed) + // item is being captured (button 0 constantly pressed); + // when this event has a handler, uiGetState() will return UI_ACTIVE as + // long as button 0 is down. UI_BUTTON0_CAPTURE = 0x08, - // item has been added to container + // item has received a new child + // this can be used to allow container items to configure child items + // as they appear. UI_APPEND = 0x10, } UIevent; @@ -118,8 +281,12 @@ typedef struct UIrect { // unless declared otherwise, all operations have the complexity O(1). +// Context Management +// ------------------ + // create a new UI context; call uiMakeCurrent() to make this context the -// current context. +// current context. The context is managed by the client and must be released +// using uiDestroyContext() UIcontext *uiCreateContext(); // select an UI context as the current context; a context must always be @@ -130,16 +297,8 @@ void uiMakeCurrent(UIcontext *ctx); // context is the current context, the current context will be set to NULL void uiDestroyContext(UIcontext *ctx); -// sets a mouse or gamepad button as pressed/released -// button is in the range 0..63 and maps to an application defined input -// source. -// enabled is 1 for pressed, 0 for released -void uiSetButton(int button, int enabled); - -// returns the current state of an application dependent input button -// as set by uiSetButton(). -// the function returns 1 if the button has been set to pressed, 0 for released. -int uiGetButton(int button); +// Input Control +// ------------- // sets the current cursor position (usually belonging to a mouse) to the // screen coordinates at (x,y) @@ -156,30 +315,90 @@ UIvec2 uiGetCursorDelta(); // operation. UIvec2 uiGetCursorStartDelta(); -// clear the item buffer; uiClear() should be called before each UI declaration -// to avoid concatenation of the same UI multiple times. +// sets a mouse or gamepad button as pressed/released +// button is in the range 0..63 and maps to an application defined input +// source. +// enabled is 1 for pressed, 0 for released +void uiSetButton(int button, int enabled); + +// returns the current state of an application dependent input button +// as set by uiSetButton(). +// the function returns 1 if the button has been set to pressed, 0 for released. +int uiGetButton(int button); + +// Stages +// ------ + +// clear the item buffer; uiClear() should be called before the first +// UI declaration for this frame to avoid concatenation of the same UI multiple +// times. // After the call, all previously declared item IDs are invalid, and all // application dependent context data has been freed. void uiClear(); +// layout all added items starting from the root item 0, update the +// internal state according to the current cursor position and button states, +// and call all registered handlers. +// after calling uiProcess(), no further modifications to the item tree should +// be done until the next call to uiClear(). +// It is safe to immediately draw the items after a call to uiProcess(). +// this is an O(N) operation for N = number of declared items. +void uiProcess(); + +// UI Declaration +// -------------- + // create a new UI item and return the new items ID. int uiItem(); +// set the application-dependent handle of an item. +// handle is an application defined 64-bit handle. If handle is 0, the item +// will not be interactive. +void uiSetHandle(int item, UIhandle handle); + +// allocate space for application-dependent context data and return the pointer +// if successful. If no data has been allocated, a new pointer is returned. +// Otherwise, an assertion is thrown. +// The memory of the pointer is managed by the UI context. +void *uiAllocData(int item, int size); + +// set the handler callback for an interactive item. +// flags is a combination of UI_EVENT_* and designates for which events the +// handler should be called. +void uiSetHandler(int item, UIhandler handler, int flags); + // assign an item to a container. // 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. -// It is safe to immediately draw the items after a call to uiProcess(). -// this is an O(N) operation for N = number of declared items. -void uiProcess(); +// set the size of the item; a size of 0 indicates the dimension to be +// dynamic; if the size is set, the item can not expand beyond that size. +void uiSetSize(int item, int w, int h); -// returns the number of child items a container item contains. If the item -// is not a container or does not contain any items, 0 is returned. -// if item is 0, the child item count of the root item will be returned. -int uiGetChildCount(int item); +// set the anchoring behavior of the item to one or multiple UIlayoutFlags +void uiSetLayout(int item, int flags); + +// set the left, top, right and bottom margins of an item; when the item is +// anchored to the parent or another item, the margin controls the distance +// from the neighboring element. +void uiSetMargins(int item, int l, int t, int r, int b); + +// anchor the item to another sibling within the same container, so that the +// sibling is left to this item. +void uiSetRelToLeft(int item, int other); +// anchor the item to another sibling within the same container, so that the +// sibling is above this item. +void uiSetRelToTop(int item, int other); +// anchor the item to another sibling within the same container, so that the +// sibling is right to this item. +void uiSetRelToRight(int item, int other); +// anchor the item to another sibling within the same container, so that the +// sibling is below this item. +void uiSetRelToDown(int item, int other); + +// Iteration +// --------- // returns the first child item of a container item. If the item is not // a container or does not contain any items, -1 is returned. @@ -195,12 +414,6 @@ int uiLastChild(int item); // if item is 0, -1 will be returned. int uiParent(int item); -// returns an items child index relative to its parent. If the item is the -// first item, the return value is 0; If the item is the last item, the return -// value is equivalent to uiGetChildCount(uiParent(item))-1. -// if item is 0, 0 will be returned. -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); @@ -209,69 +422,76 @@ int uiNextSibling(int item); // 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); -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); +// Querying +// -------- -// returns the items layout rectangle relative to the parent. If uiGetRect() -// is called before uiProcess(), the values of the returned rectangle are -// undefined. -UIrect uiGetRect(int item); +// return the current state of the item. This state is only valid after +// a call to uiProcess(). +// The returned value is one of UI_COLD, UI_HOT, UI_ACTIVE. +int uiGetState(int item); -// allocate space for application-dependent context data and return the pointer -// if successful. If no data has been allocated, a new pointer is returned. -// Otherwise, an assertion is thrown. -// The memory of the pointer is managed by the UI context. -void *uiAllocData(int item, int size); +// return the application-dependent handle of the item as passed to uiSetHandle(). +UIhandle uiGetHandle(int item); // return the application-dependent context data for an item as passed to // uiAllocData(). The memory of the pointer is managed by the UI context // and must not be altered. const void *uiGetData(int item); -// set the application-dependent handle of an item. -// handle is an application defined 64-bit handle. If handle is 0, the item -// will not be interactive. -void uiSetHandle(int item, UIhandle handle); +// return the handler callback for an item as passed to uiSetHandler() +UIhandler uiGetHandler(int item); +// return the handler flags for an item as passed to uiSetHandler() +int uiGetHandlerFlags(int item); -// return the application-dependent handle of the item as passed to uiSetHandle(). -UIhandle uiGetHandle(int item); +// returns the number of child items a container item contains. If the item +// is not a container or does not contain any items, 0 is returned. +// if item is 0, the child item count of the root item will be returned. +int uiGetChildCount(int item); -// return the current state of the item. This state is only valid after -// a call to uiProcess(). -// The returned value is one of UI_COLD, UI_HOT, UI_ACTIVE. -int uiGetState(int item); +// returns an items child index relative to its parent. If the item is the +// first item, the return value is 0; If the item is the last item, the return +// value is equivalent to uiGetChildCount(uiParent(item))-1. +// if item is 0, 0 will be returned. +int uiGetChildId(int item); -// set the handler callback for an interactive item. -// flags is a combination of UI_EVENT_* and designates for which events the -// handler should be called. -void uiSetHandler(int item, UIhandler handler, int flags); +// returns the items layout rectangle relative to the parent. If uiGetRect() +// is called before uiProcess(), the values of the returned rectangle are +// undefined. +UIrect uiGetRect(int item); -// return the handler callback for an item as passed to uiSetHandler() -UIhandler uiGetHandler(int item); +// return the width of the item as set by uiSetSize() +int uiGetWidth(int item); +// return the height of the item as set by uiSetSize() +int uiGetHeight(int item); -// return the handler flags for an item as passed to uiSetHandler() -int uiGetHandlerFlags(int item); +// return the anchoring behavior as set by uiSetLayout() +int uiGetLayout(int item); + +// return the left margin of the item as set with uiSetMargins() +int uiGetMarginLeft(int item); +// return the top margin of the item as set with uiSetMargins() +int uiGetMarginTop(int item); +// return the right margin of the item as set with uiSetMargins() +int uiGetMarginRight(int item); +// return the bottom margin of the item as set with uiSetMargins() +int uiGetMarginDown(int item); + +// return the items anchored sibling as assigned with uiSetRelToLeft() +// or -1 if not set. +int uiGetRelToLeft(int item); +// return the items anchored sibling as assigned with uiSetRelToTop() +// or -1 if not set. +int uiGetRelToTop(int item); +// return the items anchored sibling as assigned with uiSetRelToRight() +// or -1 if not set. +int uiGetRelToRight(int item); +// return the items anchored sibling as assigned with uiSetRelToBottom() +// or -1 if not set. +int uiGetRelToDown(int item); -#endif // _UI_H_ +#endif // _OUI_H_ -#define UI_IMPLEMENTATION -#ifdef UI_IMPLEMENTATION +#ifdef OUI_IMPLEMENTATION #include @@ -319,6 +539,7 @@ typedef struct UIitem { int layout_flags; // size UIvec2 size; + // visited flags for layouting int visited; // margin offsets, interpretation depends on flags int margins[4]; @@ -916,4 +1137,4 @@ int uiGetState(int item) { return UI_COLD; } -#endif // UI_IMPLEMENTATION +#endif // OUI_IMPLEMENTATION