2 OUI - A minimal semi-immediate GUI handling & layouting library
4 Copyright (c) 2014 Leonard Ritter <leonard.ritter@duangle.com>
6 Permission is hereby granted, free of charge, to any person obtaining a copy
7 of this software and associated documentation files (the "Software"), to deal
8 in the Software without restriction, including without limitation the rights
9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 copies of the Software, and to permit persons to whom the Software is
11 furnished to do so, subject to the following conditions:
13 The above copyright notice and this permission notice shall be included in
14 all copies or substantial portions of the Software.
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 /* Invisible Vector Library
25 * ported by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
26 * Understanding is not required. Only obedience.
27 * yes, this D port is GPLed.
29 * This program is free software: you can redistribute it and/or modify
30 * it under the terms of the GNU General Public License as published by
31 * the Free Software Foundation, version 3 of the License ONLY.
33 * This program is distributed in the hope that it will be useful,
34 * but WITHOUT ANY WARRANTY; without even the implied warranty of
35 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36 * GNU General Public License for more details.
38 * You should have received a copy of the GNU General Public License
39 * along with this program. If not, see <http://www.gnu.org/licenses/>.
41 module iv
.nanovega
.oui
.engine
is aliced
;
43 import core
.stdc
.stdlib
: malloc
, free
;
44 import core
.stdc
.string
: memset
;
47 Revision 4 (2014-12-17)
49 OUI (short for "Open UI", spoken like the french "oui" for "yes") is a
50 platform agnostic single-header C library for layouting GUI elements and
51 handling related user input. Together with a set of widget drawing and logic
52 routines it can be used to build complex user interfaces.
54 OUI is a semi-immediate GUI. Widget declarations are persistent for the duration
55 of the setup and evaluation, but do not need to be kept around longer than one
58 OUI has no widget types; instead, it provides only one kind of element, "Items",
59 which can be tailored to the application by the user and expanded with custom
60 buffers and event handlers to behave as containers, buttons, sliders, radio
63 OUI also does not draw anything; Instead it provides a set of functions to
64 iterate and query the layouted items in order to allow client code to render
65 each widget with its current state using a preferred graphics library.
67 See example.cpp in the repository for a full usage example.
69 A basic setup for OUI usage looks like this:
70 ============================================
72 // a header for each widget
78 /// global event dispatch
79 void ui_handler (int item, UIevent event) {
80 auto data = uiGetHandle!Data(item);
81 if (data !is null && data.handler !is null) data.handler(item, event);
85 UIcontextP context = uiCreateContext(4096, 1<<20);
86 uiMakeCurrent(context);
87 uiSetHandler(ui_handler);
89 while (app_running()) {
90 // update position of mouse cursor; the ui can also be updated
91 // from received events.
92 uiSetCursor(app_get_mouse_x(), app_get_mouse_y());
94 // update button state
95 for (int i = 0; i < 3; ++i) uiSetButton(i, app_get_button_state(i));
97 // you can also send keys and scroll events; see "oui_example.d" for more
100 // this section does not have to be regenerated on frame; a good
101 // policy is to invalidate it on events, as this usually alters
102 // structure and layout.
104 // begin new UI declarations
107 // - UI setup code goes here -
115 // draw UI, starting with the first item, index 0
116 app_draw_ui(render_context, 0);
118 // update states and fire handlers
119 uiProcess(get_time_ms());
122 uiDestroyContext(context);
125 Here's an example setup for a checkbox control:
126 ===============================================
128 typedef struct CheckBoxData {
134 // called when the item is clicked (see checkbox())
135 void app_checkbox_handler (int item, UIevent event) {
136 // retrieve custom data (see checkbox())
137 auto data = uiGetHandle!CheckBoxData(item);
140 case UI_BUTTON0_DOWN:
142 *data.checked = !(*data.checked);
147 // creates a checkbox control for a pointer to a boolean
148 int checkbox (sting label, bool* checked) {
149 // create new ui item
152 // set minimum size of wiget; horizontal size is dynamic, vertical is fixed
153 uiSetSize(item, 0, APP_WIDGET_HEIGHT);
155 // store some custom data with the checkbox that we use for rendering
156 // and value changes.
157 auto data = uiAllocHandle!CheckBoxData(item);
159 // assign a custom typeid to the data so the renderer knows how to
160 // render this control, and our event handler
161 data.head.type = APP_WIDGET_CHECKBOX;
162 data.head.handler = toDelegate(&app_checkbox_handler);
164 data.checked = checked;
166 // set to fire as soon as the left button is
167 // pressed; UI_BUTTON0_HOT_UP is also a popular alternative.
168 uiSetEvents(item, UI_BUTTON0_DOWN);
173 A simple recursive drawing routine can look like this:
174 ======================================================
176 void app_draw_ui (AppRenderContext* ctx, int item) {
177 // retrieve custom data and cast it to Data; we assume the first member
178 // of every widget data item to be a Data field.
179 auto head = uiGetHandle!Data(item);
181 // if a handle is set, this is a specialized widget
183 // get the widgets absolute rectangle
184 UIrect rect = uiGetRect(item);
187 case APP_WIDGET_LABEL:
190 case APP_WIDGET_BUTTON:
193 case APP_WIDGET_CHECKBOX:
194 // cast to the full data type
195 auto data = cast(CheckBoxData*)head;
197 // get the widgets current state
198 int state = uiGetState(item);
200 // if the value is set, the state is always active
201 if (*data.checked) state = UI_ACTIVE;
204 app_draw_checkbox(ctx, rect, state, data.label);
209 // iterate through all children and draw
210 int kid = uiFirstChild(item);
212 app_draw_ui(ctx, kid);
213 kid = uiNextSibling(kid);
217 Layouting items works like this:
218 ================================
220 void layout_window (int w, int h) {
221 // create root item; the first item always has index 0
222 int parent = uiItem();
224 uiSetSize(parent, w, h);
226 // create column box and use as new parent
227 parent = uiInsert(parent, uiItem());
228 // configure as column
229 uiSetBox(parent, UI_COLUMN);
230 // span horizontally, attach to top
231 uiSetLayout(parent, UI_HFILL|UI_TOP);
233 // add a label - we're assuming custom control functions to exist
234 int item = uiInsert(parent, label("Hello World"));
235 // set a fixed height for the label
236 uiSetSize(item, 0, APP_WIDGET_HEIGHT);
237 // span the label horizontally
238 uiSetLayout(item, UI_HFILL);
240 static bool checked = false;
242 // add a checkbox to the same parent as item; this is faster than
243 // calling uiInsert on the same parent repeatedly.
244 item = uiAppend(item, checkbox("Checked:", &checked));
245 // set a fixed height for the checkbox
246 uiSetSize(item, 0, APP_WIDGET_HEIGHT);
247 // span the checkbox in the same way as the label
248 uiSetLayout(item, UI_HFILL);
252 // ////////////////////////////////////////////////////////////////////////// //
255 // maximum size in bytes of a single data buffer passed to uiAllocData().
256 UI_MAX_DATASIZE
= 4096,
257 // maximum depth of nested containers
259 // maximum number of buffered input events
260 UI_MAX_INPUT_EVENTS
= 64,
261 // consecutive click threshold in ms
262 UI_CLICK_THRESHOLD
= 250,
268 //typedef struct UIcontext UIcontext;
269 alias UIcontextP
= UIcontext
*;
271 // item states as returned by uiGetState()
273 alias UIitemState
= int;
274 enum /*UIitemState*/ {
275 // the item is inactive
277 // the item is inactive, but the cursor is hovering over this item
279 // the item is toggled, activated, focused (depends on item kind)
281 // the item is unresponsive
285 // container flags to pass to uiSetBox()
286 alias UIboxFlags
= int;
287 enum /*UIboxFlags*/ {
288 // flex-direction (bit 0+1)
306 // multi-line, wrap left to right
310 // justify-content (start, end, center, space-between)
311 // at start of row/column
313 // at center of row/column
315 // at end of row/column
317 // insert spacing to stretch across whole row/column
321 // can be implemented by putting a flex container in a layout container,
322 // then using UI_TOP, UI_DOWN, UI_VFILL, UI_VCENTER, etc.
323 // FILL is equivalent to stretch/grow
325 // align-content (start, end, center, stretch)
326 // can be implemented by putting a flex container in a layout container,
327 // then using UI_TOP, UI_DOWN, UI_VFILL, UI_VCENTER, etc.
328 // FILL is equivalent to stretch; space-between is not supported.
331 // child layout flags to pass to uiSetLayout()
332 alias UIlayoutFlags
= int;
333 enum /*UIlayoutFlags*/ {
334 // attachments (bit 5-8)
335 // fully valid when parent uses UI_LAYOUT model
336 // partially valid when in UI_FLEX model
338 // anchor to left item or left side of parent
340 // anchor to top item or top side of parent
342 // anchor to right item or right side of parent
344 // anchor to bottom item or bottom side of parent
346 // anchor to both left and right item or parent borders
348 // anchor to both top and bottom item or parent borders
350 // center horizontally, with left margin as offset
352 // center vertically, with top margin as offset
354 // center in both directions, with left/top margin as offset
356 // anchor to all four directions
358 // when wrapping, put this element on a new line
359 // wrapping layout code auto-inserts UI_BREAK flags,
360 // drawing routines can read them with uiGetLayout()
368 UI_BUTTON0_DOWN
= 0x0400,
370 // when this event has a handler, uiGetState() will return UI_ACTIVE as
371 // long as button 0 is down.
372 UI_BUTTON0_UP
= 0x0800,
373 // on button 0 up while item is hovered
374 // when this event has a handler, uiGetState() will return UI_ACTIVE
375 // when the cursor is hovering the items rectangle; this is the
376 // behavior expected for buttons.
377 UI_BUTTON0_HOT_UP
= 0x1000,
378 // item is being captured (button 0 constantly pressed);
379 // when this event has a handler, uiGetState() will return UI_ACTIVE as
380 // long as button 0 is down.
381 UI_BUTTON0_CAPTURE
= 0x2000,
382 // on button 2 down (right mouse button, usually triggers context menu)
383 UI_BUTTON2_DOWN
= 0x4000,
384 // item has received a scrollwheel event
385 // the accumulated wheel offset can be queried with uiGetScroll()
387 // item is focused and has received a key-down event
388 // the respective key can be queried using uiGetKey() and uiGetModifier()
389 UI_KEY_DOWN
= 0x10000,
390 // item is focused and has received a key-up event
391 // the respective key can be queried using uiGetKey() and uiGetModifier()
393 // item is focused and has received a character event
394 // the respective character can be queried using uiGetKey()
399 // these bits, starting at bit 24, can be safely assigned by the
400 // application, e.g. as item types, other event types, drop targets, etc.
401 // they can be set and queried using uiSetFlags() and uiGetFlags()
402 UI_USERMASK
= 0xff000000,
404 // a special mask passed to uiFindItem()
408 // handler callback; event is one of UI_EVENT_*
409 alias UIhandler
= void delegate (int item
, UIevent event
);
411 // for cursor positions, mainly
412 align(1) struct UIvec2
{
417 align(1) struct { align(1): int x
, y
; }
419 this (int ax
, int ay
) pure nothrow @safe @nogc { pragma(inline
, true); x
= ax
; y
= ay
; }
423 align(1) struct UIrect
{
428 align(1) struct { align(1): int x
, y
, w
, h
; }
430 this (int ax
, int ay
, int aw
, int ah
) pure nothrow @safe @nogc { pragma(inline
, true); x
= ax
; y
= ay
; w
= aw
; h
= ah
; }
434 // unless declared otherwise, all operations have the complexity O(1).
436 // Context Management
437 // ------------------
439 // create a new UI context; call uiMakeCurrent() to make this context the
440 // current context. The context is managed by the client and must be released
441 // using uiDestroyContext()
442 // item_capacity is the maximum of number of items that can be declared.
443 // buffer_capacity is the maximum total size of bytes that can be allocated
444 // using uiAllocHandle(); you may pass 0 if you don't need to allocate
446 // 4096 and (1<<20) are good starting values.
447 //!OUI_EXPORT UIcontext* uiCreateContext(uint item_capacity, uint buffer_capacity);
449 // select an UI context as the current context; a context must always be
450 // selected before using any of the other UI functions
451 //!OUI_EXPORT void uiMakeCurrent(UIcontext* ctx);
453 // release the memory of an UI context created with uiCreateContext(); if the
454 // context is the current context, the current context will be set to null
455 //!OUI_EXPORT void uiDestroyContext(UIcontext* ctx);
457 // returns the currently selected context or null
458 //!OUI_EXPORT UIcontext* uiGetContext();
463 // sets the current cursor position (usually belonging to a mouse) to the
464 // screen coordinates at (x,y)
465 //!OUI_EXPORT void uiSetCursor(int x, int y);
467 // returns the current cursor position in screen coordinates as set by uiSetCursor()
468 OUI_EXPORT UIvec2 uiGetCursor();
470 // returns the offset of the cursor relative to the last call to uiProcess()
471 OUI_EXPORT UIvec2 uiGetCursorDelta();
473 // returns the beginning point of a drag operation.
474 OUI_EXPORT UIvec2 uiGetCursorStart();
476 // returns the offset of the cursor relative to the beginning point of a drag operation.
477 OUI_EXPORT UIvec2 uiGetCursorStartDelta();
479 // sets a mouse or gamepad button as pressed/released
480 // button is in the range 0..63 and maps to an application defined input source.
481 // mod is an application defined set of flags for modifier keys
482 // enabled is `true` for pressed, `false` for released
483 OUI_EXPORT void uiSetButton(uint button, uint mod, int enabled);
485 // returns the current state of an application dependent input button
486 // as set by uiSetButton().
487 // the function returns `true` if the button has been set to pressed, `false` for released.
488 OUI_EXPORT bool uiGetButton(uint button);
490 // returns the number of chained clicks; 1 is a single click,
491 // 2 is a double click, etc.
492 OUI_EXPORT int uiGetClicks();
494 // sets a key as down/up; the key can be any application defined keycode
495 // mod is an application defined set of flags for modifier keys
496 // enabled is 1 for key down, 0 for key up
497 // all key events are being buffered until the next call to uiProcess()
498 OUI_EXPORT void uiSetKey(uint key, uint mod, int enabled);
500 // sends a single character for text input; the character is usually in the
501 // unicode range, but can be application defined.
502 // all char events are being buffered until the next call to uiProcess()
503 OUI_EXPORT void uiSetChar(uint value);
505 // accumulates scroll wheel offsets for the current frame
506 // all offsets are being accumulated until the next call to uiProcess()
507 OUI_EXPORT void uiSetScroll(int x, int y);
509 // returns the currently accumulated scroll wheel offsets for this frame
510 OUI_EXPORT UIvec2 uiGetScroll();
517 // clear the item buffer; uiBeginLayout() should be called before the first
518 // UI declaration for this frame to avoid concatenation of the same UI multiple
520 // After the call, all previously declared item IDs are invalid, and all
521 // application dependent context data has been freed.
522 // uiBeginLayout() must be followed by uiEndLayout().
523 OUI_EXPORT void uiBeginLayout();
525 // layout all added items starting from the root item 0.
526 // after calling uiEndLayout(), no further modifications to the item tree should
527 // be done until the next call to uiBeginLayout().
528 // It is safe to immediately draw the items after a call to uiEndLayout().
529 // this is an O(N) operation for N = number of declared items.
530 OUI_EXPORT void uiEndLayout();
532 // update the current hot item; this only needs to be called if items are kept
533 // for more than one frame and uiEndLayout() is not called
534 OUI_EXPORT void uiUpdateHotItem();
536 // update the internal state according to the current cursor position and
537 // button states, and call all registered handlers.
538 // timestamp is the time in milliseconds relative to the last call to uiProcess()
539 // and is used to estimate the threshold for double-clicks
540 // after calling uiProcess(), no further modifications to the item tree should
541 // be done until the next call to uiBeginLayout().
542 // Items should be drawn before a call to uiProcess()
543 // this is an O(N) operation for N = number of declared items.
544 OUI_EXPORT void uiProcess(int timestamp);
546 // reset the currently stored hot/active etc. handles; this should be called when
547 // a re-declaration of the UI changes the item indices, to avoid state
548 // related glitches because item identities have changed.
549 OUI_EXPORT void uiClearState();
554 // create a new UI item and return the new items ID.
555 OUI_EXPORT int uiItem();
557 // set an items state to frozen; the UI will not recurse into frozen items
558 // when searching for hot or active items; subsequently, frozen items and
559 // their child items will not cause mouse event notifications.
560 // The frozen state is not applied recursively; uiGetState() will report
561 // UI_COLD for child items. Upon encountering a frozen item, the drawing
562 // routine needs to handle rendering of child items appropriately.
563 // see example.cpp for a demonstration.
564 OUI_EXPORT void uiSetFrozen(int item, int enable);
566 // set the application-dependent handle of an item.
567 // handle is an application defined 64-bit handle. If handle is null, the item
568 // will not be interactive.
569 OUI_EXPORT void uiSetHandle(int item, void* handle);
571 // allocate space for application-dependent context data and assign it
572 // as the handle to the item.
573 // The memory of the pointer is managed by the UI context and released
574 // upon the next call to uiBeginLayout()
575 OUI_EXPORT void* uiAllocHandle(int item, uint size);
577 // set the global handler callback for interactive items.
578 // the handler will be called for each item whose event flags are set using
580 OUI_EXPORT void uiSetHandler(UIhandler handler);
582 // flags is a combination of UI_EVENT_* and designates for which events the
583 // handler should be called.
584 OUI_EXPORT void uiSetEvents(int item, uint flags);
586 // flags is a user-defined set of flags defined by UI_USERMASK.
587 OUI_EXPORT void uiSetFlags(int item, uint flags);
589 // assign an item to a container.
590 // an item ID of 0 refers to the root item.
591 // the function returns the child item ID
592 // if the container has already added items, the function searches
593 // for the last item and calls uiAppend() on it, which is an
594 // O(N) operation for N siblings.
595 // it is usually more efficient to call uiInsert() for the first child,
596 // then chain additional siblings using uiAppend().
597 OUI_EXPORT int uiInsert(int item, int child);
599 // assign an item to the same container as another item
600 // sibling is inserted after item.
601 OUI_EXPORT int uiAppend(int item, int sibling);
603 // insert child into container item like uiInsert(), but prepend
604 // it to the first child item, effectively putting it in
606 // it is efficient to call uiInsertBack() repeatedly
607 // in cases where drawing or layout order doesn't matter.
608 OUI_EXPORT int uiInsertBack(int item, int child);
610 // same as uiInsert()
611 OUI_EXPORT int uiInsertFront(int item, int child);
613 // set the size of the item; a size of 0 indicates the dimension to be
614 // dynamic; if the size is set, the item can not expand beyond that size.
615 OUI_EXPORT void uiSetSize(int item, int w, int h);
617 // set the anchoring behavior of the item to one or multiple UIlayoutFlags
618 OUI_EXPORT void uiSetLayout(int item, uint flags);
620 // set the box model behavior of the item to one or multiple UIboxFlags
621 OUI_EXPORT void uiSetBox(int item, uint flags);
623 // set the left, top, right and bottom margins of an item; when the item is
624 // anchored to the parent or another item, the margin controls the distance
625 // from the neighboring element.
626 OUI_EXPORT void uiSetMargins(int item, short l, short t, short r, short b);
628 // set item as recipient of all keyboard events; if item is -1, no item will
630 OUI_EXPORT void uiFocus(int item);
635 // returns the first child item of a container item. If the item is not
636 // a container or does not contain any items, -1 is returned.
637 // if item is 0, the first child item of the root item will be returned.
638 OUI_EXPORT int uiFirstChild(int item);
640 // returns an items next sibling in the list of the parent containers children.
641 // if item is 0 or the item is the last child item, -1 will be returned.
642 OUI_EXPORT int uiNextSibling(int item);
647 // return the total number of allocated items
648 OUI_EXPORT int uiGetItemCount();
650 // return the total bytes that have been allocated by uiAllocHandle()
651 OUI_EXPORT uint uiGetAllocSize();
653 // return the current state of the item. This state is only valid after
654 // a call to uiProcess().
655 // The returned value is one of UI_COLD, UI_HOT, UI_ACTIVE, UI_FROZEN.
656 OUI_EXPORT UIitemState uiGetState(int item);
658 // return the application-dependent handle of the item as passed to uiSetHandle()
659 // or uiAllocHandle().
660 OUI_EXPORT void* uiGetHandle(int item);
662 // return the item that is currently under the cursor or -1 for none
663 OUI_EXPORT int uiGetHotItem();
665 // return the item that is currently focused or -1 for none
666 OUI_EXPORT int uiGetFocusedItem();
668 // returns the topmost item containing absolute location (x,y), starting with
669 // item as parent, using a set of flags and masks as filter:
670 // if both flags and mask are UI_ANY, the first topmost item is returned.
671 // if mask is UI_ANY, the first topmost item matching *any* of flags is returned.
672 // otherwise the first item matching (item.flags&flags) == mask is returned.
673 // you may combine box, layout, event and user flags.
674 // frozen items will always be ignored.
675 OUI_EXPORT int uiFindItem(int item, int x, int y,
676 uint flags, uint mask);
678 // return the handler callback as passed to uiSetHandler()
679 OUI_EXPORT UIhandler uiGetHandler();
680 // return the event flags for an item as passed to uiSetEvents()
681 OUI_EXPORT uint uiGetEvents(int item);
682 // return the user-defined flags for an item as passed to uiSetFlags()
683 OUI_EXPORT uint uiGetFlags(int item);
685 // when handling a KEY_DOWN/KEY_UP event: the key that triggered this event
686 OUI_EXPORT uint uiGetKey();
687 // when handling a keyboard or mouse event: the active modifier keys
688 OUI_EXPORT uint uiGetModifier();
690 // returns the items layout rectangle in absolute coordinates. If
691 // uiGetRect() is called before uiEndLayout(), the values of the returned
692 // rectangle are undefined.
693 OUI_EXPORT UIrect uiGetRect(int item);
695 // returns 1 if an items absolute rectangle contains a given coordinate
697 OUI_EXPORT int uiContains(int item, int x, int y);
699 // return the width of the item as set by uiSetSize()
700 OUI_EXPORT int uiGetWidth(int item);
701 // return the height of the item as set by uiSetSize()
702 OUI_EXPORT int uiGetHeight(int item);
704 // return the anchoring behavior as set by uiSetLayout()
705 OUI_EXPORT uint uiGetLayout(int item);
706 // return the box model as set by uiSetBox()
707 OUI_EXPORT uint uiGetBox(int item);
709 // return the left margin of the item as set with uiSetMargins()
710 OUI_EXPORT short uiGetMarginLeft(int item);
711 // return the top margin of the item as set with uiSetMargins()
712 OUI_EXPORT short uiGetMarginTop(int item);
713 // return the right margin of the item as set with uiSetMargins()
714 OUI_EXPORT short uiGetMarginRight(int item);
715 // return the bottom margin of the item as set with uiSetMargins()
716 OUI_EXPORT short uiGetMarginDown(int item);
718 // when uiBeginLayout() is called, the most recently declared items are retained.
719 // when uiEndLayout() completes, it matches the old item hierarchy to the new one
720 // and attempts to map old items to new items as well as possible.
721 // when passed an item Id from the previous frame, uiRecoverItem() returns the
722 // items new assumed Id, or -1 if the item could not be mapped.
723 // it is valid to pass -1 as item.
724 OUI_EXPORT int uiRecoverItem(int olditem);
726 // in cases where it is important to recover old state over changes in
727 // the view, and the built-in remapping fails, the UI declaration can manually
728 // remap old items to new IDs in cases where e.g. the previous item ID has been
729 // temporarily saved; uiRemapItem() would then be called after creating the
730 // new item using uiItem().
731 OUI_EXPORT void uiRemapItem(int olditem, int newitem);
733 // returns the number if items that have been allocated in the last frame
734 OUI_EXPORT int uiGetLastItemCount();
738 // ////////////////////////////////////////////////////////////////////////// //
740 enum UI_MAX_KIND
= 16;
742 enum UI_ANY_BUTTON0_INPUT
= (UI_BUTTON0_DOWN|UI_BUTTON0_UP|UI_BUTTON0_HOT_UP|UI_BUTTON0_CAPTURE
);
743 enum UI_ANY_BUTTON2_INPUT
= (UI_BUTTON2_DOWN
);
744 enum UI_ANY_MOUSE_INPUT
= (UI_ANY_BUTTON0_INPUT|UI_ANY_BUTTON2_INPUT
);
745 enum UI_ANY_KEY_INPUT
= (UI_KEY_DOWN|UI_KEY_UP|UI_CHAR
);
746 enum UI_ANY_INPUT
= (UI_ANY_MOUSE_INPUT|UI_ANY_KEY_INPUT
);
752 UI_ITEM_BOX_MODEL_MASK
= 0x000007,
754 UI_ITEM_BOX_MASK
= 0x00001F,
756 UI_ITEM_LAYOUT_MASK
= 0x0003E0,
758 UI_ITEM_EVENT_MASK
= 0x07FC00,
759 // item is frozen (bit 19)
760 UI_ITEM_FROZEN
= 0x080000,
761 // item handle is pointer to data (bit 20)
762 UI_ITEM_DATA
= 0x100000,
763 // item has been inserted (bit 21)
764 UI_ITEM_INSERTED
= 0x200000,
765 // horizontal size has been explicitly set (bit 22)
766 UI_ITEM_HFIXED
= 0x400000,
767 // vertical size has been explicitly set (bit 23)
768 UI_ITEM_VFIXED
= 0x800000,
770 UI_ITEM_FIXED_MASK
= 0xC00000,
772 // which flag bits will be compared
773 UI_ITEM_COMPARE_MASK
= UI_ITEM_BOX_MODEL_MASK|
(UI_ITEM_LAYOUT_MASK
&~UI_BREAK
)|UI_ITEM_EVENT_MASK|UI_USERMASK
,
780 // about 27 bits worth of flags
783 // index of first kid
784 // if old item: index of equivalent new item
786 // index of next sibling with same parent
789 // margin offsets, interpretation depends on flags
790 // after layouting, the first two components are absolute coordinates
805 UI_STAGE_POST_LAYOUT
,
809 struct UIhandleEntry
{
814 struct UIinputEvent
{
822 uint buffer_capacity
;
827 // button state in this frame
829 // button state in the previous frame
832 // where the cursor was at the beginning of the active state
834 // where the cursor was last frame
836 // where the cursor is currently
838 // accumulated scroll wheel offsets
850 uint active_modifier
;
851 uint active_button_modifier
;
853 int last_click_timestamp
;
866 UIinputEvent
[UI_MAX_INPUT_EVENTS
] events
;
869 int ui_max() (int a
, int b
) { pragma(inline
, true); return (a
> b ? a
: b
); }
870 int ui_min() (int a
, int b
) { pragma(inline
, true); return (a
< b ? a
: b
); }
871 float ui_maxf() (float a
, float b
) { pragma(inline
, true); return (a
> b ? a
: b
); }
872 float ui_minf() (float a
, float b
) { pragma(inline
, true); return (a
< b ? a
: b
); }
874 __gshared UIcontext
* ui_context
= null;
876 public void uiClear () {
877 ui_context
.last_count
= ui_context
.count
;
878 ui_context
.count
= 0;
879 if (ui_context
.datasize
> 0) memset(ui_context
.data
, 0, ui_context
.datasize
);
880 ui_context
.datasize
= 0;
881 ui_context
.hot_item
= -1;
883 UIitem
* items
= ui_context
.items
;
884 ui_context
.items
= ui_context
.last_items
;
885 ui_context
.last_items
= items
;
886 for (int i
= 0; i
< ui_context
.last_count
; ++i
) ui_context
.item_map
[i
] = -1;
889 public UIcontextP
uiCreateContext(bool useGC
=true) (uint item_capacity
, uint buffer_capacity
=0) {
890 assert(item_capacity
);
891 UIcontext
* ctx
= cast(UIcontext
*)malloc(UIcontext
.sizeof
);
892 memset(ctx
, 0, UIcontext
.sizeof
);
893 ctx
.item_capacity
= item_capacity
;
894 ctx
.buffer_capacity
= buffer_capacity
;
895 ctx
.stage
= UI_STAGE_PROCESS
;
896 ctx
.items
= cast(UIitem
*)malloc(UIitem
.sizeof
*item_capacity
);
897 ctx
.last_items
= cast(UIitem
*)malloc(UIitem
.sizeof
*item_capacity
);
898 ctx
.item_map
= cast(int*)malloc(int.sizeof
*item_capacity
);
899 if (buffer_capacity
) {
900 ctx
.data
= cast(ubyte*)malloc(buffer_capacity
);
902 if (ctx
.data
!is null) {
903 import core
.memory
: GC
;
905 GC
.addRange(ctx
.data
, buffer_capacity
);
911 UIcontext
* oldctx
= ui_context
;
915 uiMakeCurrent(oldctx
);
919 public void uiMakeCurrent (UIcontext
* ctx
) {
923 public void uiDestroyContext (UIcontext
* ctx
) {
924 if (ctx
is null) return;
925 if (ui_context
== ctx
) uiMakeCurrent(null);
926 if (ctx
.gced
&& ctx
.data
!is null) {
927 import core
.memory
: GC
;
928 GC
.removeRange(ctx
.data
);
931 free(ctx
.last_items
);
937 public UIcontext
* uiGetContext () {
941 public void uiSetButton (ubyte button
, uint mod
, bool enabled
) {
943 ulong mask
= 1UL<<button
;
945 ui_context
.buttons
= (enabled ?
(ui_context
.buttons|mask
) : (ui_context
.buttons
&~mask
));
946 ui_context
.active_button_modifier
= mod
;
949 void uiAddInputEvent (UIinputEvent event
) {
951 if (ui_context
.eventcount
== UI_MAX_INPUT_EVENTS
) return;
952 ui_context
.events
[ui_context
.eventcount
++] = event
;
955 void uiClearInputEvents () {
957 ui_context
.eventcount
= 0;
958 ui_context
.scroll
.x
= 0;
959 ui_context
.scroll
.y
= 0;
962 public void uiSetKey (uint key
, uint mod
, bool enabled
) {
964 auto event
= UIinputEvent(key
, mod
, (enabled ? UI_KEY_DOWN
: UI_KEY_UP
));
965 uiAddInputEvent(event
);
968 public void uiSetChar (uint value
) {
970 auto event
= UIinputEvent(value
, 0, UI_CHAR
);
971 uiAddInputEvent(event
);
974 public void uiSetScroll (int x
, int y
) {
976 ui_context
.scroll
.x
+= x
;
977 ui_context
.scroll
.y
+= y
;
980 public UIvec2
uiGetScroll () {
982 return ui_context
.scroll
;
985 public bool uiGetLastButton (ubyte button
) {
987 return (ui_context
.last_buttons
&(1UL<<button
) ?
true : false);
990 public bool uiGetButton (ubyte button
) {
992 return (ui_context
.buttons
&(1UL<<button
) ?
true : false);
995 public bool uiButtonPressed (ubyte button
) {
997 return !uiGetLastButton(button
) && uiGetButton(button
);
1000 public bool uiButtonReleased (ubyte button
) {
1002 return uiGetLastButton(button
) && !uiGetButton(button
);
1005 public void uiSetCursor (int x
, int y
) {
1007 ui_context
.cursor
.x
= x
;
1008 ui_context
.cursor
.y
= y
;
1011 public UIvec2
uiGetCursor () {
1013 return ui_context
.cursor
;
1016 public UIvec2
uiGetCursorStart () {
1018 return ui_context
.start_cursor
;
1021 public UIvec2
uiGetCursorDelta () {
1023 return UIvec2(ui_context
.cursor
.x
-ui_context
.last_cursor
.x
, ui_context
.cursor
.y
-ui_context
.last_cursor
.y
);
1026 public UIvec2
uiGetCursorStartDelta () {
1028 return UIvec2(ui_context
.cursor
.x
-ui_context
.start_cursor
.x
, ui_context
.cursor
.y
-ui_context
.start_cursor
.y
);
1031 public uint uiGetKey () {
1033 return ui_context
.active_key
;
1036 public uint uiGetModifier () {
1038 return ui_context
.active_modifier
;
1041 public int uiGetItemCount () {
1043 return ui_context
.count
;
1046 public int uiGetLastItemCount () {
1048 return ui_context
.last_count
;
1051 public uint uiGetAllocSize () {
1053 return ui_context
.datasize
;
1056 public UIitem
* uiItemPtr (int item
) {
1057 assert(ui_context
&& (item
>= 0) && (item
< ui_context
.count
));
1058 return ui_context
.items
+item
;
1061 public UIitem
* uiLastItemPtr (int item
) {
1062 assert(ui_context
&& (item
>= 0) && (item
< ui_context
.last_count
));
1063 return ui_context
.last_items
+item
;
1066 public int uiGetHotItem () {
1068 return ui_context
.hot_item
;
1071 public void uiFocus (int item
) {
1072 assert(ui_context
&& (item
>= -1) && (item
< ui_context
.count
));
1073 assert(ui_context
.stage
!= UI_STAGE_LAYOUT
);
1074 ui_context
.focus_item
= item
;
1077 void uiValidateStateItems () {
1079 ui_context
.last_hot_item
= uiRecoverItem(ui_context
.last_hot_item
);
1080 ui_context
.active_item
= uiRecoverItem(ui_context
.active_item
);
1081 ui_context
.focus_item
= uiRecoverItem(ui_context
.focus_item
);
1082 ui_context
.last_click_item
= uiRecoverItem(ui_context
.last_click_item
);
1085 public int uiGetFocusedItem () {
1087 return ui_context
.focus_item
;
1091 public void uiBeginLayout () {
1093 assert(ui_context
.stage
== UI_STAGE_PROCESS
); // must run uiEndLayout(), uiProcess() first
1095 ui_context
.stage
= UI_STAGE_LAYOUT
;
1098 public void uiClearState () {
1100 ui_context
.last_hot_item
= -1;
1101 ui_context
.active_item
= -1;
1102 ui_context
.focus_item
= -1;
1103 ui_context
.last_click_item
= -1;
1106 public int uiItem () {
1108 assert(ui_context
.stage
== UI_STAGE_LAYOUT
); // must run between uiBeginLayout() and uiEndLayout()
1109 assert(ui_context
.count
< cast(int)ui_context
.item_capacity
);
1110 int idx
= ui_context
.count
++;
1111 UIitem
* item
= uiItemPtr(idx
);
1112 memset(item
, 0, UIitem
.sizeof
);
1118 public void uiNotifyItem (int item
, UIevent event
) {
1120 if (!ui_context
.handler
) return;
1121 assert((event
&UI_ITEM_EVENT_MASK
) == event
);
1122 UIitem
* pitem
= uiItemPtr(item
);
1123 if (pitem
.flags
&event
) ui_context
.handler(item
, event
);
1126 public int uiLastChild (int item
) {
1127 item
= uiFirstChild(item
);
1128 if (item
< 0) return -1;
1130 int nextitem
= uiNextSibling(item
);
1131 if (nextitem
< 0) return item
;
1136 public int uiAppend (int item
, int sibling
) {
1137 assert(sibling
> 0);
1138 UIitem
* pitem
= uiItemPtr(item
);
1139 UIitem
* psibling
= uiItemPtr(sibling
);
1140 assert(!(psibling
.flags
&UI_ITEM_INSERTED
));
1141 psibling
.nextitem
= pitem
.nextitem
;
1142 psibling
.flags |
= UI_ITEM_INSERTED
;
1143 pitem
.nextitem
= sibling
;
1147 public int uiInsert (int item
, int child
) {
1149 UIitem
* pparent
= uiItemPtr(item
);
1150 UIitem
* pchild
= uiItemPtr(child
);
1151 assert(!(pchild
.flags
&UI_ITEM_INSERTED
));
1152 if (pparent
.firstkid
< 0) {
1153 pparent
.firstkid
= child
;
1154 pchild
.flags |
= UI_ITEM_INSERTED
;
1156 uiAppend(uiLastChild(item
), child
);
1161 public int uiInsertFront (int item
, int child
) {
1162 return uiInsert(item
, child
);
1165 public int uiInsertBack (int item
, int child
) {
1167 UIitem
* pparent
= uiItemPtr(item
);
1168 UIitem
* pchild
= uiItemPtr(child
);
1169 assert(!(pchild
.flags
&UI_ITEM_INSERTED
));
1170 pchild
.nextitem
= pparent
.firstkid
;
1171 pparent
.firstkid
= child
;
1172 pchild
.flags |
= UI_ITEM_INSERTED
;
1176 public void uiSetFrozen (int item
, int enable
) {
1177 UIitem
* pitem
= uiItemPtr(item
);
1179 pitem
.flags |
= UI_ITEM_FROZEN
;
1181 pitem
.flags
&= ~UI_ITEM_FROZEN
;
1184 public void uiSetSize (int item
, int w
, int h
) {
1185 UIitem
* pitem
= uiItemPtr(item
);
1186 pitem
.size
[0] = cast(short)w
;
1187 pitem
.size
[1] = cast(short)h
;
1189 pitem
.flags
&= ~UI_ITEM_HFIXED
;
1191 pitem
.flags |
= UI_ITEM_HFIXED
;
1193 pitem
.flags
&= ~UI_ITEM_VFIXED
;
1195 pitem
.flags |
= UI_ITEM_VFIXED
;
1198 public int uiGetWidth (int item
) {
1199 return uiItemPtr(item
).size
[0];
1202 public int uiGetHeight (int item
) {
1203 return uiItemPtr(item
).size
[1];
1206 public void uiSetLayout (int item
, uint flags
) {
1207 UIitem
* pitem
= uiItemPtr(item
);
1208 assert((flags
&UI_ITEM_LAYOUT_MASK
) == cast(uint)flags
);
1209 pitem
.flags
&= ~UI_ITEM_LAYOUT_MASK
;
1210 pitem
.flags |
= flags
&UI_ITEM_LAYOUT_MASK
;
1213 public uint uiGetLayout (int item
) {
1214 return uiItemPtr(item
).flags
&UI_ITEM_LAYOUT_MASK
;
1217 public void uiSetBox (int item
, uint flags
) {
1218 UIitem
* pitem
= uiItemPtr(item
);
1219 assert((flags
&UI_ITEM_BOX_MASK
) == cast(uint)flags
);
1220 pitem
.flags
&= ~UI_ITEM_BOX_MASK
;
1221 pitem
.flags |
= flags
&UI_ITEM_BOX_MASK
;
1224 public uint uiGetBox (int item
) {
1225 return uiItemPtr(item
).flags
&UI_ITEM_BOX_MASK
;
1228 public void uiSetMargins (int item
, short l
, short t
, short r
, short b
) {
1229 UIitem
* pitem
= uiItemPtr(item
);
1230 pitem
.margins
[0] = l
;
1231 pitem
.margins
[1] = t
;
1232 pitem
.margins
[2] = r
;
1233 pitem
.margins
[3] = b
;
1236 public short uiGetMarginLeft (int item
) { return uiItemPtr(item
).margins
[0]; }
1237 public short uiGetMarginTop (int item
) { return uiItemPtr(item
).margins
[1]; }
1238 public short uiGetMarginRight (int item
) { return uiItemPtr(item
).margins
[2]; }
1239 public short uiGetMarginDown (int item
) { return uiItemPtr(item
).margins
[3]; }
1241 // compute bounding box of all items super-imposed
1242 public void uiComputeImposedSize (UIitem
* pitem
, int dim
) {
1244 // largest size is required size
1245 short need_size
= 0;
1246 int kid
= pitem
.firstkid
;
1248 UIitem
* pkid
= uiItemPtr(kid
);
1249 // width = start margin + calculated width + end margin
1250 int kidsize
= pkid
.margins
[dim
]+pkid
.size
[dim
]+pkid
.margins
[wdim
];
1251 need_size
= cast(short)ui_max(need_size
, kidsize
);
1252 kid
= uiNextSibling(kid
);
1254 pitem
.size
[dim
] = need_size
;
1257 // compute bounding box of all items stacked
1258 public void uiComputeStackedSize (UIitem
* pitem
, int dim
) {
1260 short need_size
= 0;
1261 int kid
= pitem
.firstkid
;
1263 UIitem
* pkid
= uiItemPtr(kid
);
1264 // width += start margin + calculated width + end margin
1265 need_size
+= pkid
.margins
[dim
]+pkid
.size
[dim
]+pkid
.margins
[wdim
];
1266 kid
= uiNextSibling(kid
);
1268 pitem
.size
[dim
] = need_size
;
1271 // compute bounding box of all items stacked, repeating when breaking
1272 public void uiComputeWrappedStackedSize (UIitem
* pitem
, int dim
) {
1274 short need_size
= 0;
1275 short need_size2
= 0;
1276 int kid
= pitem
.firstkid
;
1278 UIitem
* pkid
= uiItemPtr(kid
);
1279 // if next position moved back, we assume a new line
1280 if (pkid
.flags
&UI_BREAK
) {
1281 need_size2
= cast(short)ui_max(need_size2
, need_size
);
1285 // width = start margin + calculated width + end margin
1286 need_size
+= pkid
.margins
[dim
]+pkid
.size
[dim
]+pkid
.margins
[wdim
];
1287 kid
= uiNextSibling(kid
);
1289 pitem
.size
[dim
] = cast(short)ui_max(need_size2
, need_size
);
1292 // compute bounding box of all items stacked + wrapped
1293 public void uiComputeWrappedSize (UIitem
* pitem
, int dim
) {
1295 short need_size
= 0;
1296 short need_size2
= 0;
1297 int kid
= pitem
.firstkid
;
1299 UIitem
* pkid
= uiItemPtr(kid
);
1300 // if next position moved back, we assume a new line
1301 if (pkid
.flags
&UI_BREAK
) {
1302 need_size2
+= need_size
;
1306 // width = start margin + calculated width + end margin
1307 int kidsize
= pkid
.margins
[dim
]+pkid
.size
[dim
]+pkid
.margins
[wdim
];
1308 need_size
= cast(short)ui_max(need_size
, kidsize
);
1309 kid
= uiNextSibling(kid
);
1311 pitem
.size
[dim
] = cast(short)(need_size2
+need_size
);
1314 void uiComputeSize (int item
, int dim
) {
1315 UIitem
* pitem
= uiItemPtr(item
);
1317 // children expand the size
1318 int kid
= pitem
.firstkid
;
1320 uiComputeSize(kid
, dim
);
1321 kid
= uiNextSibling(kid
);
1324 if (pitem
.size
[dim
]) return;
1325 switch(pitem
.flags
&UI_ITEM_BOX_MODEL_MASK
) {
1326 case UI_COLUMN|UI_WRAP
:
1328 if (dim
) // direction
1329 uiComputeStackedSize(pitem
, 1);
1331 uiComputeImposedSize(pitem
, 0);
1333 case UI_ROW|UI_WRAP
:
1335 if (!dim
) // direction
1336 uiComputeWrappedStackedSize(pitem
, 0);
1338 uiComputeWrappedSize(pitem
, 1);
1343 if ((pitem
.flags
&1) == cast(uint)dim
) // direction
1344 uiComputeStackedSize(pitem
, dim
);
1346 uiComputeImposedSize(pitem
, dim
);
1350 uiComputeImposedSize(pitem
, dim
);
1355 // stack all items according to their alignment
1356 public void uiArrangeStacked (UIitem
* pitem
, int dim
, bool wrap
) {
1359 short space
= pitem
.size
[dim
];
1360 float max_x2
= cast(float)pitem
.margins
[dim
]+cast(float)space
;
1362 int start_kid
= pitem
.firstkid
;
1363 while (start_kid
>= 0) {
1366 int count
= 0; // count of fillers
1367 int squeezed_count
= 0; // count of squeezable elements
1369 bool hardbreak
= false;
1370 // first pass: count items that need to be expanded,
1371 // and the space that is used
1372 int kid
= start_kid
;
1375 UIitem
* pkid
= uiItemPtr(kid
);
1376 int flags
= (pkid
.flags
&UI_ITEM_LAYOUT_MASK
)>>dim
;
1377 int fflags
= (pkid
.flags
&UI_ITEM_FIXED_MASK
)>>dim
;
1378 short extend
= used
;
1379 if ((flags
&UI_HFILL
) == UI_HFILL
) {
1382 extend
+= pkid
.margins
[dim
]+pkid
.margins
[wdim
];
1384 if ((fflags
&UI_ITEM_HFIXED
) != UI_ITEM_HFIXED
) ++squeezed_count
;
1385 extend
+= pkid
.margins
[dim
]+pkid
.size
[dim
]+pkid
.margins
[wdim
];
1387 // wrap on end of line or manual flag
1388 if (wrap
&& total
&& (extend
> space ||
(pkid
.flags
&UI_BREAK
))) {
1390 hardbreak
= ((pkid
.flags
&UI_BREAK
) == UI_BREAK
);
1391 // add marker for subsequent queries
1392 pkid
.flags |
= UI_BREAK
;
1396 kid
= uiNextSibling(kid
);
1401 int extra_space
= space
-used
;
1402 float filler
= 0.0f;
1403 float spacer
= 0.0f;
1404 float extra_margin
= 0.0f;
1407 if (extra_space
> 0) {
1409 filler
= cast(float)extra_space
/cast(float)count
;
1411 switch(pitem
.flags
&UI_JUSTIFY
) {
1413 extra_margin
= extra_space
/2.0f;
1416 // justify when not wrapping or not in last line, or not manually breaking
1417 if (!wrap ||
(end_kid
!= -1 && !hardbreak
)) spacer
= cast(float)extra_space
/cast(float)(total
-1);
1422 extra_margin
= extra_space
;
1426 } else if (!wrap
&& (extra_space
< 0)) {
1427 eater
= cast(float)extra_space
/cast(float)squeezed_count
;
1430 // distribute width among items
1431 float x
= cast(float)pitem
.margins
[dim
];
1433 // second pass: distribute and rescale
1435 while (kid
!= end_kid
) {
1437 UIitem
* pkid
= uiItemPtr(kid
);
1438 int flags
= (pkid
.flags
&UI_ITEM_LAYOUT_MASK
)>>dim
;
1439 int fflags
= (pkid
.flags
&UI_ITEM_FIXED_MASK
)>>dim
;
1441 x
+= cast(float)pkid
.margins
[dim
]+extra_margin
;
1442 if ((flags
&UI_HFILL
) == UI_HFILL
) {
1445 } else if ((fflags
&UI_ITEM_HFIXED
) == UI_ITEM_HFIXED
) {
1446 x1
= x
+cast(float)pkid
.size
[dim
];
1449 x1
= x
+ui_maxf(0.0f, cast(float)pkid
.size
[dim
]+eater
);
1453 ix1
= cast(short)ui_minf(max_x2
-cast(float)pkid
.margins
[wdim
], x1
);
1455 ix1
= cast(short)x1
;
1456 pkid
.margins
[dim
] = ix0
;
1457 pkid
.size
[dim
] = cast(short)(ix1
-ix0
);
1458 x
= x1
+cast(float)pkid
.margins
[wdim
];
1460 kid
= uiNextSibling(kid
);
1461 extra_margin
= spacer
;
1464 start_kid
= end_kid
;
1468 // superimpose all items according to their alignment
1469 public void uiArrangeImposedRange (UIitem
* pitem
, int dim
, int start_kid
, int end_kid
, short offset
, short space
) {
1471 int kid
= start_kid
;
1472 while (kid
!= end_kid
) {
1473 UIitem
* pkid
= uiItemPtr(kid
);
1474 int flags
= (pkid
.flags
&UI_ITEM_LAYOUT_MASK
)>>dim
;
1475 switch (flags
&UI_HFILL
) {
1478 pkid
.margins
[dim
] += (space
-pkid
.size
[dim
])/2-pkid
.margins
[wdim
];
1481 pkid
.margins
[dim
] = cast(short)(space
-pkid
.size
[dim
]-pkid
.margins
[wdim
]);
1484 pkid
.size
[dim
] = cast(short)ui_max(0,space
-pkid
.margins
[dim
]-pkid
.margins
[wdim
]);
1487 pkid
.margins
[dim
] += offset
;
1488 kid
= uiNextSibling(kid
);
1492 public void uiArrangeImposed (UIitem
* pitem
, int dim
) {
1493 uiArrangeImposedRange(pitem
, dim
, pitem
.firstkid
, -1, pitem
.margins
[dim
], pitem
.size
[dim
]);
1496 // superimpose all items according to their alignment,
1497 // squeeze items that expand the available space
1498 public void uiArrangeImposedSqueezedRange (UIitem
* pitem
, int dim
, int start_kid
, int end_kid
, short offset
, short space
) {
1500 int kid
= start_kid
;
1501 while (kid
!= end_kid
) {
1502 UIitem
* pkid
= uiItemPtr(kid
);
1503 int flags
= (pkid
.flags
&UI_ITEM_LAYOUT_MASK
)>>dim
;
1504 short min_size
= cast(short)ui_max(0,space
-pkid
.margins
[dim
]-pkid
.margins
[wdim
]);
1505 switch (flags
&UI_HFILL
) {
1507 pkid
.size
[dim
] = cast(short)ui_min(pkid
.size
[dim
], min_size
);
1510 pkid
.size
[dim
] = cast(short)ui_min(pkid
.size
[dim
], min_size
);
1511 pkid
.margins
[dim
] += (space
-pkid
.size
[dim
])/2-pkid
.margins
[wdim
];
1514 pkid
.size
[dim
] = cast(short)ui_min(pkid
.size
[dim
], min_size
);
1515 pkid
.margins
[dim
] = cast(short)(space
-pkid
.size
[dim
]-pkid
.margins
[wdim
]);
1518 pkid
.size
[dim
] = min_size
;
1521 pkid
.margins
[dim
] += offset
;
1522 kid
= uiNextSibling(kid
);
1526 public void uiArrangeImposedSqueezed (UIitem
* pitem
, int dim
) {
1527 uiArrangeImposedSqueezedRange(pitem
, dim
, pitem
.firstkid
, -1, pitem
.margins
[dim
], pitem
.size
[dim
]);
1530 // superimpose all items according to their alignment
1531 public short uiArrangeWrappedImposedSqueezed (UIitem
* pitem
, int dim
) {
1533 short offset
= pitem
.margins
[dim
];
1534 short need_size
= 0;
1535 int kid
= pitem
.firstkid
;
1536 int start_kid
= kid
;
1538 UIitem
* pkid
= uiItemPtr(kid
);
1539 if (pkid
.flags
&UI_BREAK
) {
1540 uiArrangeImposedSqueezedRange(pitem
, dim
, start_kid
, kid
, offset
, need_size
);
1541 offset
+= need_size
;
1546 // width = start margin + calculated width + end margin
1547 int kidsize
= pkid
.margins
[dim
]+pkid
.size
[dim
]+pkid
.margins
[wdim
];
1548 need_size
= cast(short)ui_max(need_size
, kidsize
);
1549 kid
= uiNextSibling(kid
);
1551 uiArrangeImposedSqueezedRange(pitem
, dim
, start_kid
, -1, offset
, need_size
);
1552 offset
+= need_size
;
1556 void uiArrange (int item
, int dim
) {
1557 UIitem
* pitem
= uiItemPtr(item
);
1558 switch (pitem
.flags
&UI_ITEM_BOX_MODEL_MASK
) {
1559 case UI_COLUMN|UI_WRAP
:
1560 // flex model, wrapping
1561 if (dim
) { // direction
1562 uiArrangeStacked(pitem
, 1, true);
1563 // this retroactive resize will not effect parent widths
1564 short offset
= uiArrangeWrappedImposedSqueezed(pitem
, 0);
1565 pitem
.size
[0] = cast(short)(offset
-pitem
.margins
[0]);
1568 case UI_ROW|UI_WRAP
:
1569 // flex model, wrapping
1570 if (!dim
) { // direction
1571 uiArrangeStacked(pitem
, 0, true);
1573 uiArrangeWrappedImposedSqueezed(pitem
, 1);
1579 if ((pitem
.flags
&1) == cast(uint)dim
) // direction
1580 uiArrangeStacked(pitem
, dim
, false);
1582 uiArrangeImposedSqueezed(pitem
, dim
);
1586 uiArrangeImposed(pitem
, dim
);
1589 int kid
= uiFirstChild(item
);
1591 uiArrange(kid
, dim
);
1592 kid
= uiNextSibling(kid
);
1596 public bool uiCompareItems (UIitem
* item1
, UIitem
* item2
) {
1597 return ((item1
.flags
&UI_ITEM_COMPARE_MASK
) == (item2
.flags
&UI_ITEM_COMPARE_MASK
));
1600 bool uiMapItems (int item1
, int item2
) {
1601 UIitem
* pitem1
= uiLastItemPtr(item1
);
1602 if (item2
== -1) return false;
1604 UIitem
* pitem2
= uiItemPtr(item2
);
1605 if (!uiCompareItems(pitem1
, pitem2
)) return false;
1609 int kid1
= pitem1
.firstkid
;
1610 int kid2
= pitem2
.firstkid
;
1611 while (kid1
!= -1) {
1612 UIitem
* pkid1
= uiLastItemPtr(kid1
);
1614 if (!uiMapItems(kid1
, kid2
)) {
1618 kid1
= pkid1
.nextitem
;
1619 if (kid2
!= -1) kid2
= uiItemPtr(kid2
).nextitem
;
1622 if (count
&& failed
== 1) return false;
1624 ui_context
.item_map
[item1
] = item2
;
1628 public int uiRecoverItem (int olditem
) {
1630 assert(olditem
>= -1 && olditem
< ui_context
.last_count
);
1631 if (olditem
== -1) return -1;
1632 return ui_context
.item_map
[olditem
];
1635 public void uiRemapItem (int olditem
, int newitem
) {
1637 assert(olditem
>= 0 && olditem
< ui_context
.last_count
);
1638 assert(newitem
>= -1 && newitem
< ui_context
.count
);
1639 ui_context
.item_map
[olditem
] = newitem
;
1642 public void uiEndLayout () {
1644 assert(ui_context
.stage
== UI_STAGE_LAYOUT
); // must run uiBeginLayout() first
1646 if (ui_context
.count
) {
1652 if (ui_context
.last_count
) {
1653 // map old item id to new item id
1658 uiValidateStateItems();
1659 if (ui_context
.count
) {
1660 // drawing routines may require this to be set already
1664 ui_context
.stage
= UI_STAGE_POST_LAYOUT
;
1667 public UIrect
uiGetRect (int item
) {
1668 UIitem
* pitem
= uiItemPtr(item
);
1669 return UIrect(pitem
.margins
[0], pitem
.margins
[1], pitem
.size
[0], pitem
.size
[1]);
1672 public int uiFirstChild (int item
) {
1673 return uiItemPtr(item
).firstkid
;
1676 public int uiNextSibling (int item
) {
1677 return uiItemPtr(item
).nextitem
;
1680 public void* uiAllocHandle (int item
, uint size
) {
1681 assert(size
> 0 && size
< UI_MAX_DATASIZE
);
1682 UIitem
* pitem
= uiItemPtr(item
);
1683 assert(pitem
.handle
is null);
1684 assert(ui_context
.datasize
+size
<= ui_context
.buffer_capacity
);
1685 pitem
.handle
= ui_context
.data
+ui_context
.datasize
;
1686 pitem
.flags |
= UI_ITEM_DATA
;
1687 ui_context
.datasize
+= size
;
1688 return pitem
.handle
;
1691 public T
* uiAllocHandle(T
) (int item
) if (!is(T
== class)) {
1692 enum size
= T
.sizeof
;
1693 UIitem
* pitem
= uiItemPtr(item
);
1694 assert(pitem
.handle
is null);
1695 assert(ui_context
.datasize
+size
<= ui_context
.buffer_capacity
);
1696 pitem
.handle
= ui_context
.data
+ui_context
.datasize
;
1697 pitem
.flags |
= UI_ITEM_DATA
;
1698 ui_context
.datasize
+= size
;
1699 static if (is(T
== struct)) {
1700 import core
.stdc
.string
: memcpy
;
1701 static immutable T i
= T
.init
;
1702 memcpy(pitem
.handle
, &i
, T
.sizeof
);
1704 return cast(T
*)pitem
.handle
;
1707 public void uiSetHandle (int item
, void* handle
) {
1708 UIitem
* pitem
= uiItemPtr(item
);
1709 assert(pitem
.handle
is null);
1710 pitem
.handle
= handle
;
1713 public void* uiGetHandle (int item
) {
1714 return uiItemPtr(item
).handle
;
1717 public T
* uiGetHandle(T
) (int item
) {
1718 return cast(T
*)uiItemPtr(item
).handle
;
1721 public void uiSetHandler (UIhandler handler
) {
1723 ui_context
.handler
= handler
;
1726 public UIhandler
uiGetHandler () {
1728 return ui_context
.handler
;
1731 public void uiSetEvents (int item
, uint flags
) {
1732 UIitem
* pitem
= uiItemPtr(item
);
1733 pitem
.flags
&= ~UI_ITEM_EVENT_MASK
;
1734 pitem
.flags |
= flags
&UI_ITEM_EVENT_MASK
;
1737 public uint uiGetEvents (int item
) {
1738 return uiItemPtr(item
).flags
&UI_ITEM_EVENT_MASK
;
1741 public void uiSetFlags (int item
, uint flags
) {
1742 UIitem
* pitem
= uiItemPtr(item
);
1743 pitem
.flags
&= ~UI_USERMASK
;
1744 pitem
.flags |
= flags
&UI_USERMASK
;
1747 public uint uiGetFlags (int item
) {
1748 return uiItemPtr(item
).flags
&UI_USERMASK
;
1751 public bool uiContains (int item
, int x
, int y
) {
1752 UIrect rect
= uiGetRect(item
);
1755 return (x
>= 0 && y
>= 0 && x
< rect
.w
&& y
< rect
.h
);
1758 public int uiFindItem (int item
, int x
, int y
, uint flags
, uint mask
) {
1759 UIitem
* pitem
= uiItemPtr(item
);
1760 if (pitem
.flags
&UI_ITEM_FROZEN
) return -1;
1761 if (uiContains(item
, x
, y
)) {
1763 int kid
= uiFirstChild(item
);
1765 int hit
= uiFindItem(kid
, x
, y
, flags
, mask
);
1769 kid
= uiNextSibling(kid
);
1771 if (best_hit
>= 0) return best_hit
;
1772 if ((mask
== UI_ANY
&& (flags
== UI_ANY ||
(pitem
.flags
&flags
) != 0)) ||
(pitem
.flags
&flags
) == mask
) return item
;
1777 public void uiUpdateHotItem () {
1779 if (!ui_context
.count
) return;
1780 ui_context
.hot_item
= uiFindItem(0, ui_context
.cursor
.x
, ui_context
.cursor
.y
, UI_ANY_MOUSE_INPUT
, UI_ANY
);
1781 //{ import core.stdc.stdio : printf; printf("hot for (%d,%d): %d\n", ui_context.cursor.x, ui_context.cursor.y, ui_context.hot_item); }
1784 public int uiGetClicks () {
1785 return ui_context
.clicks
;
1788 public void uiProcess (int timestamp
) {
1791 assert(ui_context
.stage
!= UI_STAGE_LAYOUT
); // must run uiBeginLayout(), uiEndLayout() first
1793 if (ui_context
.stage
== UI_STAGE_PROCESS
) uiUpdateHotItem();
1794 ui_context
.stage
= UI_STAGE_PROCESS
;
1796 if (!ui_context
.count
) {
1797 uiClearInputEvents();
1801 int hot_item
= ui_context
.last_hot_item
;
1802 int active_item
= ui_context
.active_item
;
1803 int focus_item
= ui_context
.focus_item
;
1805 // send all keyboard events
1806 if (focus_item
>= 0) {
1807 for (int i
= 0; i
< ui_context
.eventcount
; ++i
) {
1808 ui_context
.active_key
= ui_context
.events
[i
].key
;
1809 ui_context
.active_modifier
= ui_context
.events
[i
].mod
;
1810 uiNotifyItem(focus_item
, ui_context
.events
[i
].event
);
1813 ui_context
.focus_item
= -1;
1815 if (ui_context
.scroll
.x || ui_context
.scroll
.y
) {
1816 int scroll_item
= uiFindItem(0, ui_context
.cursor
.x
, ui_context
.cursor
.y
, UI_SCROLL
, UI_ANY
);
1817 if (scroll_item
>= 0) uiNotifyItem(scroll_item
, UI_SCROLL
);
1820 uiClearInputEvents();
1822 int hot
= ui_context
.hot_item
;
1823 //{ import core.stdc.stdio : printf; printf("hot: %d\n", hot); }
1825 switch (ui_context
.state
) {
1828 ui_context
.start_cursor
= ui_context
.cursor
;
1829 if (uiGetButton(0)) {
1833 if (active_item
!= focus_item
) {
1835 ui_context
.focus_item
= -1;
1838 if (active_item
>= 0) {
1839 if (timestamp
-ui_context
.last_click_timestamp
> UI_CLICK_THRESHOLD || ui_context
.last_click_item
!= active_item
) ui_context
.clicks
= 0;
1840 ++ui_context
.clicks
;
1841 ui_context
.last_click_timestamp
= timestamp
;
1842 ui_context
.last_click_item
= active_item
;
1843 ui_context
.active_modifier
= ui_context
.active_button_modifier
;
1844 uiNotifyItem(active_item
, UI_BUTTON0_DOWN
);
1846 ui_context
.state
= UI_STATE_CAPTURE
;
1847 } else if (uiGetButton(2) && !uiGetLastButton(2)) {
1849 hot
= uiFindItem(0, ui_context
.cursor
.x
, ui_context
.cursor
.y
, UI_BUTTON2_DOWN
, UI_ANY
);
1851 ui_context
.active_modifier
= ui_context
.active_button_modifier
;
1852 uiNotifyItem(hot
, UI_BUTTON2_DOWN
);
1858 case UI_STATE_CAPTURE
:
1859 if (!uiGetButton(0)) {
1860 if (active_item
>= 0) {
1861 ui_context
.active_modifier
= ui_context
.active_button_modifier
;
1862 uiNotifyItem(active_item
, UI_BUTTON0_UP
);
1863 if (active_item
== hot
) uiNotifyItem(active_item
, UI_BUTTON0_HOT_UP
);
1866 ui_context
.state
= UI_STATE_IDLE
;
1868 if (active_item
>= 0) {
1869 ui_context
.active_modifier
= ui_context
.active_button_modifier
;
1870 uiNotifyItem(active_item
, UI_BUTTON0_CAPTURE
);
1872 hot_item
= (hot
== active_item ? hot
: -1);
1877 ui_context
.last_cursor
= ui_context
.cursor
;
1878 ui_context
.last_hot_item
= hot_item
;
1879 ui_context
.active_item
= active_item
;
1881 ui_context
.last_timestamp
= timestamp
;
1882 ui_context
.last_buttons
= ui_context
.buttons
;
1885 int uiIsActive (int item
) {
1887 return ui_context
.active_item
== item
;
1890 int uiIsHot (int item
) {
1892 return ui_context
.last_hot_item
== item
;
1895 int uiIsFocused (int item
) {
1897 return ui_context
.focus_item
== item
;
1900 public UIitemState
uiGetState (int item
) {
1901 UIitem
* pitem
= uiItemPtr(item
);
1902 if (pitem
.flags
&UI_ITEM_FROZEN
) return UI_FROZEN
;
1903 if (uiIsFocused(item
)) {
1904 if (pitem
.flags
&(UI_KEY_DOWN|UI_CHAR|UI_KEY_UP
)) return UI_ACTIVE
;
1906 if (uiIsActive(item
)) {
1907 if (pitem
.flags
&(UI_BUTTON0_CAPTURE|UI_BUTTON0_UP
)) return UI_ACTIVE
;
1908 if ((pitem
.flags
&UI_BUTTON0_HOT_UP
) && uiIsHot(item
)) return UI_ACTIVE
;
1910 } else if (uiIsHot(item
)) {