|
- /*
- OUI - A minimal semi-immediate GUI handling & layouting library
-
- Copyright (c) 2014 Leonard Ritter <leonard.ritter@duangle.com>
-
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
-
- The above copyright notice and this permission notice shall be included in
- all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- THE SOFTWARE.
- */
-
- #ifndef _OUI_H_
- #define _OUI_H_
-
- #ifdef __cplusplus
- extern "C" {
- #endif
-
- /*
- Revision 3 (2014-09-23)
-
- 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 related 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 tailored 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.
-
- 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.
-
- See example.cpp in the repository for a full usage example.
-
- A basic setup for OUI usage in C looks like this:
- =================================================
-
- // a header for each widget
- typedef struct Data {
- int type;
- UIhandler handler;
- } Data;
-
- /// global event dispatch
- void ui_handler(int item, UIevent event) {
- Data *data = (Data *)uiGetHandle(item);
- if (data && data->handler) {
- data->handler(item, event);
- }
- }
-
- void app_main(...) {
- UIcontext *context = uiCreateContext(4096, 1<<20);
- uiMakeCurrent(context);
- uiSetHandler(ui_handler);
-
- 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));
-
- // you can also send keys and scroll events; see example.cpp for more
-
- // --------------
- // this section does not have to be regenerated on frame; a good
- // policy is to invalidate it on events, as this usually alters
- // structure and layout.
-
- // begin new UI declarations
- uiBeginLayout();
-
- // - UI setup code goes here -
- app_setup_ui();
-
- // layout UI
- uiEndLayout();
-
- // --------------
-
- // draw UI, starting with the first item, index 0
- app_draw_ui(render_context,0);
-
- // update states and fire handlers
- uiProcess(get_time_ms());
- }
-
- uiDestroyContext(context);
- }
-
- Here's an example setup for a checkbox control:
- ===============================================
-
- typedef struct CheckBoxData {
- Data head;
- 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())
- CheckBoxData *data = (CheckBoxData *)uiGetHandle(item);
-
- switch(event) {
- default: break;
- case UI_BUTTON0_DOWN: {
- // toggle value
- *data->checked = !(*data->checked);
- } break;
- }
- }
-
- // creates a checkbox control for a pointer to a boolean
- int checkbox(const char *label, bool *checked) {
-
- // create new ui item
- int item = uiItem();
-
- // set minimum size of wiget; horizontal size is dynamic, vertical is fixed
- uiSetSize(item, 0, APP_WIDGET_HEIGHT);
-
- // store some custom data with the checkbox that we use for rendering
- // and value changes.
- CheckBoxData *data = (CheckBoxData *)uiAllocHandle(item, sizeof(CheckBoxData));
-
- // assign a custom typeid to the data so the renderer knows how to
- // render this control, and our event handler
- data->head.type = APP_WIDGET_CHECKBOX;
- data->head.handler = app_checkbox_handler;
- data->label = label;
- data->checked = checked;
-
- // set to fire as soon as the left button is
- // pressed; UI_BUTTON0_HOT_UP is also a popular alternative.
- uiSetEvents(item, UI_BUTTON0_DOWN);
-
- return item;
- }
-
- A simple recursive drawing routine can look like this:
- ======================================================
-
- void app_draw_ui(AppRenderContext *ctx, int item) {
- // retrieve custom data and cast it to Data; we assume the first member
- // of every widget data item to be a Data field.
- Data *head = (Data *)uiGetHandle(item);
-
- // if a handle is set, this is a specialized widget
- if (head) {
- // get the widgets absolute rectangle
- UIrect rect = uiGetRect(item);
-
- switch(head->type) {
- default: break;
- case APP_WIDGET_LABEL: {
- // ...
- } break;
- case APP_WIDGET_BUTTON: {
- // ...
- } break;
- case APP_WIDGET_CHECKBOX: {
- // cast to the full data type
- CheckBoxData *data = (CheckBoxData*)head;
-
- // 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 != -1) {
- app_draw_ui(ctx, kid);
- kid = uiNextSibling(kid);
- }
- }
-
- Layouting items works like this:
- ================================
-
- void layout_window(int w, int h) {
- // create root item; the first item always has index 0
- int parent = uiItem();
- // assign fixed size
- uiSetSize(parent, w, h);
-
- // create column box and use as new parent
- parent = uiInsert(parent, uiItem());
- // configure as column
- uiSetBox(parent, UI_COLUMN);
- // span horizontally, attach to top
- uiSetLayout(parent, UI_HFILL | UI_TOP);
-
- // add a label - we're assuming custom control functions to exist
- int item = uiInsert(parent, label("Hello World"));
- // set a fixed height for the label
- uiSetSize(item, 0, APP_WIDGET_HEIGHT);
- // span the label horizontally
- uiSetLayout(item, UI_HFILL);
-
- static bool checked = false;
-
- // add a checkbox to the same parent as item; this is faster than
- // calling uiInsert on the same parent repeatedly.
- item = uiAppend(item, checkbox("Checked:", &checked));
- // set a fixed height for the checkbox
- uiSetSize(item, 0, APP_WIDGET_HEIGHT);
- // span the checkbox in the same way as the label
- uiSetLayout(item, UI_HFILL);
- }
-
-
-
- */
-
- // you can override this from the outside to pick
- // the export level you need
- #ifndef OUI_EXPORT
- #define OUI_EXPORT
- #endif
-
- // limits
-
- enum {
- // maximum size in bytes of a single data buffer passed to uiAllocData().
- UI_MAX_DATASIZE = 4096,
- // maximum depth of nested containers
- UI_MAX_DEPTH = 64,
- // maximum number of buffered input events
- UI_MAX_INPUT_EVENTS = 64,
- // consecutive click threshold in ms
- UI_CLICK_THRESHOLD = 250,
- };
-
- typedef unsigned int UIuint;
-
- // opaque UI context
- typedef struct UIcontext UIcontext;
-
- // item states as returned by uiGetState()
-
- typedef enum UIitemState {
- // the item is inactive
- UI_COLD = 0,
- // the item is inactive, but the cursor is hovering over this item
- UI_HOT = 1,
- // the item is toggled, activated, focused (depends on item kind)
- UI_ACTIVE = 2,
- // the item is unresponsive
- UI_FROZEN = 3,
- } UIitemState;
-
- // container flags to pass to uiSetBox()
- typedef enum UIboxFlags {
- // flex-direction (bit 0+1)
-
- // left to right
- UI_ROW = 0x002,
- // top to bottom
- UI_COLUMN = 0x003,
-
- // model (bit 1)
-
- // free layout
- UI_LAYOUT = 0x000,
- // flex model
- UI_FLEX = 0x002,
-
- // flex-wrap (bit 2)
-
- // single-line
- UI_NOWRAP = 0x000,
- // multi-line, wrap left to right
- UI_WRAP = 0x004,
-
-
- // justify-content (start, end, center, space-between)
- // 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,
- // then using UI_TOP, UI_DOWN, UI_VFILL, UI_VCENTER, etc.
- // FILL is equivalent to stretch/grow
-
- // align-content (start, end, center, stretch)
- // can be implemented by putting a flex container in a layout container,
- // then using UI_TOP, UI_DOWN, UI_VFILL, UI_VCENTER, etc.
- // FILL is equivalent to stretch; space-between is not supported.
- } UIboxFlags;
-
- // child layout flags to pass to uiSetLayout()
- typedef enum UIlayoutFlags {
- // attachments (bit 5-8)
- // fully valid when parent uses UI_LAYOUT model
- // partially valid when in UI_FLEX model
-
- // anchor to left item or left side of parent
- UI_LEFT = 0x020,
- // anchor to top item or top side of parent
- UI_TOP = 0x040,
- // anchor to right item or right side of parent
- UI_RIGHT = 0x080,
- // anchor to bottom item or bottom side of parent
- UI_DOWN = 0x100,
- // anchor to both left and right item or parent borders
- UI_HFILL = 0x0a0,
- // anchor to both top and bottom item or parent borders
- UI_VFILL = 0x140,
- // center horizontally, with left margin as offset
- UI_HCENTER = 0x000,
- // center vertically, with top margin as offset
- UI_VCENTER = 0x000,
- // center in both directions, with left/top margin as offset
- UI_CENTER = 0x000,
- // anchor to all four directions
- UI_FILL = 0x1e0,
- // when wrapping, put this element on a new line
- // wrapping layout code auto-inserts UI_BREAK flags,
- // drawing routines can read them with uiGetLayout()
- UI_BREAK = 0x200,
-
- } UIlayoutFlags;
-
- // event flags
- typedef enum UIevent {
- // on button 0 down
- UI_BUTTON0_DOWN = 0x0400,
- // 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 = 0x0800,
- // 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 = 0x1000,
- // 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 = 0x2000,
- // on button 2 down (right mouse button, usually triggers context menu)
- UI_BUTTON2_DOWN = 0x4000,
- // item has received a scrollwheel event
- // the accumulated wheel offset can be queried with uiGetScroll()
- UI_SCROLL = 0x8000,
- // item is focused and has received a key-down event
- // the respective key can be queried using uiGetKey() and uiGetModifier()
- UI_KEY_DOWN = 0x10000,
- // item is focused and has received a key-up event
- // the respective key can be queried using uiGetKey() and uiGetModifier()
- UI_KEY_UP = 0x20000,
- // item is focused and has received a character event
- // the respective character can be queried using uiGetKey()
- UI_CHAR = 0x40000,
- } UIevent;
-
- enum {
- // these bits, starting at bit 24, can be safely assigned by the
- // application, e.g. as item types, other event types, drop targets, etc.
- // they can be set and queried using uiSetFlags() and uiGetFlags()
- UI_USERMASK = 0xff000000,
-
- // a special mask passed to uiFindItem()
- UI_ANY = 0xffffffff,
- };
-
- // handler callback; event is one of UI_EVENT_*
- typedef void (*UIhandler)(int item, UIevent event);
-
- // for cursor positions, mainly
- typedef struct UIvec2 {
- union {
- int v[2];
- struct { int x, y; };
- };
- } UIvec2;
-
- // layout rectangle
- typedef struct UIrect {
- union {
- int v[4];
- struct { int x, y, w, h; };
- };
- } 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. The context is managed by the client and must be released
- // using uiDestroyContext()
- // item_capacity is the maximum of number of items that can be declared.
- // buffer_capacity is the maximum total size of bytes that can be allocated
- // using uiAllocHandle(); you may pass 0 if you don't need to allocate
- // handles.
- // 4096 and (1<<20) are good starting values.
- OUI_EXPORT UIcontext *uiCreateContext(
- unsigned int item_capacity,
- unsigned int buffer_capacity);
-
- // select an UI context as the current context; a context must always be
- // selected before using any of the other UI functions
- OUI_EXPORT void uiMakeCurrent(UIcontext *ctx);
-
- // release the memory of an UI context created with uiCreateContext(); if the
- // context is the current context, the current context will be set to NULL
- OUI_EXPORT void uiDestroyContext(UIcontext *ctx);
-
- // returns the currently selected context or NULL
- OUI_EXPORT UIcontext *uiGetContext();
-
- // Input Control
- // -------------
-
- // sets the current cursor position (usually belonging to a mouse) to the
- // screen coordinates at (x,y)
- OUI_EXPORT void uiSetCursor(int x, int y);
-
- // returns the current cursor position in screen coordinates as set by
- // uiSetCursor()
- OUI_EXPORT UIvec2 uiGetCursor();
-
- // returns the offset of the cursor relative to the last call to uiProcess()
- OUI_EXPORT UIvec2 uiGetCursorDelta();
-
- // returns the beginning point of a drag operation.
- OUI_EXPORT UIvec2 uiGetCursorStart();
-
- // returns the offset of the cursor relative to the beginning point of a drag
- // operation.
- OUI_EXPORT UIvec2 uiGetCursorStartDelta();
-
- // sets a mouse or gamepad button as pressed/released
- // button is in the range 0..63 and maps to an application defined input
- // source.
- // mod is an application defined set of flags for modifier keys
- // enabled is 1 for pressed, 0 for released
- OUI_EXPORT void uiSetButton(unsigned int button, unsigned int mod, 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.
- OUI_EXPORT int uiGetButton(unsigned int button);
-
- // returns the number of chained clicks; 1 is a single click,
- // 2 is a double click, etc.
- OUI_EXPORT int uiGetClicks();
-
- // sets a key as down/up; the key can be any application defined keycode
- // mod is an application defined set of flags for modifier keys
- // enabled is 1 for key down, 0 for key up
- // all key events are being buffered until the next call to uiProcess()
- OUI_EXPORT void uiSetKey(unsigned int key, unsigned int mod, int enabled);
-
- // sends a single character for text input; the character is usually in the
- // unicode range, but can be application defined.
- // all char events are being buffered until the next call to uiProcess()
- OUI_EXPORT void uiSetChar(unsigned int value);
-
- // accumulates scroll wheel offsets for the current frame
- // all offsets are being accumulated until the next call to uiProcess()
- OUI_EXPORT void uiSetScroll(int x, int y);
-
- // returns the currently accumulated scroll wheel offsets for this frame
- OUI_EXPORT UIvec2 uiGetScroll();
-
-
-
-
-
- // Stages
- // ------
-
- // clear the item buffer; uiBeginLayout() 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.
- // uiBeginLayout() must be followed by uiEndLayout().
- OUI_EXPORT void uiBeginLayout();
-
- // layout all added items starting from the root item 0.
- // after calling uiEndLayout(), no further modifications to the item tree should
- // be done until the next call to uiBeginLayout().
- // It is safe to immediately draw the items after a call to uiEndLayout().
- // this is an O(N) operation for N = number of declared items.
- OUI_EXPORT void uiEndLayout();
-
- // update the current hot item; this only needs to be called if items are kept
- // for more than one frame and uiEndLayout() is not called
- OUI_EXPORT void uiUpdateHotItem();
-
- // update the internal state according to the current cursor position and
- // button states, and call all registered handlers.
- // timestamp is the time in milliseconds relative to the last call to uiProcess()
- // and is used to estimate the threshold for double-clicks
- // after calling uiProcess(), no further modifications to the item tree should
- // be done until the next call to uiBeginLayout().
- // Items should be drawn before a call to uiProcess()
- // this is an O(N) operation for N = number of declared items.
- OUI_EXPORT void uiProcess(int timestamp);
-
- // reset the currently stored hot/active etc. handles; this should be called when
- // a re-declaration of the UI changes the item indices, to avoid state
- // related glitches because item identities have changed.
- OUI_EXPORT void uiClearState();
-
- // UI Declaration
- // --------------
-
- // create a new UI item and return the new items ID.
- OUI_EXPORT int uiItem();
-
- // set an items state to frozen; the UI will not recurse into frozen items
- // when searching for hot or active items; subsequently, frozen items and
- // their child items will not cause mouse event notifications.
- // The frozen state is not applied recursively; uiGetState() will report
- // UI_COLD for child items. Upon encountering a frozen item, the drawing
- // routine needs to handle rendering of child items appropriately.
- // see example.cpp for a demonstration.
- OUI_EXPORT void uiSetFrozen(int item, int enable);
-
- // set the application-dependent handle of an item.
- // handle is an application defined 64-bit handle. If handle is NULL, the item
- // will not be interactive.
- OUI_EXPORT void uiSetHandle(int item, void *handle);
-
- // allocate space for application-dependent context data and assign it
- // as the handle to the item.
- // The memory of the pointer is managed by the UI context and released
- // upon the next call to uiBeginLayout()
- OUI_EXPORT void *uiAllocHandle(int item, unsigned int size);
-
- // set the global handler callback for interactive items.
- // the handler will be called for each item whose event flags are set using
- // uiSetEvents.
- OUI_EXPORT void uiSetHandler(UIhandler handler);
-
- // flags is a combination of UI_EVENT_* and designates for which events the
- // handler should be called.
- OUI_EXPORT void uiSetEvents(int item, unsigned int flags);
-
- // flags is a user-defined set of flags defined by UI_USERMASK.
- OUI_EXPORT void uiSetFlags(int item, unsigned int flags);
-
- // assign an item to a container.
- // an item ID of 0 refers to the root item.
- // the function returns the child item ID
- // if the container has already added items, the function searches
- // for the last item and calls uiAppend() on it, which is an
- // O(N) operation for N siblings.
- // it is usually more efficient to call uiInsert() for the first child,
- // then chain additional siblings using uiAppend().
- OUI_EXPORT int uiInsert(int item, int child);
-
- // assign an item to the same container as another item
- // sibling is inserted after item.
- OUI_EXPORT int uiAppend(int item, int sibling);
-
- // insert child into container item like uiInsert(), but prepend
- // it to the first child item, effectively putting it in
- // the background.
- // it is efficient to call uiInsertBack() repeatedly
- // in cases where drawing or layout order doesn't matter.
- OUI_EXPORT int uiInsertBack(int item, int child);
-
- // same as uiInsert()
- OUI_EXPORT int uiInsertFront(int item, int child);
-
- // 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.
- OUI_EXPORT void uiSetSize(int item, int w, int h);
-
- // set the anchoring behavior of the item to one or multiple UIlayoutFlags
- OUI_EXPORT void uiSetLayout(int item, unsigned int flags);
-
- // set the box model behavior of the item to one or multiple UIboxFlags
- OUI_EXPORT void uiSetBox(int item, unsigned 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.
- OUI_EXPORT void uiSetMargins(int item, short l, short t, short r, short b);
-
- // set item as recipient of all keyboard events; if item is -1, no item will
- // be focused.
- OUI_EXPORT void uiFocus(int item);
-
- // 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.
- // if item is 0, the first child item of the root item will be returned.
- OUI_EXPORT int uiFirstChild(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.
- OUI_EXPORT int uiNextSibling(int item);
-
- // Querying
- // --------
-
- // return the total number of allocated items
- OUI_EXPORT int uiGetItemCount();
-
- // return the total bytes that have been allocated by uiAllocHandle()
- OUI_EXPORT unsigned int uiGetAllocSize();
-
- // 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, UI_FROZEN.
- OUI_EXPORT UIitemState uiGetState(int item);
-
- // return the application-dependent handle of the item as passed to uiSetHandle()
- // or uiAllocHandle().
- OUI_EXPORT void *uiGetHandle(int item);
-
- // return the item that is currently under the cursor or -1 for none
- OUI_EXPORT int uiGetHotItem();
-
- // return the item that is currently focused or -1 for none
- OUI_EXPORT int uiGetFocusedItem();
-
- // returns the topmost item containing absolute location (x,y), starting with
- // item as parent, using a set of flags and masks as filter:
- // if both flags and mask are UI_ANY, the first topmost item is returned.
- // if mask is UI_ANY, the first topmost item matching *any* of flags is returned.
- // otherwise the first item matching (item.flags & flags) == mask is returned.
- // you may combine box, layout, event and user flags.
- // frozen items will always be ignored.
- OUI_EXPORT int uiFindItem(int item, int x, int y,
- unsigned int flags, unsigned int mask);
-
- // return the handler callback as passed to uiSetHandler()
- OUI_EXPORT UIhandler uiGetHandler();
- // return the event flags for an item as passed to uiSetEvents()
- OUI_EXPORT unsigned int uiGetEvents(int item);
- // return the user-defined flags for an item as passed to uiSetFlags()
- OUI_EXPORT unsigned int uiGetFlags(int item);
-
- // when handling a KEY_DOWN/KEY_UP event: the key that triggered this event
- OUI_EXPORT unsigned int uiGetKey();
- // when handling a keyboard or mouse event: the active modifier keys
- OUI_EXPORT unsigned int uiGetModifier();
-
- // returns the items layout rectangle in absolute coordinates. If
- // uiGetRect() is called before uiEndLayout(), the values of the returned
- // rectangle are undefined.
- OUI_EXPORT UIrect uiGetRect(int item);
-
- // returns 1 if an items absolute rectangle contains a given coordinate
- // otherwise 0
- OUI_EXPORT int uiContains(int item, int x, int y);
-
- // return the width of the item as set by uiSetSize()
- OUI_EXPORT int uiGetWidth(int item);
- // return the height of the item as set by uiSetSize()
- OUI_EXPORT int uiGetHeight(int item);
-
- // return the anchoring behavior as set by uiSetLayout()
- OUI_EXPORT unsigned int uiGetLayout(int item);
- // return the box model as set by uiSetBox()
- OUI_EXPORT unsigned int uiGetBox(int item);
-
- // return the left margin of the item as set with uiSetMargins()
- OUI_EXPORT short uiGetMarginLeft(int item);
- // return the top margin of the item as set with uiSetMargins()
- OUI_EXPORT short uiGetMarginTop(int item);
- // return the right margin of the item as set with uiSetMargins()
- OUI_EXPORT short uiGetMarginRight(int item);
- // return the bottom margin of the item as set with uiSetMargins()
- OUI_EXPORT short uiGetMarginDown(int item);
-
- // when uiBeginLayout() is called, the most recently declared items are retained.
- // when uiEndLayout() completes, it matches the old item hierarchy to the new one
- // and attempts to map old items to new items as well as possible.
- // when passed an item Id from the previous frame, uiRecoverItem() returns the
- // items new assumed Id, or -1 if the item could not be mapped.
- // it is valid to pass -1 as item.
- OUI_EXPORT int uiRecoverItem(int olditem);
-
- // in cases where it is important to recover old state over changes in
- // the view, and the built-in remapping fails, the UI declaration can manually
- // remap old items to new IDs in cases where e.g. the previous item ID has been
- // temporarily saved; uiRemapItem() would then be called after creating the
- // new item using uiItem().
- OUI_EXPORT void uiRemapItem(int olditem, int newitem);
-
- // returns the number if items that have been allocated in the last frame
- OUI_EXPORT int uiGetLastItemCount();
-
- #ifdef __cplusplus
- };
- #endif
-
- #endif // _OUI_H_
-
- #ifdef OUI_IMPLEMENTATION
-
- #include <assert.h>
-
- #ifdef _MSC_VER
- #pragma warning (disable: 4996) // Switch off security warnings
- #pragma warning (disable: 4100) // Switch off unreferenced formal parameter warnings
- #pragma warning (disable: 4244)
- #pragma warning (disable: 4305)
-
- #ifdef __cplusplus
- #define UI_INLINE inline
- #else
- #define UI_INLINE
- #endif
- #else
- #define UI_INLINE inline
- #endif
-
- #define UI_MAX_KIND 16
-
- #define UI_ANY_BUTTON0_INPUT (UI_BUTTON0_DOWN \
- |UI_BUTTON0_UP \
- |UI_BUTTON0_HOT_UP \
- |UI_BUTTON0_CAPTURE)
-
- #define UI_ANY_BUTTON2_INPUT (UI_BUTTON2_DOWN)
-
- #define UI_ANY_MOUSE_INPUT (UI_ANY_BUTTON0_INPUT \
- |UI_ANY_BUTTON2_INPUT)
-
- #define UI_ANY_KEY_INPUT (UI_KEY_DOWN \
- |UI_KEY_UP \
- |UI_CHAR)
-
- #define UI_ANY_INPUT (UI_ANY_MOUSE_INPUT \
- |UI_ANY_KEY_INPUT)
-
- enum {
- // extra item flags
-
- // bit 0-2
- UI_ITEM_BOX_MODEL_MASK = 0x000007,
- // bit 0-4
- UI_ITEM_BOX_MASK = 0x00001F,
- // bit 5-8
- UI_ITEM_LAYOUT_MASK = 0x0003E0,
- // bit 9-18
- UI_ITEM_EVENT_MASK = 0x07FC00,
- // item is frozen (bit 19)
- UI_ITEM_FROZEN = 0x080000,
- // item handle is pointer to data (bit 20)
- UI_ITEM_DATA = 0x100000,
- // item has been inserted (bit 21)
- UI_ITEM_INSERTED = 0x200000,
-
- // which flag bits will be compared
- UI_ITEM_COMPARE_MASK = UI_ITEM_BOX_MODEL_MASK
- | (UI_ITEM_LAYOUT_MASK & ~UI_BREAK)
- | UI_ITEM_EVENT_MASK
- | UI_USERMASK,
- };
-
- typedef struct UIitem {
- // data handle
- void *handle;
-
- // about 27 bits worth of flags
- unsigned int flags;
-
- // index of first kid
- // if old item: index of equivalent new item
- int firstkid;
- // index of next sibling with same parent
- int nextitem;
-
- // margin offsets, interpretation depends on flags
- // after layouting, the first two components are absolute coordinates
- short margins[4];
- // size
- short size[2];
- } UIitem;
-
- typedef enum UIstate {
- UI_STATE_IDLE = 0,
- UI_STATE_CAPTURE,
- } UIstate;
-
- typedef enum UIstage {
- UI_STAGE_LAYOUT = 0,
- UI_STAGE_POST_LAYOUT,
- UI_STAGE_PROCESS,
- } UIstage;
-
- typedef struct UIhandleEntry {
- unsigned int key;
- int item;
- } UIhandleEntry;
-
- typedef struct UIinputEvent {
- unsigned int key;
- unsigned int mod;
- UIevent event;
- } UIinputEvent;
-
- struct UIcontext {
- unsigned int item_capacity;
- unsigned int buffer_capacity;
-
- // handler
- UIhandler handler;
-
- // button state in this frame
- unsigned long long buttons;
- // button state in the previous frame
- unsigned long long last_buttons;
-
- // where the cursor was at the beginning of the active state
- UIvec2 start_cursor;
- // where the cursor was last frame
- UIvec2 last_cursor;
- // where the cursor is currently
- UIvec2 cursor;
- // accumulated scroll wheel offsets
- UIvec2 scroll;
-
- int active_item;
- int focus_item;
- int last_hot_item;
- int last_click_item;
- int hot_item;
-
- UIstate state;
- UIstage stage;
- unsigned int active_key;
- unsigned int active_modifier;
- unsigned int active_button_modifier;
- int last_timestamp;
- int last_click_timestamp;
- int clicks;
-
- int count;
- int last_count;
- int eventcount;
- unsigned int datasize;
-
- UIitem *items;
- unsigned char *data;
- UIitem *last_items;
- int *item_map;
- UIinputEvent events[UI_MAX_INPUT_EVENTS];
- };
-
- UI_INLINE int ui_max(int a, int b) {
- return (a>b)?a:b;
- }
-
- UI_INLINE int ui_min(int a, int b) {
- return (a<b)?a:b;
- }
-
- UI_INLINE float ui_maxf(float a, float b) {
- return (a>b)?a:b;
- }
-
- UI_INLINE float ui_minf(float a, float b) {
- return (a<b)?a:b;
- }
-
- static UIcontext *ui_context = NULL;
-
- void uiClear() {
- ui_context->last_count = ui_context->count;
- ui_context->count = 0;
- ui_context->datasize = 0;
- ui_context->hot_item = -1;
- // swap buffers
- UIitem *items = ui_context->items;
- ui_context->items = ui_context->last_items;
- ui_context->last_items = items;
- for (int i = 0; i < ui_context->last_count; ++i) {
- ui_context->item_map[i] = -1;
- }
- }
-
- UIcontext *uiCreateContext(
- unsigned int item_capacity,
- unsigned int buffer_capacity) {
- assert(item_capacity);
- UIcontext *ctx = (UIcontext *)malloc(sizeof(UIcontext));
- memset(ctx, 0, sizeof(UIcontext));
- ctx->item_capacity = item_capacity;
- ctx->buffer_capacity = buffer_capacity;
- ctx->stage = UI_STAGE_PROCESS;
- ctx->items = (UIitem *)malloc(sizeof(UIitem) * item_capacity);
- ctx->last_items = (UIitem *)malloc(sizeof(UIitem) * item_capacity);
- ctx->item_map = (int *)malloc(sizeof(int) * item_capacity);
- if (buffer_capacity) {
- ctx->data = (unsigned char *)malloc(buffer_capacity);
- }
-
- UIcontext *oldctx = ui_context;
- uiMakeCurrent(ctx);
- uiClear();
- uiClearState();
- uiMakeCurrent(oldctx);
- return ctx;
- }
-
- void uiMakeCurrent(UIcontext *ctx) {
- ui_context = ctx;
- }
-
- void uiDestroyContext(UIcontext *ctx) {
- if (ui_context == ctx)
- uiMakeCurrent(NULL);
- free(ctx->items);
- free(ctx->last_items);
- free(ctx->item_map);
- free(ctx->data);
- free(ctx);
- }
-
- OUI_EXPORT UIcontext *uiGetContext() {
- return ui_context;
- }
-
- void uiSetButton(unsigned int button, unsigned int mod, int enabled) {
- assert(ui_context);
- unsigned long long mask = 1ull<<button;
- // set new bit
- ui_context->buttons = (enabled)?
- (ui_context->buttons | mask):
- (ui_context->buttons & ~mask);
- ui_context->active_button_modifier = mod;
- }
-
- static void uiAddInputEvent(UIinputEvent event) {
- assert(ui_context);
- if (ui_context->eventcount == UI_MAX_INPUT_EVENTS) return;
- ui_context->events[ui_context->eventcount++] = event;
- }
-
- static void uiClearInputEvents() {
- assert(ui_context);
- ui_context->eventcount = 0;
- ui_context->scroll.x = 0;
- ui_context->scroll.y = 0;
- }
-
- void uiSetKey(unsigned int key, unsigned int mod, int enabled) {
- assert(ui_context);
- UIinputEvent event = { key, mod, enabled?UI_KEY_DOWN:UI_KEY_UP };
- uiAddInputEvent(event);
- }
-
- void uiSetChar(unsigned int value) {
- assert(ui_context);
- UIinputEvent event = { value, 0, UI_CHAR };
- uiAddInputEvent(event);
- }
-
- void uiSetScroll(int x, int y) {
- assert(ui_context);
- ui_context->scroll.x += x;
- ui_context->scroll.y += y;
- }
-
- UIvec2 uiGetScroll() {
- assert(ui_context);
- return ui_context->scroll;
- }
-
- int uiGetLastButton(int button) {
- assert(ui_context);
- return (ui_context->last_buttons & (1ull<<button))?1:0;
- }
-
- int uiGetButton(int button) {
- assert(ui_context);
- return (ui_context->buttons & (1ull<<button))?1:0;
- }
-
- int uiButtonPressed(int button) {
- assert(ui_context);
- return !uiGetLastButton(button) && uiGetButton(button);
- }
-
- int uiButtonReleased(int button) {
- assert(ui_context);
- return uiGetLastButton(button) && !uiGetButton(button);
- }
-
- void uiSetCursor(int x, int y) {
- assert(ui_context);
- ui_context->cursor.x = x;
- ui_context->cursor.y = y;
- }
-
- UIvec2 uiGetCursor() {
- assert(ui_context);
- return ui_context->cursor;
- }
-
- UIvec2 uiGetCursorStart() {
- assert(ui_context);
- return ui_context->start_cursor;
- }
-
- UIvec2 uiGetCursorDelta() {
- assert(ui_context);
- UIvec2 result = {{{
- ui_context->cursor.x - ui_context->last_cursor.x,
- ui_context->cursor.y - ui_context->last_cursor.y
- }}};
- return result;
- }
-
- UIvec2 uiGetCursorStartDelta() {
- assert(ui_context);
- UIvec2 result = {{{
- ui_context->cursor.x - ui_context->start_cursor.x,
- ui_context->cursor.y - ui_context->start_cursor.y
- }}};
- return result;
- }
-
- unsigned int uiGetKey() {
- assert(ui_context);
- return ui_context->active_key;
- }
-
- unsigned int uiGetModifier() {
- assert(ui_context);
- return ui_context->active_modifier;
- }
-
- int uiGetItemCount() {
- assert(ui_context);
- return ui_context->count;
- }
-
- int uiGetLastItemCount() {
- assert(ui_context);
- return ui_context->last_count;
- }
-
- unsigned int uiGetAllocSize() {
- assert(ui_context);
- return ui_context->datasize;
- }
-
- UIitem *uiItemPtr(int item) {
- assert(ui_context && (item >= 0) && (item < ui_context->count));
- return ui_context->items + item;
- }
-
- UIitem *uiLastItemPtr(int item) {
- assert(ui_context && (item >= 0) && (item < ui_context->last_count));
- return ui_context->last_items + item;
- }
-
- int uiGetHotItem() {
- assert(ui_context);
- return ui_context->hot_item;
- }
-
- void uiFocus(int item) {
- assert(ui_context && (item >= -1) && (item < ui_context->count));
- assert(ui_context->stage != UI_STAGE_LAYOUT);
- ui_context->focus_item = item;
- }
-
- static void uiValidateStateItems() {
- assert(ui_context);
- ui_context->last_hot_item = uiRecoverItem(ui_context->last_hot_item);
- ui_context->active_item = uiRecoverItem(ui_context->active_item);
- ui_context->focus_item = uiRecoverItem(ui_context->focus_item);
- ui_context->last_click_item = uiRecoverItem(ui_context->last_click_item);
- }
-
- int uiGetFocusedItem() {
- assert(ui_context);
- return ui_context->focus_item;
- }
-
-
- void uiBeginLayout() {
- assert(ui_context);
- assert(ui_context->stage == UI_STAGE_PROCESS); // must run uiEndLayout(), uiProcess() first
- uiClear();
- ui_context->stage = UI_STAGE_LAYOUT;
- }
-
- void uiClearState() {
- assert(ui_context);
- ui_context->last_hot_item = -1;
- ui_context->active_item = -1;
- ui_context->focus_item = -1;
- ui_context->last_click_item = -1;
- }
-
- int uiItem() {
- assert(ui_context);
- assert(ui_context->stage == UI_STAGE_LAYOUT); // must run between uiBeginLayout() and uiEndLayout()
- assert(ui_context->count < (int)ui_context->item_capacity);
- int idx = ui_context->count++;
- UIitem *item = uiItemPtr(idx);
- memset(item, 0, sizeof(UIitem));
- item->firstkid = -1;
- item->nextitem = -1;
- return idx;
- }
-
- void uiNotifyItem(int item, UIevent event) {
- assert(ui_context);
- if (!ui_context->handler)
- return;
- assert((event & UI_ITEM_EVENT_MASK) == event);
- UIitem *pitem = uiItemPtr(item);
- if (pitem->flags & event) {
- ui_context->handler(item, event);
- }
- }
-
- UI_INLINE int uiLastChild(int item) {
- item = uiFirstChild(item);
- if (item < 0)
- return -1;
- while (true) {
- int nextitem = uiNextSibling(item);
- if (nextitem < 0)
- return item;
- item = nextitem;
- }
- }
-
- int uiAppend(int item, int sibling) {
- assert(sibling > 0);
- UIitem *pitem = uiItemPtr(item);
- UIitem *psibling = uiItemPtr(sibling);
- assert(!(psibling->flags & UI_ITEM_INSERTED));
- psibling->nextitem = pitem->nextitem;
- psibling->flags |= UI_ITEM_INSERTED;
- pitem->nextitem = sibling;
- return sibling;
- }
-
- int uiInsert(int item, int child) {
- assert(child > 0);
- UIitem *pparent = uiItemPtr(item);
- UIitem *pchild = uiItemPtr(child);
- assert(!(pchild->flags & UI_ITEM_INSERTED));
- if (pparent->firstkid < 0) {
- pparent->firstkid = child;
- pchild->flags |= UI_ITEM_INSERTED;
- } else {
- uiAppend(uiLastChild(item), child);
- }
- return child;
- }
-
- int uiInsertFront(int item, int child) {
- return uiInsert(item, child);
- }
-
- int uiInsertBack(int item, int child) {
- assert(child > 0);
- UIitem *pparent = uiItemPtr(item);
- UIitem *pchild = uiItemPtr(child);
- assert(!(pchild->flags & UI_ITEM_INSERTED));
- pchild->nextitem = pparent->firstkid;
- pparent->firstkid = child;
- pchild->flags |= UI_ITEM_INSERTED;
- return child;
- }
-
- void uiSetFrozen(int item, int enable) {
- UIitem *pitem = uiItemPtr(item);
- if (enable)
- pitem->flags |= UI_ITEM_FROZEN;
- else
- pitem->flags &= ~UI_ITEM_FROZEN;
- }
-
- void uiSetSize(int item, int w, int h) {
- UIitem *pitem = uiItemPtr(item);
- pitem->size[0] = w;
- pitem->size[1] = h;
- }
-
- int uiGetWidth(int item) {
- return uiItemPtr(item)->size[0];
- }
-
- int uiGetHeight(int item) {
- return uiItemPtr(item)->size[1];
- }
-
- void uiSetLayout(int item, unsigned int flags) {
- UIitem *pitem = uiItemPtr(item);
- assert((flags & UI_ITEM_LAYOUT_MASK) == (unsigned int)flags);
- pitem->flags &= ~UI_ITEM_LAYOUT_MASK;
- pitem->flags |= flags & UI_ITEM_LAYOUT_MASK;
- }
-
- unsigned int uiGetLayout(int item) {
- return uiItemPtr(item)->flags & UI_ITEM_LAYOUT_MASK;
- }
-
- void uiSetBox(int item, unsigned int flags) {
- UIitem *pitem = uiItemPtr(item);
- assert((flags & UI_ITEM_BOX_MASK) == (unsigned int)flags);
- pitem->flags &= ~UI_ITEM_BOX_MASK;
- pitem->flags |= flags & UI_ITEM_BOX_MASK;
- }
-
- unsigned int uiGetBox(int item) {
- return uiItemPtr(item)->flags & UI_ITEM_BOX_MASK;
- }
-
- void uiSetMargins(int item, short l, short t, short r, short b) {
- UIitem *pitem = uiItemPtr(item);
- pitem->margins[0] = l;
- pitem->margins[1] = t;
- pitem->margins[2] = r;
- pitem->margins[3] = b;
- }
-
- short uiGetMarginLeft(int item) {
- return uiItemPtr(item)->margins[0];
- }
- short uiGetMarginTop(int item) {
- return uiItemPtr(item)->margins[1];
- }
- short uiGetMarginRight(int item) {
- return uiItemPtr(item)->margins[2];
- }
- short uiGetMarginDown(int item) {
- return uiItemPtr(item)->margins[3];
- }
-
- // compute bounding box of all items super-imposed
- UI_INLINE void uiComputeImposedSize(UIitem *pitem, int dim) {
- int wdim = dim+2;
- // 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);
- kid = uiNextSibling(kid);
- }
- pitem->size[dim] = need_size;
- }
-
- // compute bounding box of all items stacked
- UI_INLINE void uiComputeStackedSize(UIitem *pitem, int dim) {
- int wdim = dim+2;
- short need_size = 0;
- int kid = pitem->firstkid;
- while (kid >= 0) {
- UIitem *pkid = uiItemPtr(kid);
- // width += start margin + calculated width + end margin
- need_size += pkid->margins[dim] + pkid->size[dim] + pkid->margins[wdim];
- kid = uiNextSibling(kid);
- }
- pitem->size[dim] = need_size;
- }
-
- // compute bounding box of all items stacked, repeating when breaking
- UI_INLINE void uiComputeWrappedStackedSize(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_BREAK) {
- need_size2 = ui_max(need_size2, need_size);
- // newline
- need_size = 0;
- }
-
- // width = start margin + calculated width + end margin
- need_size += pkid->margins[dim] + pkid->size[dim] + pkid->margins[wdim];
- kid = uiNextSibling(kid);
- }
- pitem->size[dim] = ui_max(need_size2, need_size);
- }
-
- // 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_BREAK) {
- 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 = pitem->firstkid;
- while (kid >= 0) {
- uiComputeSize(kid, dim);
- kid = uiNextSibling(kid);
- }
-
- if (pitem->size[dim])
- return;
- switch(pitem->flags & UI_ITEM_BOX_MODEL_MASK) {
- case UI_COLUMN|UI_WRAP: {
- // flex model
- if (dim) // direction
- uiComputeStackedSize(pitem, 1);
- else
- uiComputeImposedSize(pitem, 0);
- } break;
- case UI_ROW|UI_WRAP: {
- // flex model
- if (!dim) // direction
- uiComputeWrappedStackedSize(pitem, 0);
- else
- uiComputeWrappedSize(pitem, 1);
- } break;
- case UI_COLUMN:
- case UI_ROW: {
- // flex model
- if ((pitem->flags & 1) == (unsigned int)dim) // direction
- uiComputeStackedSize(pitem, dim);
- else
- uiComputeImposedSize(pitem, dim);
- } break;
- default: {
- // layout model
- uiComputeImposedSize(pitem, dim);
- } break;
- }
- }
-
- // stack all items according to their alignment
- UI_INLINE void uiArrangeStacked(UIitem *pitem, int dim, bool wrap) {
- int wdim = dim+2;
-
- short space = pitem->size[dim];
- float max_x2 = (float)pitem->margins[dim] + (float)space;
-
- int start_kid = pitem->firstkid;
- while (start_kid >= 0) {
- short used = 0;
-
- int count = 0; // count of fillers
- int fixed_count = 0; // count of fixed elements
- int total = 0;
- bool hardbreak = false;
- // 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;
- short extend = used;
- if ((flags & UI_HFILL) == UI_HFILL) { // grow
- count++;
- extend += pkid->margins[dim] + pkid->margins[wdim];
- } else {
- fixed_count++;
- extend += pkid->margins[dim] + pkid->size[dim] + pkid->margins[wdim];
- }
- // wrap on end of line or manual flag
- if (wrap && (total && ((extend > space) || (pkid->flags & UI_BREAK)))) {
- end_kid = kid;
- hardbreak = ((pkid->flags & UI_BREAK) == UI_BREAK);
- // add marker for subsequent queries
- pkid->flags |= UI_BREAK;
- break;
- } else {
- used = extend;
- kid = uiNextSibling(kid);
- }
- total++;
- }
-
- int extra_space = space - used;
- float filler = 0.0f;
- float spacer = 0.0f;
- float extra_margin = 0.0f;
- float eater = 0.0f;
-
- if (extra_space > 0) {
- 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: {
- // justify when not wrapping or not in last line,
- // or not manually breaking
- if (!wrap || ((end_kid != -1) && !hardbreak))
- spacer = (float)extra_space / (float)(total-1);
- } break;
- case UI_START: {
- } break;
- case UI_END: {
- extra_margin = extra_space;
- } break;
- }
- }
- } else if (!wrap && (extra_space < 0)) {
- eater = (float)extra_space / (float)fixed_count;
- }
-
- // 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+ui_maxf(0.0f,(float)pkid->size[dim]+eater);
- }
- ix0 = (short)x;
- ix1 = (short)ui_minf(max_x2-(float)pkid->margins[wdim], 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 uiArrangeImposedRange(UIitem *pitem, int dim,
- int start_kid, int end_kid, short offset, short space) {
- int wdim = dim+2;
-
- int kid = start_kid;
- while (kid != end_kid) {
- UIitem *pkid = uiItemPtr(kid);
-
- int flags = (pkid->flags & UI_ITEM_LAYOUT_MASK) >> dim;
-
- switch(flags & UI_HFILL) {
- default: break;
- case UI_HCENTER: {
- pkid->margins[dim] += (space-pkid->size[dim])/2 - pkid->margins[wdim];
- } break;
- case UI_RIGHT: {
- pkid->margins[dim] = space-pkid->size[dim]-pkid->margins[wdim];
- } break;
- case UI_HFILL: {
- pkid->size[dim] = ui_max(0,space-pkid->margins[dim]-pkid->margins[wdim]);
- } break;
- }
- pkid->margins[dim] += offset;
-
- kid = uiNextSibling(kid);
- }
- }
-
- 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,
- // squeeze items that expand the available space
- UI_INLINE void uiArrangeImposedSqueezedRange(UIitem *pitem, int dim,
- int start_kid, int end_kid, short offset, short space) {
- int wdim = dim+2;
-
- int kid = start_kid;
- while (kid != end_kid) {
- UIitem *pkid = uiItemPtr(kid);
-
- int flags = (pkid->flags & UI_ITEM_LAYOUT_MASK) >> dim;
-
- short min_size = ui_max(0,space-pkid->margins[dim]-pkid->margins[wdim]);
- switch(flags & UI_HFILL) {
- default: {
- pkid->size[dim] = ui_min(pkid->size[dim], min_size);
- } break;
- case UI_HCENTER: {
- pkid->size[dim] = ui_min(pkid->size[dim], min_size);
- pkid->margins[dim] += (space-pkid->size[dim])/2 - pkid->margins[wdim];
- } break;
- case UI_RIGHT: {
- pkid->size[dim] = ui_min(pkid->size[dim], min_size);
- pkid->margins[dim] = space-pkid->size[dim]-pkid->margins[wdim];
- } break;
- case UI_HFILL: {
- pkid->size[dim] = min_size;
- } break;
- }
- pkid->margins[dim] += offset;
-
- kid = uiNextSibling(kid);
- }
- }
-
- UI_INLINE void uiArrangeImposedSqueezed(UIitem *pitem, int dim) {
- uiArrangeImposedSqueezedRange(pitem, dim, pitem->firstkid, -1, pitem->margins[dim], pitem->size[dim]);
- }
-
- // superimpose all items according to their alignment
- UI_INLINE short uiArrangeWrappedImposedSqueezed(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_BREAK) {
- uiArrangeImposedSqueezedRange(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);
- }
-
- uiArrangeImposedSqueezedRange(pitem, dim, start_kid, -1, offset, need_size);
- offset += need_size;
- return offset;
- }
-
- static void uiArrange(int item, int dim) {
- UIitem *pitem = uiItemPtr(item);
-
- switch(pitem->flags & UI_ITEM_BOX_MODEL_MASK) {
- case UI_COLUMN|UI_WRAP: {
- // flex model, wrapping
- if (dim) { // direction
- uiArrangeStacked(pitem, 1, true);
- // this retroactive resize will not effect parent widths
- short offset = uiArrangeWrappedImposedSqueezed(pitem, 0);
- pitem->size[0] = offset - pitem->margins[0];
- }
- } break;
- case UI_ROW|UI_WRAP: {
- // flex model, wrapping
- if (!dim) { // direction
- uiArrangeStacked(pitem, 0, true);
- } else {
- uiArrangeWrappedImposedSqueezed(pitem, 1);
- }
- } break;
- case UI_COLUMN:
- case UI_ROW: {
- // flex model
- if ((pitem->flags & 1) == (unsigned int)dim) // direction
- uiArrangeStacked(pitem, dim, false);
- else
- uiArrangeImposedSqueezed(pitem, dim);
- } break;
- default: {
- // layout model
- uiArrangeImposed(pitem, dim);
- } break;
- }
-
- int kid = uiFirstChild(item);
- while (kid >= 0) {
- uiArrange(kid, dim);
- kid = uiNextSibling(kid);
- }
- }
-
- UI_INLINE bool uiCompareItems(UIitem *item1, UIitem *item2) {
- return ((item1->flags & UI_ITEM_COMPARE_MASK) == (item2->flags & UI_ITEM_COMPARE_MASK));
-
- }
-
- static bool uiMapItems(int item1, int item2) {
- UIitem *pitem1 = uiLastItemPtr(item1);
- if (item2 == -1) {
- return false;
- }
-
- UIitem *pitem2 = uiItemPtr(item2);
- if (!uiCompareItems(pitem1, pitem2)) {
- return false;
- }
-
- int count = 0;
- int failed = 0;
- int kid1 = pitem1->firstkid;
- int kid2 = pitem2->firstkid;
- while (kid1 != -1) {
- UIitem *pkid1 = uiLastItemPtr(kid1);
- count++;
- if (!uiMapItems(kid1, kid2)) {
- failed = count;
- break;
- }
- kid1 = pkid1->nextitem;
- if (kid2 != -1) {
- kid2 = uiItemPtr(kid2)->nextitem;
- }
- }
-
- if (count && (failed == 1)) {
- return false;
- }
-
- ui_context->item_map[item1] = item2;
- return true;
- }
-
- int uiRecoverItem(int olditem) {
- assert(ui_context);
- assert((olditem >= -1) && (olditem < ui_context->last_count));
- if (olditem == -1) return -1;
- return ui_context->item_map[olditem];
- }
-
- void uiRemapItem(int olditem, int newitem) {
- assert(ui_context);
- assert((olditem >= 0) && (olditem < ui_context->last_count));
- assert((newitem >= -1) && (newitem < ui_context->count));
- ui_context->item_map[olditem] = newitem;
- }
-
- void uiEndLayout() {
- assert(ui_context);
- assert(ui_context->stage == UI_STAGE_LAYOUT); // must run uiBeginLayout() first
-
- if (ui_context->count) {
- uiComputeSize(0,0);
- uiArrange(0,0);
- uiComputeSize(0,1);
- uiArrange(0,1);
-
- if (ui_context->last_count) {
- // map old item id to new item id
- uiMapItems(0,0);
- }
- }
-
- uiValidateStateItems();
- if (ui_context->count) {
- // drawing routines may require this to be set already
- uiUpdateHotItem();
- }
-
- ui_context->stage = UI_STAGE_POST_LAYOUT;
- }
-
- UIrect uiGetRect(int item) {
- UIitem *pitem = uiItemPtr(item);
- UIrect rc = {{{
- pitem->margins[0], pitem->margins[1],
- pitem->size[0], pitem->size[1]
- }}};
- return rc;
- }
-
- int uiFirstChild(int item) {
- return uiItemPtr(item)->firstkid;
- }
-
- int uiNextSibling(int item) {
- return uiItemPtr(item)->nextitem;
- }
-
- void *uiAllocHandle(int item, unsigned int size) {
- assert((size > 0) && (size < UI_MAX_DATASIZE));
- UIitem *pitem = uiItemPtr(item);
- assert(pitem->handle == NULL);
- assert((ui_context->datasize+size) <= ui_context->buffer_capacity);
- pitem->handle = ui_context->data + ui_context->datasize;
- pitem->flags |= UI_ITEM_DATA;
- ui_context->datasize += size;
- return pitem->handle;
- }
-
- void uiSetHandle(int item, void *handle) {
- UIitem *pitem = uiItemPtr(item);
- assert(pitem->handle == NULL);
- pitem->handle = handle;
- }
-
- void *uiGetHandle(int item) {
- return uiItemPtr(item)->handle;
- }
-
- void uiSetHandler(UIhandler handler) {
- assert(ui_context);
- ui_context->handler = handler;
- }
-
- UIhandler uiGetHandler() {
- assert(ui_context);
- return ui_context->handler;
- }
-
- void uiSetEvents(int item, unsigned int flags) {
- UIitem *pitem = uiItemPtr(item);
- pitem->flags &= ~UI_ITEM_EVENT_MASK;
- pitem->flags |= flags & UI_ITEM_EVENT_MASK;
- }
-
- unsigned int uiGetEvents(int item) {
- return uiItemPtr(item)->flags & UI_ITEM_EVENT_MASK;
- }
-
- void uiSetFlags(int item, unsigned int flags) {
- UIitem *pitem = uiItemPtr(item);
- pitem->flags &= ~UI_USERMASK;
- pitem->flags |= flags & UI_USERMASK;
- }
-
- unsigned int uiGetFlags(int item) {
- return uiItemPtr(item)->flags & UI_USERMASK;
- }
-
- int uiContains(int item, int x, int y) {
- UIrect rect = uiGetRect(item);
- x -= rect.x;
- y -= rect.y;
- if ((x>=0)
- && (y>=0)
- && (x<rect.w)
- && (y<rect.h)) return 1;
- return 0;
- }
-
- int uiFindItem(int item, int x, int y, unsigned int flags, unsigned int mask) {
- UIitem *pitem = uiItemPtr(item);
- if (pitem->flags & UI_ITEM_FROZEN) return -1;
- if (uiContains(item, x, y)) {
- int best_hit = -1;
- int kid = uiFirstChild(item);
- while (kid >= 0) {
- int hit = uiFindItem(kid, x, y, flags, mask);
- if (hit >= 0) {
- best_hit = hit;
- }
- kid = uiNextSibling(kid);
- }
- if (best_hit >= 0) {
- return best_hit;
- }
- if (((mask == UI_ANY) && ((flags == UI_ANY)
- || (pitem->flags & flags)))
- || ((pitem->flags & flags) == mask)) {
- return item;
- }
- }
- return -1;
- }
-
- void uiUpdateHotItem() {
- assert(ui_context);
- if (!ui_context->count) return;
- ui_context->hot_item = uiFindItem(0,
- ui_context->cursor.x, ui_context->cursor.y,
- UI_ANY_MOUSE_INPUT, UI_ANY);
- }
-
- int uiGetClicks() {
- return ui_context->clicks;
- }
-
- void uiProcess(int timestamp) {
- assert(ui_context);
-
- assert(ui_context->stage != UI_STAGE_LAYOUT); // must run uiBeginLayout(), uiEndLayout() first
-
- if (ui_context->stage == UI_STAGE_PROCESS) {
- uiUpdateHotItem();
- }
- ui_context->stage = UI_STAGE_PROCESS;
-
- if (!ui_context->count) {
- uiClearInputEvents();
- return;
- }
-
- int hot_item = ui_context->last_hot_item;
- int active_item = ui_context->active_item;
- int focus_item = ui_context->focus_item;
-
- // send all keyboard events
- if (focus_item >= 0) {
- for (int i = 0; i < ui_context->eventcount; ++i) {
- ui_context->active_key = ui_context->events[i].key;
- ui_context->active_modifier = ui_context->events[i].mod;
- uiNotifyItem(focus_item,
- ui_context->events[i].event);
- }
- } else {
- ui_context->focus_item = -1;
- }
- if (ui_context->scroll.x || ui_context->scroll.y) {
- int scroll_item = uiFindItem(0,
- ui_context->cursor.x, ui_context->cursor.y,
- UI_SCROLL, UI_ANY);
- if (scroll_item >= 0) {
- uiNotifyItem(scroll_item, UI_SCROLL);
- }
- }
-
- uiClearInputEvents();
-
- int hot = ui_context->hot_item;
-
- switch(ui_context->state) {
- default:
- case UI_STATE_IDLE: {
- ui_context->start_cursor = ui_context->cursor;
- if (uiGetButton(0)) {
- hot_item = -1;
- active_item = hot;
-
- if (active_item != focus_item) {
- focus_item = -1;
- ui_context->focus_item = -1;
- }
-
- if (active_item >= 0) {
- if (
- ((timestamp - ui_context->last_click_timestamp) > UI_CLICK_THRESHOLD)
- || (ui_context->last_click_item != active_item)) {
- ui_context->clicks = 0;
- }
- ui_context->clicks++;
-
- ui_context->last_click_timestamp = timestamp;
- ui_context->last_click_item = active_item;
- ui_context->active_modifier = ui_context->active_button_modifier;
- uiNotifyItem(active_item, UI_BUTTON0_DOWN);
- }
- ui_context->state = UI_STATE_CAPTURE;
- } else if (uiGetButton(2) && !uiGetLastButton(2)) {
- hot_item = -1;
- hot = uiFindItem(0, ui_context->cursor.x, ui_context->cursor.y,
- UI_BUTTON2_DOWN, UI_ANY);
- if (hot >= 0) {
- ui_context->active_modifier = ui_context->active_button_modifier;
- uiNotifyItem(hot, UI_BUTTON2_DOWN);
- }
- } else {
- hot_item = hot;
- }
- } break;
- case UI_STATE_CAPTURE: {
- if (!uiGetButton(0)) {
- if (active_item >= 0) {
- ui_context->active_modifier = ui_context->active_button_modifier;
- uiNotifyItem(active_item, UI_BUTTON0_UP);
- if (active_item == hot) {
- uiNotifyItem(active_item, UI_BUTTON0_HOT_UP);
- }
- }
- active_item = -1;
- ui_context->state = UI_STATE_IDLE;
- } else {
- if (active_item >= 0) {
- ui_context->active_modifier = ui_context->active_button_modifier;
- uiNotifyItem(active_item, UI_BUTTON0_CAPTURE);
- }
- if (hot == active_item)
- hot_item = hot;
- else
- hot_item = -1;
- }
- } break;
- }
-
- ui_context->last_cursor = ui_context->cursor;
- ui_context->last_hot_item = hot_item;
- ui_context->active_item = active_item;
-
- ui_context->last_timestamp = timestamp;
- ui_context->last_buttons = ui_context->buttons;
- }
-
- static int uiIsActive(int item) {
- assert(ui_context);
- return ui_context->active_item == item;
- }
-
- static int uiIsHot(int item) {
- assert(ui_context);
- return ui_context->last_hot_item == item;
- }
-
- static int uiIsFocused(int item) {
- assert(ui_context);
- return ui_context->focus_item == item;
- }
-
- UIitemState uiGetState(int item) {
- UIitem *pitem = uiItemPtr(item);
- if (pitem->flags & UI_ITEM_FROZEN) return UI_FROZEN;
- if (uiIsFocused(item)) {
- if (pitem->flags & (UI_KEY_DOWN|UI_CHAR|UI_KEY_UP)) return UI_ACTIVE;
- }
- if (uiIsActive(item)) {
- if (pitem->flags & (UI_BUTTON0_CAPTURE|UI_BUTTON0_UP)) return UI_ACTIVE;
- if ((pitem->flags & UI_BUTTON0_HOT_UP)
- && uiIsHot(item)) return UI_ACTIVE;
- return UI_COLD;
- } else if (uiIsHot(item)) {
- return UI_HOT;
- }
- return UI_COLD;
- }
-
- #endif // OUI_IMPLEMENTATION
|