egedit: do not save cursor movement in undo -- this is my stupid habit, and it comple...
[iv.d.git] / nanovega / oui / engine.d
blob07f5848c777cf07b99ae3c0fd73f788d03007960
1 /*
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
22 THE SOFTWARE.
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
56 frame.
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
61 buttons, and so on.
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
73 typedef struct Data {
74 int type;
75 UIhandler handler;
76 } Data;
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);
84 void app_main(...) {
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
99 // --------------
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
105 uiBeginLayout();
107 // - UI setup code goes here -
108 app_setup_ui();
110 // layout UI
111 uiEndLayout();
113 // --------------
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 {
129 Data head;
130 string label;
131 bool* checked;
132 } 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);
138 switch (event) {
139 default: break;
140 case UI_BUTTON0_DOWN:
141 // toggle value
142 *data.checked = !(*data.checked);
143 break;
147 // creates a checkbox control for a pointer to a boolean
148 int checkbox (sting label, bool* checked) {
149 // create new ui item
150 int item = uiItem();
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);
163 data.label = label;
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);
170 return item;
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
182 if (head) {
183 // get the widgets absolute rectangle
184 UIrect rect = uiGetRect(item);
185 switch(head.type) {
186 default: break;
187 case APP_WIDGET_LABEL:
188 // ...
189 break;
190 case APP_WIDGET_BUTTON:
191 // ...
192 break;
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;
203 // draw the checkbox
204 app_draw_checkbox(ctx, rect, state, data.label);
205 } break;
209 // iterate through all children and draw
210 int kid = uiFirstChild(item);
211 while (kid != -1) {
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();
223 // assign fixed size
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 // ////////////////////////////////////////////////////////////////////////// //
253 // limits
254 enum {
255 // maximum size in bytes of a single data buffer passed to uiAllocData().
256 UI_MAX_DATASIZE = 4096,
257 // maximum depth of nested containers
258 UI_MAX_DEPTH = 64,
259 // maximum number of buffered input events
260 UI_MAX_INPUT_EVENTS = 64,
261 // consecutive click threshold in ms
262 UI_CLICK_THRESHOLD = 250,
265 alias UIuint = uint;
267 // opaque UI context
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
276 UI_COLD = 0,
277 // the item is inactive, but the cursor is hovering over this item
278 UI_HOT = 1,
279 // the item is toggled, activated, focused (depends on item kind)
280 UI_ACTIVE = 2,
281 // the item is unresponsive
282 UI_FROZEN = 3,
285 // container flags to pass to uiSetBox()
286 alias UIboxFlags = int;
287 enum /*UIboxFlags*/ {
288 // flex-direction (bit 0+1)
290 // left to right
291 UI_ROW = 0x002,
292 // top to bottom
293 UI_COLUMN = 0x003,
295 // model (bit 1)
297 // free layout
298 UI_LAYOUT = 0x000,
299 // flex model
300 UI_FLEX = 0x002,
302 // flex-wrap (bit 2)
304 // single-line
305 UI_NOWRAP = 0x000,
306 // multi-line, wrap left to right
307 UI_WRAP = 0x004,
310 // justify-content (start, end, center, space-between)
311 // at start of row/column
312 UI_START = 0x008,
313 // at center of row/column
314 UI_MIDDLE = 0x000,
315 // at end of row/column
316 UI_END = 0x010,
317 // insert spacing to stretch across whole row/column
318 UI_JUSTIFY = 0x018,
320 // align-items
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
339 UI_LEFT = 0x020,
340 // anchor to top item or top side of parent
341 UI_TOP = 0x040,
342 // anchor to right item or right side of parent
343 UI_RIGHT = 0x080,
344 // anchor to bottom item or bottom side of parent
345 UI_DOWN = 0x100,
346 // anchor to both left and right item or parent borders
347 UI_HFILL = 0x0a0,
348 // anchor to both top and bottom item or parent borders
349 UI_VFILL = 0x140,
350 // center horizontally, with left margin as offset
351 UI_HCENTER = 0x000,
352 // center vertically, with top margin as offset
353 UI_VCENTER = 0x000,
354 // center in both directions, with left/top margin as offset
355 UI_CENTER = 0x000,
356 // anchor to all four directions
357 UI_FILL = 0x1e0,
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()
361 UI_BREAK = 0x200,
364 // event flags
365 alias UIevent = int;
366 enum /*UIevent*/ {
367 // on button 0 down
368 UI_BUTTON0_DOWN = 0x0400,
369 // on button 0 up
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()
386 UI_SCROLL = 0x8000,
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()
392 UI_KEY_UP = 0x20000,
393 // item is focused and has received a character event
394 // the respective character can be queried using uiGetKey()
395 UI_CHAR = 0x40000,
398 enum {
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()
405 UI_ANY = 0xffffffff,
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 {
413 align(1):
414 align(1) union {
415 align(1):
416 int[2] v;
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; }
422 // layout rectangle
423 align(1) struct UIrect {
424 align(1):
425 align(1) union {
426 align(1):
427 int[4] v;
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
445 // handles.
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();
460 // Input Control
461 // -------------
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();
514 // Stages
515 // ------
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
519 // times.
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();
551 // UI Declaration
552 // --------------
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
579 // uiSetEvents.
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
605 // the background.
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
629 // be focused.
630 OUI_EXPORT void uiFocus(int item);
632 // Iteration
633 // ---------
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);
644 // Querying
645 // --------
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
696 // otherwise 0
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 // ////////////////////////////////////////////////////////////////////////// //
739 private:
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);
748 enum : uint {
749 // extra item flags
751 // bit 0-2
752 UI_ITEM_BOX_MODEL_MASK = 0x000007,
753 // bit 0-4
754 UI_ITEM_BOX_MASK = 0x00001F,
755 // bit 5-8
756 UI_ITEM_LAYOUT_MASK = 0x0003E0,
757 // bit 9-18
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,
769 // bit 22-23
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,
776 struct UIitem {
777 // data handle
778 void* handle;
780 // about 27 bits worth of flags
781 uint flags;
783 // index of first kid
784 // if old item: index of equivalent new item
785 int firstkid;
786 // index of next sibling with same parent
787 int nextitem;
789 // margin offsets, interpretation depends on flags
790 // after layouting, the first two components are absolute coordinates
791 short[4] margins;
792 // size
793 short[2] size;
796 alias UIstate = int;
797 enum /*UIstate*/ {
798 UI_STATE_IDLE = 0,
799 UI_STATE_CAPTURE,
802 alias UIstage = int;
803 enum /*UIstage*/ {
804 UI_STAGE_LAYOUT = 0,
805 UI_STAGE_POST_LAYOUT,
806 UI_STAGE_PROCESS,
809 struct UIhandleEntry {
810 uint key;
811 int item;
814 struct UIinputEvent {
815 uint key;
816 uint mod;
817 UIevent event;
820 struct UIcontext {
821 uint item_capacity;
822 uint buffer_capacity;
824 // handler
825 UIhandler handler;
827 // button state in this frame
828 ulong buttons;
829 // button state in the previous frame
830 ulong last_buttons;
832 // where the cursor was at the beginning of the active state
833 UIvec2 start_cursor;
834 // where the cursor was last frame
835 UIvec2 last_cursor;
836 // where the cursor is currently
837 UIvec2 cursor;
838 // accumulated scroll wheel offsets
839 UIvec2 scroll;
841 int active_item;
842 int focus_item;
843 int last_hot_item;
844 int last_click_item;
845 int hot_item;
847 UIstate state;
848 UIstage stage;
849 uint active_key;
850 uint active_modifier;
851 uint active_button_modifier;
852 int last_timestamp;
853 int last_click_timestamp;
854 int clicks;
856 int count;
857 int last_count;
858 int eventcount;
859 uint datasize;
860 bool gced;
862 UIitem* items;
863 ubyte* data;
864 UIitem* last_items;
865 int* item_map;
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;
882 // swap buffers
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);
901 static if (useGC) {
902 if (ctx.data !is null) {
903 import core.memory : GC;
904 ctx.gced = true;
905 GC.addRange(ctx.data, buffer_capacity);
907 } else {
908 ctx.gced = false;
911 UIcontext* oldctx = ui_context;
912 uiMakeCurrent(ctx);
913 uiClear();
914 uiClearState();
915 uiMakeCurrent(oldctx);
916 return ctx;
919 public void uiMakeCurrent (UIcontext* ctx) {
920 ui_context = 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);
930 free(ctx.items);
931 free(ctx.last_items);
932 free(ctx.item_map);
933 free(ctx.data);
934 free(ctx);
937 public UIcontext* uiGetContext () {
938 return ui_context;
941 public void uiSetButton (ubyte button, uint mod, bool enabled) {
942 assert(ui_context);
943 ulong mask = 1UL<<button;
944 // set new bit
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) {
950 assert(ui_context);
951 if (ui_context.eventcount == UI_MAX_INPUT_EVENTS) return;
952 ui_context.events[ui_context.eventcount++] = event;
955 void uiClearInputEvents () {
956 assert(ui_context);
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) {
963 assert(ui_context);
964 auto event = UIinputEvent(key, mod, (enabled ? UI_KEY_DOWN : UI_KEY_UP));
965 uiAddInputEvent(event);
968 public void uiSetChar (uint value) {
969 assert(ui_context);
970 auto event = UIinputEvent(value, 0, UI_CHAR);
971 uiAddInputEvent(event);
974 public void uiSetScroll (int x, int y) {
975 assert(ui_context);
976 ui_context.scroll.x += x;
977 ui_context.scroll.y += y;
980 public UIvec2 uiGetScroll () {
981 assert(ui_context);
982 return ui_context.scroll;
985 public bool uiGetLastButton (ubyte button) {
986 assert(ui_context);
987 return (ui_context.last_buttons&(1UL<<button) ? true : false);
990 public bool uiGetButton (ubyte button) {
991 assert(ui_context);
992 return (ui_context.buttons&(1UL<<button) ? true : false);
995 public bool uiButtonPressed (ubyte button) {
996 assert(ui_context);
997 return !uiGetLastButton(button) && uiGetButton(button);
1000 public bool uiButtonReleased (ubyte button) {
1001 assert(ui_context);
1002 return uiGetLastButton(button) && !uiGetButton(button);
1005 public void uiSetCursor (int x, int y) {
1006 assert(ui_context);
1007 ui_context.cursor.x = x;
1008 ui_context.cursor.y = y;
1011 public UIvec2 uiGetCursor () {
1012 assert(ui_context);
1013 return ui_context.cursor;
1016 public UIvec2 uiGetCursorStart () {
1017 assert(ui_context);
1018 return ui_context.start_cursor;
1021 public UIvec2 uiGetCursorDelta () {
1022 assert(ui_context);
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 () {
1027 assert(ui_context);
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 () {
1032 assert(ui_context);
1033 return ui_context.active_key;
1036 public uint uiGetModifier () {
1037 assert(ui_context);
1038 return ui_context.active_modifier;
1041 public int uiGetItemCount () {
1042 assert(ui_context);
1043 return ui_context.count;
1046 public int uiGetLastItemCount () {
1047 assert(ui_context);
1048 return ui_context.last_count;
1051 public uint uiGetAllocSize () {
1052 assert(ui_context);
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 () {
1067 assert(ui_context);
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 () {
1078 assert(ui_context);
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 () {
1086 assert(ui_context);
1087 return ui_context.focus_item;
1091 public void uiBeginLayout () {
1092 assert(ui_context);
1093 assert(ui_context.stage == UI_STAGE_PROCESS); // must run uiEndLayout(), uiProcess() first
1094 uiClear();
1095 ui_context.stage = UI_STAGE_LAYOUT;
1098 public void uiClearState () {
1099 assert(ui_context);
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 () {
1107 assert(ui_context);
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);
1113 item.firstkid = -1;
1114 item.nextitem = -1;
1115 return idx;
1118 public void uiNotifyItem (int item, UIevent event) {
1119 assert(ui_context);
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;
1129 while (true) {
1130 int nextitem = uiNextSibling(item);
1131 if (nextitem < 0) return item;
1132 item = nextitem;
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;
1144 return sibling;
1147 public int uiInsert (int item, int child) {
1148 assert(child > 0);
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;
1155 } else {
1156 uiAppend(uiLastChild(item), child);
1158 return child;
1161 public int uiInsertFront (int item, int child) {
1162 return uiInsert(item, child);
1165 public int uiInsertBack (int item, int child) {
1166 assert(child > 0);
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;
1173 return child;
1176 public void uiSetFrozen (int item, int enable) {
1177 UIitem* pitem = uiItemPtr(item);
1178 if (enable)
1179 pitem.flags |= UI_ITEM_FROZEN;
1180 else
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;
1188 if (!w)
1189 pitem.flags &= ~UI_ITEM_HFIXED;
1190 else
1191 pitem.flags |= UI_ITEM_HFIXED;
1192 if (!h)
1193 pitem.flags &= ~UI_ITEM_VFIXED;
1194 else
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) {
1243 int wdim = dim+2;
1244 // largest size is required size
1245 short need_size = 0;
1246 int kid = pitem.firstkid;
1247 while (kid >= 0) {
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) {
1259 int wdim = dim+2;
1260 short need_size = 0;
1261 int kid = pitem.firstkid;
1262 while (kid >= 0) {
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) {
1273 int wdim = dim+2;
1274 short need_size = 0;
1275 short need_size2 = 0;
1276 int kid = pitem.firstkid;
1277 while (kid >= 0) {
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);
1282 // newline
1283 need_size = 0;
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) {
1294 int wdim = dim+2;
1295 short need_size = 0;
1296 short need_size2 = 0;
1297 int kid = pitem.firstkid;
1298 while (kid >= 0) {
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;
1303 // newline
1304 need_size = 0;
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;
1319 while (kid >= 0) {
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:
1327 // flex model
1328 if (dim) // direction
1329 uiComputeStackedSize(pitem, 1);
1330 else
1331 uiComputeImposedSize(pitem, 0);
1332 break;
1333 case UI_ROW|UI_WRAP:
1334 // flex model
1335 if (!dim) // direction
1336 uiComputeWrappedStackedSize(pitem, 0);
1337 else
1338 uiComputeWrappedSize(pitem, 1);
1339 break;
1340 case UI_COLUMN:
1341 case UI_ROW:
1342 // flex model
1343 if ((pitem.flags&1) == cast(uint)dim) // direction
1344 uiComputeStackedSize(pitem, dim);
1345 else
1346 uiComputeImposedSize(pitem, dim);
1347 break;
1348 default:
1349 // layout model
1350 uiComputeImposedSize(pitem, dim);
1351 break;
1355 // stack all items according to their alignment
1356 public void uiArrangeStacked (UIitem* pitem, int dim, bool wrap) {
1357 int wdim = dim+2;
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) {
1364 short used = 0;
1366 int count = 0; // count of fillers
1367 int squeezed_count = 0; // count of squeezable elements
1368 int total = 0;
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;
1373 int end_kid = -1;
1374 while (kid >= 0) {
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) {
1380 // grow
1381 ++count;
1382 extend += pkid.margins[dim]+pkid.margins[wdim];
1383 } else {
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))) {
1389 end_kid = kid;
1390 hardbreak = ((pkid.flags&UI_BREAK) == UI_BREAK);
1391 // add marker for subsequent queries
1392 pkid.flags |= UI_BREAK;
1393 break;
1394 } else {
1395 used = extend;
1396 kid = uiNextSibling(kid);
1398 ++total;
1401 int extra_space = space-used;
1402 float filler = 0.0f;
1403 float spacer = 0.0f;
1404 float extra_margin = 0.0f;
1405 float eater = 0.0f;
1407 if (extra_space > 0) {
1408 if (count) {
1409 filler = cast(float)extra_space/cast(float)count;
1410 } else if (total) {
1411 switch(pitem.flags&UI_JUSTIFY) {
1412 default:
1413 extra_margin = extra_space/2.0f;
1414 break;
1415 case UI_JUSTIFY:
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);
1418 break;
1419 case UI_START:
1420 break;
1421 case UI_END:
1422 extra_margin = extra_space;
1423 break;
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];
1432 float x1;
1433 // second pass: distribute and rescale
1434 kid = start_kid;
1435 while (kid != end_kid) {
1436 short ix0,ix1;
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) {
1443 // grow
1444 x1 = x+filler;
1445 } else if ((fflags&UI_ITEM_HFIXED) == UI_ITEM_HFIXED) {
1446 x1 = x+cast(float)pkid.size[dim];
1447 } else {
1448 // squeeze
1449 x1 = x+ui_maxf(0.0f, cast(float)pkid.size[dim]+eater);
1451 ix0 = cast(short)x;
1452 if (wrap)
1453 ix1 = cast(short)ui_minf(max_x2-cast(float)pkid.margins[wdim], x1);
1454 else
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) {
1470 int wdim = dim+2;
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) {
1476 default: break;
1477 case UI_HCENTER:
1478 pkid.margins[dim] += (space-pkid.size[dim])/2-pkid.margins[wdim];
1479 break;
1480 case UI_RIGHT:
1481 pkid.margins[dim] = cast(short)(space-pkid.size[dim]-pkid.margins[wdim]);
1482 break;
1483 case UI_HFILL:
1484 pkid.size[dim] = cast(short)ui_max(0,space-pkid.margins[dim]-pkid.margins[wdim]);
1485 break;
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) {
1499 int wdim = dim+2;
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) {
1506 default:
1507 pkid.size[dim] = cast(short)ui_min(pkid.size[dim], min_size);
1508 break;
1509 case UI_HCENTER:
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];
1512 break;
1513 case UI_RIGHT:
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]);
1516 break;
1517 case UI_HFILL:
1518 pkid.size[dim] = min_size;
1519 break;
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) {
1532 int wdim = dim+2;
1533 short offset = pitem.margins[dim];
1534 short need_size = 0;
1535 int kid = pitem.firstkid;
1536 int start_kid = kid;
1537 while (kid >= 0) {
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;
1542 start_kid = kid;
1543 // newline
1544 need_size = 0;
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;
1553 return offset;
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]);
1567 break;
1568 case UI_ROW|UI_WRAP:
1569 // flex model, wrapping
1570 if (!dim) { // direction
1571 uiArrangeStacked(pitem, 0, true);
1572 } else {
1573 uiArrangeWrappedImposedSqueezed(pitem, 1);
1575 break;
1576 case UI_COLUMN:
1577 case UI_ROW:
1578 // flex model
1579 if ((pitem.flags&1) == cast(uint)dim) // direction
1580 uiArrangeStacked(pitem, dim, false);
1581 else
1582 uiArrangeImposedSqueezed(pitem, dim);
1583 break;
1584 default:
1585 // layout model
1586 uiArrangeImposed(pitem, dim);
1587 break;
1589 int kid = uiFirstChild(item);
1590 while (kid >= 0) {
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;
1607 int count = 0;
1608 int failed = 0;
1609 int kid1 = pitem1.firstkid;
1610 int kid2 = pitem2.firstkid;
1611 while (kid1 != -1) {
1612 UIitem* pkid1 = uiLastItemPtr(kid1);
1613 ++count;
1614 if (!uiMapItems(kid1, kid2)) {
1615 failed = count;
1616 break;
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;
1625 return true;
1628 public int uiRecoverItem (int olditem) {
1629 assert(ui_context);
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) {
1636 assert(ui_context);
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 () {
1643 assert(ui_context);
1644 assert(ui_context.stage == UI_STAGE_LAYOUT); // must run uiBeginLayout() first
1646 if (ui_context.count) {
1647 uiComputeSize(0,0);
1648 uiArrange(0,0);
1649 uiComputeSize(0,1);
1650 uiArrange(0,1);
1652 if (ui_context.last_count) {
1653 // map old item id to new item id
1654 uiMapItems(0,0);
1658 uiValidateStateItems();
1659 if (ui_context.count) {
1660 // drawing routines may require this to be set already
1661 uiUpdateHotItem();
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) {
1722 assert(ui_context);
1723 ui_context.handler = handler;
1726 public UIhandler uiGetHandler () {
1727 assert(ui_context);
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);
1753 x -= rect.x;
1754 y -= rect.y;
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)) {
1762 int best_hit = -1;
1763 int kid = uiFirstChild(item);
1764 while (kid >= 0) {
1765 int hit = uiFindItem(kid, x, y, flags, mask);
1766 if (hit >= 0) {
1767 best_hit = hit;
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;
1774 return -1;
1777 public void uiUpdateHotItem () {
1778 assert(ui_context);
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) {
1789 assert(ui_context);
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();
1798 return;
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);
1812 } else {
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) {
1826 default:
1827 case UI_STATE_IDLE:
1828 ui_context.start_cursor = ui_context.cursor;
1829 if (uiGetButton(0)) {
1830 hot_item = -1;
1831 active_item = hot;
1833 if (active_item != focus_item) {
1834 focus_item = -1;
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)) {
1848 hot_item = -1;
1849 hot = uiFindItem(0, ui_context.cursor.x, ui_context.cursor.y, UI_BUTTON2_DOWN, UI_ANY);
1850 if (hot >= 0) {
1851 ui_context.active_modifier = ui_context.active_button_modifier;
1852 uiNotifyItem(hot, UI_BUTTON2_DOWN);
1854 } else {
1855 hot_item = hot;
1857 break;
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);
1865 active_item = -1;
1866 ui_context.state = UI_STATE_IDLE;
1867 } else {
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);
1874 break;
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) {
1886 assert(ui_context);
1887 return ui_context.active_item == item;
1890 int uiIsHot (int item) {
1891 assert(ui_context);
1892 return ui_context.last_hot_item == item;
1895 int uiIsFocused (int item) {
1896 assert(ui_context);
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;
1909 return UI_COLD;
1910 } else if (uiIsHot(item)) {
1911 return UI_HOT;
1913 return UI_COLD;