sq3: show SQLite error messages on stderr by default
[iv.d.git] / nanovega / fui / engine.d
blobdaea9a93e9be0f7cd3b2081b094c1e8e14be2307
1 /* Invisible Vector Library
2 * simple FlexBox-based UI layout engine, suitable for using in
3 * immediate mode GUI libraries.
5 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
6 * Understanding is not required. Only obedience.
8 * This program is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, version 3 of the License ONLY.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 module iv.nanovega.fui.engine /*is aliced*/;
22 import arsd.simpledisplay;
24 import iv.alice;
25 import iv.nanovega.nanovega;
27 //version = fui_many_asserts;
30 // ////////////////////////////////////////////////////////////////////////// //
31 __gshared ushort fuiDoubleTime = 250; // 250 msecs to register doubleclick
34 // ////////////////////////////////////////////////////////////////////////// //
35 align(1) struct FuiPoint {
36 align(1):
37 int x, y;
38 @property pure nothrow @safe @nogc:
39 bool inside (in FuiRect rc) const { pragma(inline, true); return (x >= rc.pos.x && y >= rc.pos.y && x < rc.pos.x+rc.size.w && y < rc.pos.y+rc.size.h); }
41 align(1) struct FuiSize { align(1): int w, h; }
42 align(1) struct FuiRect {
43 align(1):
44 FuiPoint pos;
45 FuiSize size;
46 @property pure nothrow @safe @nogc:
47 int x () const { pragma(inline, true); return pos.x; }
48 int y () const { pragma(inline, true); return pos.y; }
49 int w () const { pragma(inline, true); return size.w; }
50 int h () const { pragma(inline, true); return size.h; }
51 void x (int v) { pragma(inline, true); pos.x = v; }
52 void y (int v) { pragma(inline, true); pos.y = v; }
53 void w (int v) { pragma(inline, true); size.w = v; }
54 void h (int v) { pragma(inline, true); size.h = v; }
56 ref int xp () { pragma(inline, true); return pos.x; }
57 ref int yp () { pragma(inline, true); return pos.y; }
58 ref int wp () { pragma(inline, true); return size.w; }
59 ref int hp () { pragma(inline, true); return size.h; }
61 bool inside (in FuiPoint pt) const { pragma(inline, true); return (pt.x >= pos.x && pt.y >= pos.y && pt.x < pos.x+size.w && pt.y < pos.y+size.h); }
63 align(1) struct FuiMargin { align(1): int left, top, right, bottom; }
66 // ////////////////////////////////////////////////////////////////////////// //
67 // pporperties for layouter
68 public align(1) struct FuiLayoutProps {
69 align(1):
70 enum Orientation {
71 Horizontal,
72 Vertical,
75 // "NPD" means "non-packing direction"
76 enum Align {
77 Center, // the available space is divided evenly
78 Start, // the NPD edge of each box is placed along the NPD of the parent box
79 End, // the opposite-NPD edge of each box is placed along the opposite-NPD of the parent box
80 Stretch, // the NPD-size of each boxes is adjusted to fill the parent box
83 // flags accessors
84 @property pure nothrow @safe @nogc {
85 bool hovered () const { pragma(inline, true); return ((flags&Flags.Hovered) != 0); }
86 bool active () const { pragma(inline, true); return ((flags&Flags.Active) != 0); }
88 bool canBeFocused () const { pragma(inline, true); return ((flags&Flags.CanBeFocused) != 0); }
89 void canBeFocused (bool v) { pragma(inline, true); if (v) flags |= Flags.CanBeFocused; else flags &= ~Flags.CanBeFocused; }
91 bool enabled () const { pragma(inline, true); return ((flags&Flags.Disabled) == 0); }
92 void enabled (bool v) { pragma(inline, true); if (v) flags &= ~Flags.Disabled; else flags = (flags|Flags.Disabled)&~(Flags.Hovered|Flags.Active); }
94 bool disabled () const { pragma(inline, true); return ((flags&Flags.Disabled) != 0); }
95 void disabled (bool v) { pragma(inline, true); if (v) flags = (flags|Flags.Disabled)&~(Flags.Hovered|Flags.Active); else flags &= ~Flags.Disabled; }
97 bool visible () const { pragma(inline, true); return ((flags&Flags.Invisible) == 0); }
98 void visible (bool v) { pragma(inline, true); if (v) flags &= ~Flags.Invisible; else flags = (flags|Flags.Invisible)&~(Flags.Hovered|Flags.Active); }
100 bool hidden () const { pragma(inline, true); return ((flags&Flags.Invisible) != 0); }
101 void hidden (bool v) { pragma(inline, true); if (v) flags = (flags|Flags.Invisible)&~(Flags.Hovered|Flags.Active); else flags &= ~Flags.Invisible; }
103 bool lineBreak () const { pragma(inline, true); return ((flags&Flags.LineBreak) != 0); }
104 void lineBreak (bool v) { pragma(inline, true); if (v) flags |= Flags.LineBreak; else flags &= ~Flags.LineBreak; }
106 bool wantTab () const { pragma(inline, true); return ((flags&Flags.WantTab) != 0); }
107 void wantTab (bool v) { pragma(inline, true); if (v) flags |= Flags.WantTab; else flags &= ~Flags.WantTab; }
109 bool wantReturn () const { pragma(inline, true); return ((flags&Flags.WantReturn) != 0); }
110 void wantReturn (bool v) { pragma(inline, true); if (v) flags |= Flags.WantReturn; else flags &= ~Flags.WantReturn; }
112 ushort userFlags () const { pragma(inline, true); return (flags&Flags.UserFlagsMask); }
113 void userFlags (ushort v) { pragma(inline, true); flags = (flags&~Flags.UserFlagsMask)|v; }
116 // WARNING! don't change this fields from user code!
117 FuiRect position; // calculated item position
118 int itemid = -1;
119 int parent = -1; // item parent
120 int firstChild = -1;
121 int lastChild = -1;
122 int prevSibling = -1;
123 int nextSibling = -1;
125 Orientation orientation = Orientation.Horizontal; // box orientation
126 Align aligning = Align.Start; // NPD for children
127 int flex = 1; // default flex value
129 @property pure nothrow @safe @nogc {
130 bool horizontal () const { pragma(inline, true); return (orientation == Orientation.Horizontal); }
131 bool vertical () const { pragma(inline, true); return (orientation == Orientation.Vertical); }
133 void horizontal (bool v) { pragma(inline, true); orientation = (v ? Orientation.Horizontal : Orientation.Vertical); }
134 void vertical (bool v) { pragma(inline, true); orientation = (v ? Orientation.Vertical : Orientation.Horizontal); }
137 FuiSize minSize;
138 FuiSize maxSize = FuiSize(int.max-1024, int.max-1024); // arbitrary limit, you know
139 FuiMargin padding;
140 int spacing; // spacing for children
141 int lineSpacing; // line spacing for horizontal boxes
143 enum Buttons : ubyte {
144 None = 0,
145 Left = 0x01,
146 Right = 0x02,
147 Middle = 0x04,
150 ubyte clickMask; // buttons that can be used to click this item to do some action
151 ubyte doubleMask; // buttons that can be used to double-click this item to do some action
153 mixin(GroupPropMixin!("hgroup", "hGroup"));
154 mixin(GroupPropMixin!("vgroup", "vGroup"));
156 private:
157 enum Flags : uint {
158 None = 0,
159 UserFlagsMask = 0xffffu,
160 // UI flags
161 Disabled = 0x0001_0000u, // this item is dimed
162 LineBreak = 0x0002_0000u, // layouter should start a new line after this item
163 Invisible = 0x0004_0000u, // this item is used purely for layouting purposes
164 Hovered = 0x0008_0000u, // this item is hovered
165 CanBeFocused = 0x0010_0000u, // this item can be focused
166 Active = 0x0020_0000u, // mouse is pressed on this
167 WantTab = 0x0040_0000u, // want to receive tab key events
168 WantReturn = 0x0080_0000u, // want to receive return key events
169 // internal flags for layouter
170 TempLineBreak = 0x1000_0000u,
171 TouchedByGroup = 0x2000_0000u,
172 LayouterFlagsMask =
173 TempLineBreak|
174 TouchedByGroup|
178 uint flags = Flags.None; // see Flags
179 // "mark counter" for groups; also, bit 31 set means "group head"
180 int hGroupNext = -1; // next hgroup head
181 int vGroupNext = -1; // next vgroup head
182 int hGroupSibling = -1; // next item in this hgroup; not -1 and bit 31 set: head
183 int vGroupSibling = -1; // next item in this vgroup; not -1 and bit 31 set: head
184 //int tempLineHeight; // valid for item after tempLineBreak and first item
185 FuiContextImpl* ctx;
187 enum GroupPropMixin(string name, string gvar) =
188 "void "~name~" (int parent) {\n"~
189 " if (ctx is null || itemid < 0 || parent == itemid) return;\n"~
190 " auto lp = ctx.layprops(parent);\n"~
191 " if (lp is null) assert(0, \"invalid parent for hgroup\");\n"~
192 " if (lp."~gvar~"Sibling == -1) {\n"~
193 " // first item in new group\n"~
194 " lp."~gvar~"Sibling = itemid|0x8000_0000;\n"~
195 " } else {\n"~
196 " // append to group\n"~
197 " auto it = lp."~gvar~"Sibling&0x7fff_ffff;\n"~
198 " version(fui_many_asserts) assert(it != 0x7fff_ffff);\n"~
199 " for (;;) {\n"~
200 " auto clp = ctx.layprops(it);\n"~
201 " version(fui_many_asserts) assert(clp !is null);\n"~
202 " if (clp."~gvar~"Sibling == -1) {\n"~
203 " clp."~gvar~"Sibling = itemid;\n"~
204 " return;\n"~
205 " }\n"~
206 " it = clp."~gvar~"Sibling;\n"~
207 " }\n"~
208 " }\n"~
209 "}\n"~
212 @property pure nothrow @safe @nogc {
213 void resetLayouterFlags () { pragma(inline, true); flags &= ~Flags.LayouterFlagsMask; }
215 bool tempLineBreak () const { pragma(inline, true); return ((flags&Flags.TempLineBreak) != 0); }
216 void tempLineBreak (bool v) { pragma(inline, true); if (v) flags |= Flags.TempLineBreak; else flags &= ~Flags.TempLineBreak; }
218 bool touchedByGroup () const { pragma(inline, true); return ((flags&Flags.TouchedByGroup) != 0); }
219 void touchedByGroup (bool v) { pragma(inline, true); if (v) flags |= Flags.TouchedByGroup; else flags &= ~Flags.TouchedByGroup; }
221 // this is strictly internal thing
222 void hovered (bool v) { pragma(inline, true); if (v) flags |= Flags.Hovered; else flags &= ~Flags.Hovered; }
223 void active (bool v) { pragma(inline, true); if (v) flags |= Flags.Active; else flags &= ~Flags.Active; }
228 // ////////////////////////////////////////////////////////////////////////// //
229 public static struct FuiEvent {
230 enum Type {
231 None, // just in case
232 Char, // param0: dchar; param1: mods&buttons
233 Key, // param0: sdpy keycode; param1: mods&buttons
234 Click, // mouse click; param0: buttton index; param1: mods&buttons
235 Double, // mouse double-click; param0: buttton index; param1: mods&buttons
238 Type type;
239 int item;
240 uint param0;
241 uint param1;
243 @property const pure nothrow @safe @nogc:
244 ubyte mods () { pragma(inline, true); return cast(ubyte)(param1>>8); }
245 ubyte buts () { pragma(inline, true); return cast(ubyte)param1; }
247 Key key () { pragma(inline, true); return cast(Key)param0; }
248 dchar ch () { pragma(inline, true); return cast(dchar)param0; }
249 ubyte bidx () { pragma(inline, true); return cast(ubyte)param0; }
253 // ////////////////////////////////////////////////////////////////////////// //
254 // all controls lives here! ;-)
255 private struct FuiContextImpl {
256 private:
257 enum MaxQueuedEvents = 16;
258 enum MaxQueuedExternalEvents = 64;
260 private:
261 uint rc = 1; // refcount
262 ubyte* pmem; // private memory: this holds controls
263 uint pmemused;
264 uint pmemsize;
265 uint* pidx; // this will hold offset of each item in `pmem`
266 uint pidxsize; // in elements
267 int pcount; // number of controls in this context
268 int focusedId = -1; // what item is focused (i.e. will receive keyboard events)?
269 int lastHover = -1; // for speed
270 ubyte lastButtons, lastMods;
271 FuiPoint lastMouse = FuiPoint(-1, -1); // last mouse coordinates
272 short[8] lastClickDelta = short.max; // how much time passed since last click with the given button was registered?
273 int[8] lastClick = -1; // on which item it was registered?
274 ubyte[8] beventCount; // oooh...
275 FuiEvent[MaxQueuedEvents] events;
276 uint eventHead, eventPos;
277 NVGContext vgc; // doesn't own it
279 nothrow @nogc:
280 private:
281 void queueEvent (int aitem, FuiEvent.Type atype, uint aparam0=0, uint aparam1=0) nothrow @trusted @nogc {
282 if (eventPos >= events.length) return;
283 auto nn = (eventHead+eventPos++)%events.length;
284 with (events.ptr[nn]) {
285 type = atype;
286 item = aitem;
287 param0 = aparam0;
288 param1 = aparam1;
292 bool hasEvents () const pure nothrow @safe @nogc { pragma(inline, true); return (eventPos > 0); }
294 FuiEvent getEvent () nothrow @trusted @nogc {
295 if (eventPos > 0) {
296 auto nn = eventHead;
297 eventHead = (eventHead+1)%events.length;
298 --eventPos;
299 return events.ptr[nn];
300 } else {
301 return FuiEvent.init;
305 void newMousePos (FuiPoint pt) {
306 import std.math : abs;
307 if (lastMouse == pt) return;
308 int hover = itemAt(pt);
309 // fix hovering info
310 if (auto lp = layprops(lastHover)) lp.hovered = false;
311 lastHover = hover;
312 if (auto lp = layprops(hover)) if (lp.enabled) lp.hovered = true;
315 // [0..7]
316 void newButtonState (uint bidx, bool down) {
317 // 0: nothing was pressed or released yet
318 // 1: button was pressed for the first time
319 // 2: button was released for the first time
320 // 3: button was pressed for the second time
321 // 4: button was released for the second time
323 //debug(fui_mouse) { import core.stdc.stdio : printf; printf("NBS: bidx=%u; down=%d\n", bidx, cast(int)down); }
325 void resetActive () {
326 auto i = lastClick.ptr[bidx];
327 if (i == -1) return;
328 foreach (immutable idx, int lc; lastClick) {
329 if (idx != bidx && lc == i) return;
331 layprops(i).active = false;
334 void doRelease () {
335 resetActive();
336 // did we released the button on the same control we pressed it?
337 if (beventCount.ptr[bidx] == 0 || lastHover == -1 || (lastHover != lastClick.ptr[bidx])) {
338 debug(fui_mouse) { import core.stdc.stdio : printf; printf("button #%u released x00: lastHover=%d; lastClick=%d; ec=%u\n", bidx, lastHover, lastClick.ptr[bidx], cast(uint)beventCount.ptr[bidx]); }
339 // no, this is nothing, reset all info
340 lastClick.ptr[bidx] = -1;
341 beventCount.ptr[bidx] = 0;
342 return;
344 auto lp = layprops(lastHover);
345 // yep, check which kind of event this is
346 if (beventCount.ptr[bidx] == 3 && (lp.doubleMask&(1<<bidx)) != 0) {
347 debug(fui_mouse) { import core.stdc.stdio : printf; printf("button #%u possible double: lastHover=%d; lastClick=%d; ec=%u; ltt=%d\n", bidx, lastHover, lastClick.ptr[bidx], cast(uint)beventCount.ptr[bidx], lastClickDelta.ptr[bidx]); }
348 // we accepts doubleclicks, and this can be doubleclick
349 if (lastClickDelta.ptr[bidx] <= fuiDoubleTime) {
350 debug(fui_mouse) { import core.stdc.stdio : printf; printf(" DOUBLE!\n"); }
351 // it comes right in time too
352 queueEvent(lastHover, FuiEvent.Type.Double, bidx, lastButtons|(lastMods<<8));
353 // continue registering doubleclicks
354 lastClickDelta.ptr[bidx] = 0;
355 beventCount.ptr[bidx] = 2;
356 return;
358 debug(fui_mouse) { import core.stdc.stdio : printf; printf(" not double\n"); }
359 // this is invalid doubleclick, revert to simple click
360 beventCount.ptr[bidx] = 1;
361 // start registering doubleclicks
362 lastClickDelta.ptr[bidx] = 0;
364 debug(fui_mouse) { import core.stdc.stdio : printf; printf("button #%u possible single: lastHover=%d; lastClick=%d; ec=%u\n", bidx, lastHover, lastClick.ptr[bidx], cast(uint)beventCount.ptr[bidx]); }
365 // try single click
366 if (beventCount.ptr[bidx] == 1) {
367 debug(fui_mouse) { import core.stdc.stdio : printf; printf(" SINGLE\n"); }
368 if (lp.clickMask&(1<<bidx)) queueEvent(lastHover, FuiEvent.Type.Click, bidx, lastButtons|(lastMods<<8));
369 // start doubleclick timer
370 beventCount.ptr[bidx] = 2;
371 // start registering doubleclicks
372 lastClickDelta.ptr[bidx] = 0;
373 return;
375 debug(fui_mouse) { import core.stdc.stdio : printf; printf(" UNEXPECTED\n"); }
376 // something unexpected, reset it all
377 lastClick.ptr[bidx] = -1;
378 beventCount.ptr[bidx] = 0;
379 lastClickDelta.ptr[bidx] = lastClickDelta[0].max;
382 void doPress () {
383 // void?
384 if (lastHover == -1) {
385 // reset all
386 debug(fui_mouse) { import core.stdc.stdio : printf; printf("button #%u pressed at nowhere\n", bidx); }
387 lastClick.ptr[bidx] = -1;
388 beventCount.ptr[bidx] = 0;
389 lastClickDelta.ptr[bidx] = lastClickDelta[0].max;
390 return;
392 // first press?
393 if (beventCount.ptr[bidx] == 0) {
394 debug(fui_mouse) { import core.stdc.stdio : printf; printf("button #%u first press: lastHover=%d; lastClick=%d; ec=%u\n", bidx, lastHover, lastClick.ptr[bidx], cast(uint)beventCount.ptr[bidx]); }
395 // start single
396 lastClick.ptr[bidx] = lastHover;
397 beventCount.ptr[bidx] = 1;
398 lastClickDelta.ptr[bidx] = lastClickDelta[0].max;
399 auto lp = layprops(lastHover);
400 version(fui_many_asserts) assert(lp !is null);
401 if (lp.canBeFocused) focused = lastHover;
402 if ((lp.clickMask&(1<<bidx)) != 0) lp.active = true;
403 return;
405 // second press?
406 if (beventCount.ptr[bidx] == 2) {
407 debug(fui_mouse) { import core.stdc.stdio : printf; printf("button #%u second press: lastHover=%d; lastClick=%d; ec=%u\n", bidx, lastHover, lastClick.ptr[bidx], cast(uint)beventCount.ptr[bidx]); }
408 //bool asDouble = false;
409 // start double if control is the same
410 if (lastClick.ptr[bidx] == lastHover) {
411 debug(fui_mouse) { import core.stdc.stdio : printf; printf(" SAME\n"); }
412 // same
413 if (lastClickDelta.ptr[bidx] > fuiDoubleTime) {
414 // reset double to single
415 beventCount.ptr[bidx] = 1;
416 lastClickDelta.ptr[bidx] = lastClickDelta[0].max;
417 } else {
418 //asDouble = true;
419 beventCount.ptr[bidx] = 3;
420 //lastClickDelta.ptr[bidx] = 0;
422 } else {
423 debug(fui_mouse) { import core.stdc.stdio : printf; printf(" OTHER\n"); }
424 // other, reset to "first press"
425 lastClick.ptr[bidx] = lastHover;
426 beventCount.ptr[bidx] = 1;
427 lastClickDelta.ptr[bidx] = lastClickDelta[0].max;
429 auto lp = layprops(lastHover);
430 version(fui_many_asserts) assert(lp !is null);
431 if (lp.canBeFocused) focused = lastHover;
432 if (((lp.doubleMask|lp.clickMask)&(1<<bidx)) != 0) lp.active = true;
433 return;
435 debug(fui_mouse) { import core.stdc.stdio : printf; printf("button #%u unexpected press: lastHover=%d; lastClick=%d; ec=%u\n", bidx, lastHover, lastClick.ptr[bidx], cast(uint)beventCount.ptr[bidx]); }
436 resetActive();
437 // something unexpected, reset all
438 lastClick.ptr[bidx] = -1;
439 beventCount.ptr[bidx] = 0;
440 lastClickDelta.ptr[bidx] = lastClickDelta[0].max;
443 if (bidx >= lastClickDelta.length) return;
444 if (down) {
445 // button pressed
446 if ((lastButtons&(1<<bidx)) != 0) return; // state didn't changed
447 lastButtons |= cast(ubyte)(1<<bidx);
448 debug(fui_mouse) { import core.stdc.stdio : printf; printf("DOWN: bidx=%u; buts=0x%02x\n", bidx, lastButtons); }
449 doPress();
450 } else {
451 // button released
452 if ((lastButtons&(1<<bidx)) == 0) return; // state didn't changed
453 lastButtons &= cast(ubyte)~(1<<bidx);
454 debug(fui_mouse) { import core.stdc.stdio : printf; printf("UP : bidx=%u; buts=0x%02x\n", bidx, lastButtons); }
455 doRelease();
459 // external actions
460 void mouseAt (in FuiPoint pt) {
461 if (lastMouse != pt) {
462 auto nh = itemAt(pt);
463 if (nh != lastHover) {
464 if (auto lp = layprops(lastHover)) lp.hovered = false;
465 if (auto lp = layprops(nh)) { if (lp.enabled) lp.hovered = true; else nh = -1; } else nh = -1;
466 lastHover = nh;
471 private import core.time;
472 private MonoTime lastUpdateTime;
473 private bool updateWasCalled;
475 static struct ExternalEvent {
476 enum Type { Key, Char, Mouse }
477 Type type;
478 KeyEvent kev;
479 MouseEvent mev;
480 dchar cev;
482 private ExternalEvent[MaxQueuedExternalEvents] extEvents;
483 uint extevHead, extevPos;
485 void keyboardEvent (KeyEvent ev) nothrow @trusted @nogc {
486 if (extevPos >= extEvents.length) return;
487 auto nn = (extevHead+extevPos++)%extEvents.length;
488 with (extEvents.ptr[nn]) { type = ExternalEvent.Type.Key; kev = ev; }
491 void charEvent (dchar ch) nothrow @trusted @nogc {
492 if (extevPos >= extEvents.length || ch == 0 || ch > dchar.max) return;
493 auto nn = (extevHead+extevPos++)%extEvents.length;
494 with (extEvents.ptr[nn]) { type = ExternalEvent.Type.Char; cev = ch; }
497 void mouseEvent (MouseEvent ev) nothrow @trusted @nogc {
498 if (extevPos >= extEvents.length) return;
499 auto nn = (extevHead+extevPos++)%extEvents.length;
500 with (extEvents.ptr[nn]) { type = ExternalEvent.Type.Mouse; mev = ev; }
503 // don't pass anything to automatically calculate update delta
504 void update (int msecDelta) {
505 if (!updateWasCalled) {
506 updateWasCalled = true;
507 lastUpdateTime = MonoTime.currTime;
509 if (msecDelta < 0) {
510 auto ct = MonoTime.currTime;
511 msecDelta = cast(int)((ct-lastUpdateTime).total!"msecs");
512 lastUpdateTime = ct;
513 } else {
514 lastUpdateTime = MonoTime.currTime;
516 //assert(msecDelta >= 0);
517 foreach (ref ltm; lastClickDelta) {
518 if (ltm >= 0 && ltm < lastClickDelta[0].max) {
519 auto nt = ltm+msecDelta;
520 if (nt < 0 || nt > lastClickDelta[0].max) nt = lastClickDelta[0].max;
521 ltm = cast(short)nt;
524 while (extevPos > 0) {
525 final switch (extEvents.ptr[extevHead].type) {
526 case ExternalEvent.Type.Char:
527 if (auto lc = layprops(focused)) {
528 if (lc.canBeFocused) queueEvent(focused, FuiEvent.Type.Char, cast(uint)extEvents.ptr[extevHead].cev);
530 break;
531 case ExternalEvent.Type.Key:
532 if (!extEvents.ptr[extevHead].kev.pressed) break;
533 if (extEvents.ptr[extevHead].kev.pressed && extEvents.ptr[extevHead].kev.key == Key.Tab) {
534 auto lfc = layprops(focused);
535 if (lfc is null) {
536 focused = findNext(0, (int item) { if (auto lc = layprops(item)) return lc.canBeFocused; else return false; });
537 } else if (!lfc.wantTab || lfc.disabled) {
538 focused = findNext(focused, (int item) { if (auto lc = layprops(item)) return (item != focused && lc.canBeFocused); else return false; });
539 if (focused == -1) focused = findNext(0, (int item) { if (auto lc = layprops(item)) return lc.canBeFocused; else return false; });
541 break;
543 if (auto lc = layprops(focused)) {
544 if (lc.canBeFocused && lc.enabled) queueEvent(focused, FuiEvent.Type.Key, cast(uint)extEvents.ptr[extevHead].kev.key);
546 break;
547 case ExternalEvent.Type.Mouse:
548 auto ev = &extEvents.ptr[extevHead].mev;
549 mouseAt(FuiPoint(ev.x, ev.y));
550 switch (ev.type) {
551 case MouseEventType.buttonPressed:
552 case MouseEventType.buttonReleased:
553 if (ev.button) newButtonState(cast(uint)ev.button-1, (ev.type == MouseEventType.buttonPressed));
554 break;
555 case MouseEventType.motion:
556 //{ import std.stdio; writeln(ev.x, ",", ev.y); }
557 break;
558 default:
560 break;
562 extevHead = (extevHead+1)%extEvents.length;
563 --extevPos;
567 private:
568 T* xcalloc(T) () if (!is(T == class) && T.sizeof <= 65536) {
569 import core.memory : GC;
570 if (pmemused+T.sizeof > 0x100_0000) assert(0, "Fui context too big");
571 if (pmemused+T.sizeof > pmemsize) {
572 import core.stdc.stdlib : realloc;
573 uint newsz = pmemused+cast(uint)T.sizeof;
574 if (T.sizeof <= 4096) newsz += cast(uint)T.sizeof*16; // add more space for such controls
575 newsz = (newsz|0xfff)+1; // align to 4KB
576 if (pmem !is null) GC.removeRange(pmem);
577 auto v = realloc(pmem, newsz);
578 if (v is null) assert(0, "out of memory for Fui context");
579 pmem = cast(ubyte*)v;
580 pmem[pmemsize..newsz] = 0;
581 pmemsize = newsz;
582 GC.addRange(pmem, newsz);
584 version(fui_many_asserts) assert(pmemsize-pmemused >= T.sizeof);
585 ubyte* res = pmem+pmemused;
586 pmemused += cast(uint)T.sizeof;
587 static if (is(T == struct)) {
588 import core.stdc.string : memcpy;
589 static immutable T i = T.init;
590 memcpy(res, &i, T.sizeof);
592 return cast(T*)res;
595 @property int lastItemIndex () const pure { pragma(inline, true); return pcount-1; }
597 // -1: none
598 @property int focused () const pure { pragma(inline, true); return focusedId; }
599 @property void focused (int id) pure { pragma(inline, true); focusedId = (id >= 0 && id < pcount ? id : -1); }
601 // add new item; set it's offset to current memtop; return pointer to allocated data
602 T* addItem(T) () if (!is(T == class) && T.sizeof <= 65536) {
603 if (pcount >= 65535) assert(0, "too many controls in Fui context"); // arbitrary limit
604 auto ofs = pmemused;
605 auto res = xcalloc!T();
606 if (pidxsize-pcount < 1) {
607 import core.stdc.stdlib : realloc;
608 uint newsz = cast(uint)(*pidx).sizeof*pidxsize+128; // make room for more controls
609 auto v = realloc(pidx, newsz);
610 if (v is null) assert(0, "out of memory for Fui context");
611 pidx = cast(uint*)v;
612 pidxsize = newsz/cast(uint)(*pidx).sizeof;
614 version(fui_many_asserts) assert(pidxsize-pcount > 0);
615 pidx[pcount] = ofs;
616 ++pcount;
617 return res;
620 void clear () {
621 pmemused = 0;
622 pcount = 0;
623 focusedId = -1;
624 lastHover = -1;
625 lastClickDelta[] = short.max;
626 lastClick[] = -1; // on which item it was registered?
627 eventHead = eventPos = 0;
630 // this will clear only controls, use with care!
631 void clearControls () {
632 pmemused = 0;
633 pcount = 0;
636 public:
637 @disable this (this); // no copies!
639 static void decRef (usize me) {
640 if (me) {
641 auto nfo = cast(FuiContextImpl*)me;
642 version(fui_many_asserts) assert(nfo.rc);
643 if (--nfo.rc == 0) {
644 import core.stdc.stdlib : free;
645 if (nfo.pmem !is null) {
646 import core.memory : GC;
647 GC.removeRange(nfo.pmem);
648 free(nfo.pmem);
650 if (nfo.pidx !is null) free(nfo.pidx);
651 free(nfo);
656 @property int length () pure const nothrow @safe @nogc { pragma(inline, true); return pcount; }
658 inout(FuiLayoutProps)* layprops (int idx) inout {
659 pragma(inline, true);
660 return (idx >= 0 && idx < length ? cast(typeof(return))(pmem+pidx[idx]) : null);
663 // -1 or item id, iterating items backwards (so last drawn will be first hit)
664 int itemAt (FuiPoint pt) {
665 int check (int id, FuiPoint g) {
666 if (auto lp = layprops(id)) {
667 if (!lp.visible) return -1;
668 } else {
669 return -1;
671 // go to last sibling
672 debug(fui_item_at) { import core.stdc.stdio : printf; printf("startsib: %d\n", id); }
673 for (;;) {
674 auto lp = layprops(id);
675 version(fui_many_asserts) assert(lp !is null);
676 if (lp.nextSibling == -1) break;
677 id = lp.nextSibling;
679 debug(fui_item_at) { import core.stdc.stdio : printf; printf("lastsib: %d\n", id); }
680 // check all siblings from the last one
681 for (;;) {
682 auto lp = layprops(id);
683 if (lp is null) return -1;
684 if (lp.visible) {
685 auto rc = lp.position;
686 rc.xp += g.x;
687 rc.yp += g.y;
688 if (pt.inside(rc)) {
689 // inside, go on
690 if (lp.firstChild == -1) {
691 debug(fui_item_at) { import core.stdc.stdio : printf; printf("FOUND %d: pt=(%d,%d) g=(%d,%d) rc=(%d,%d|%d,%d)\n", id, pt.x, pt.y, g.x, g.y, rc.x, rc.y, rc.w, rc.h); }
692 return id; // i found her!
694 debug(fui_item_at) { import core.stdc.stdio : printf; printf("going down: fc=%d; lc=%d\n", lp.firstChild, lp.lastChild); }
695 auto res = check(lp.lastChild, rc.pos);
696 if (res != -1) return res; // i found her!
697 } else {
698 debug(fui_item_at) { import core.stdc.stdio : printf; printf("skip %d: pt=(%d,%d) g=(%d,%d) rc=(%d,%d|%d,%d)\n", id, pt.x, pt.y, g.x, g.y, rc.x, rc.y, rc.w, rc.h); }
701 // move to previous sibling
702 id = lp.prevSibling;
705 return check(0, layprops(0).position.pos);
708 // return id or -1
709 // contrary to what you may think, `id` itself will be checked too
710 int findNext (int id, scope bool delegate (int id) nothrow @nogc check) {
711 if (id == -1) id = 0;
712 for (;;) {
713 auto lp = layprops(id);
714 if (lp is null) return -1;
715 if (lp.firstChild != -1) {
716 // has children, descent
717 id = lp.firstChild;
718 continue;
720 // no children, check ourself
721 if (check(id)) return id;
722 // go to next sibling
723 for (;;) {
724 if (lp.nextSibling != -1) { id = lp.nextSibling; break; }
725 // no sibling, bubble and get next sibling
726 id = lp.parent;
727 lp = layprops(id);
728 if (lp is null) return -1;
733 @property NVGContext vg () nothrow @trusted @nogc { pragma(inline, true); return vgc; }
734 @property void vg (NVGContext v) nothrow @trusted @nogc { pragma(inline, true); vgc = v; }
738 // ////////////////////////////////////////////////////////////////////////// //
739 // note that GC *WILL* *NOT* scan private context memory!
740 // also, item struct dtors/postblits *MAY* *NOT* *BE* *CALLED*!
741 struct FuiContext {
742 static assert(usize.sizeof >= (void*).sizeof);
744 private:
745 usize ctxp; // hide from GC
747 nothrow @nogc:
748 private:
749 inout(FuiContextImpl)* ctx () inout { pragma(inline, true); return cast(typeof(return))ctxp; }
751 void decRef () { pragma(inline, true); if (ctxp) { FuiContextImpl.decRef(ctxp); ctxp = 0; } }
752 void incRef () { pragma(inline, true); if (ctxp) ++(cast(FuiContextImpl*)ctxp).rc; }
754 public:
755 // this will produce new context, ready to accept controls
756 static FuiContext create (NVGContext vg=null) {
757 import core.stdc.stdlib : malloc;
758 import core.stdc.string : memcpy;
759 FuiContext res;
760 // each context always have top-level panel
761 auto ct = cast(FuiContextImpl*)malloc(FuiContextImpl.sizeof);
762 if (ct is null) assert(0, "out of memory for Fui context");
763 static immutable FuiContextImpl i = FuiContextImpl.init;
764 memcpy(ct, &i, FuiContextImpl.sizeof);
765 ct.vg = vg;
766 res.ctxp = cast(usize)ct;
767 // add root panel
768 res.ctx.addItem!FuiLayoutProps();
769 // done
770 return res;
773 public:
774 // refcounting mechanics
775 this (in FuiContext csrc) { ctxp = csrc.ctxp; incRef(); }
776 ~this () { pragma(inline, true); decRef(); }
777 this (this) { pragma(inline, true); incRef(); }
778 void opAssign (in FuiContext csrc) {
779 if (csrc.ctxp) {
780 // first increase refcounter for source
781 ++(cast(FuiContextImpl*)csrc.ctxp).rc;
782 // now decreare our refcounter
783 FuiContextImpl.decRef(ctxp);
784 // and copy source pointer
785 ctxp = csrc.ctxp;
786 } else if (ctxp) {
787 // assigning empty context
788 FuiContextImpl.decRef(ctxp);
789 ctxp = 0;
793 public:
794 @property int length () const { pragma(inline, true); return (ctxp ? ctx.length : 0); }
795 alias opDollar = length;
797 @property bool valid () const { pragma(inline, true); return (length > 0); }
799 // add new item; return pointer to allocated data
800 // in context implementation we place item data right after FuiLayoutProps
801 int addItem(T) (int parent=0) if (!is(T == class) && T.sizeof <= 65536) {
802 if (ctxp == 0) assert(0, "can't add item to uninitialized Fui context");
803 if (length == 0) assert(0, "invalid Fui context");
804 if (parent >= length) assert(0, "invalid parent for Fui item");
805 auto cidx = length;
806 // add layouter properties
807 ctx.addItem!FuiLayoutProps();
808 auto clp = layprops(cidx);
809 clp.ctx = ctx;
810 clp.itemid = cidx;
811 if (parent >= 0) {
812 version(fui_many_asserts) assert(clp.prevSibling == -1);
813 version(fui_many_asserts) assert(clp.nextSibling == -1);
814 auto pp = layprops(parent);
815 clp.parent = parent;
816 clp.prevSibling = pp.lastChild;
817 if (pp.firstChild == -1) {
818 // no children
819 version(fui_many_asserts) assert(pp.lastChild == -1);
820 pp.firstChild = pp.lastChild = cidx;
821 } else {
822 version(fui_many_asserts) assert(pp.lastChild != -1);
823 layprops(pp.lastChild).nextSibling = cidx;
824 pp.lastChild = cidx;
827 // add item data
828 ctx.xcalloc!T();
829 return cidx;
832 // this *WILL* *NOT* call item dtors!
833 void clear () {
834 pragma(inline, true);
835 if (ctxp) {
836 ctx.clear();
837 // add root panel
838 ctx.addItem!FuiLayoutProps();
842 // this will clear only controls, use with care!
843 void clearControls () {
844 pragma(inline, true);
845 if (ctxp) {
846 ctx.clearControls();
847 // add root panel
848 ctx.addItem!FuiLayoutProps();
852 inout(T)* item(T) (int idx) inout {
853 pragma(inline, true);
854 return (ctxp && idx > 0 && idx < length ? cast(typeof(return))(ctx.pmem+ctx.pidx[idx]+FuiLayoutProps.sizeof) : null);
857 inout(FuiLayoutProps)* layprops (int idx) inout {
858 pragma(inline, true);
859 return (ctxp && idx >= 0 && idx < length ? cast(typeof(return))(ctx.pmem+ctx.pidx[idx]) : null);
862 // should be called after adding all controls, or when something was changed
863 void relayout () {
864 import std.algorithm : min, max;
866 int hGroupLast = -1, vGroupLast = -1; // list tails
868 void resetValues () {
869 enum FixGroupEnum(string gvar) =
870 "if (lp."~gvar~"Sibling != -1 && (cast(uint)(lp."~gvar~"Sibling)&0x8000_0000)) {\n"~
871 " // group start, fix list\n"~
872 " lp."~gvar~"Next = "~gvar~"Last;\n"~
873 " "~gvar~"Last = idx;\n"~
874 "}\n"~
876 // reset sizes and positions for all controls
877 // also, find and fix hgroups and vgroups
878 foreach (int idx; 0..length) {
879 auto lp = layprops(idx);
880 lp.resetLayouterFlags();
881 lp.position = lp.position.init; // zero it out
882 // setup group lists
883 mixin(FixGroupEnum!"hGroup");
884 mixin(FixGroupEnum!"vGroup");
887 { import core.stdc.stdio : printf; printf("hGroupLast=%d; vGroupLast=%d\n", hGroupLast, vGroupLast); }
888 for (int n = hGroupLast; n != -1; n = layprops(n).hGroupNext) {
889 import core.stdc.stdio : printf;
890 printf("=== HGROUP #%d ===\n", n);
891 int id = hGroupLast;
892 for (;;) {
893 auto lp = layprops(id);
894 if (lp is null) break;
895 printf(" item #%d\n", id);
896 if (lp.hGroupSibling == -1) break;
897 id = lp.hGroupSibling&0x7fff_ffff;
903 // layout children in this item
904 // `spareGroups`: don't touch widget sizes for hv groups
905 // `spareAll`: don't fix this widget's size
906 void layit (int topid) {
907 auto lp = layprops(topid);
908 if (lp is null) return;
909 // if we do group relayouting, skip touched items
911 // cache values
912 immutable bpadLeft = max(0, lp.padding.left);
913 immutable bpadRight = max(0, lp.padding.right);
914 immutable bpadTop = max(0, lp.padding.top);
915 immutable bpadBottom = max(0, lp.padding.bottom);
916 immutable bspc = max(0, lp.spacing);
917 immutable hbox = (lp.orientation == FuiLayoutProps.Orientation.Horizontal);
919 // widget can only grow, and while doing that, `maxSize` will be respected, so we don't need to fix it's size
921 // layout children, insert line breaks, if necessary
922 int curWidth = bpadLeft+bpadRight, maxW = bpadLeft+bpadRight, maxH = bpadTop+bpadBottom;
923 int lastCIdx = -1; // last processed item for the current line
924 int lineH = 0; // for the current line
925 int lineCount = 0;
926 //int lineStartIdx = 0;
928 // unconditionally add current item to the current line
929 void addToLine (FuiLayoutProps* clp, int cidx) {
930 clp.tempLineBreak = false;
931 debug(fui_layout) { import core.stdc.stdio : printf; printf("addToLine #%d; curWidth=%d; newWidth=%d\n", lineCount, curWidth, curWidth+clp.position.w+(lastCIdx != -1 ? bspc : 0)); }
932 curWidth += clp.position.w+(lastCIdx != -1 ? bspc : 0);
933 lineH = max(lineH, clp.position.h);
934 lastCIdx = cidx;
937 // flush current line
938 void flushLine () {
939 if (lastCIdx == -1) return;
940 // mark last item as line break
941 layprops(lastCIdx).tempLineBreak = true;
942 debug(fui_layout) { import core.stdc.stdio : printf; printf("flushLine #%d; curWidth=%d; maxW=%d; lineH=%d; maxH=%d; new maxH=%d\n", lineCount, curWidth, maxW, lineH+(lineCount ? lp.lineSpacing : 0), maxH, maxH+lineH+(lineCount ? lp.lineSpacing : 0)); }
943 //layprops(lineStartIdx).tempLineHeight = lineH;
944 // fix max width
945 maxW = max(maxW, curWidth);
946 // fix max height
947 maxH += lineH+(lineCount ? lp.lineSpacing : 0);
948 // restart line
949 curWidth = bpadLeft+bpadRight;
950 lastCIdx = -1;
951 lineH = 0;
952 ++lineCount;
955 // put item, do line management
956 void putItem (FuiLayoutProps* clp, int cidx) {
957 int nw = curWidth+clp.position.w+(lastCIdx != -1 ? bspc : 0);
958 // do we neeed to start a new line?
959 if (nw <= (lp.position.w ? lp.position.w : lp.maxSize.w)) {
960 addToLine(clp, cidx);
961 return;
963 // yes, check if we have at least one item in the current line
964 if (lastCIdx == -1) {
965 // alas, no items in the current line, put one
966 addToLine(clp, cidx);
967 // and flush it immediately
968 flushLine();
969 } else {
970 // flush current line
971 flushLine();
972 // and add this item to it
973 addToLine(clp, cidx);
977 // layout children, insert "soft" line breaks
978 int cidx = lp.firstChild;
979 for (;;) {
980 auto clp = layprops(cidx);
981 if (clp is null) break;
982 layit(cidx); // layout children of this box
983 if (hbox) {
984 // for horizontal box, logic is somewhat messy
985 putItem(clp, cidx);
986 if (clp.flags&clp.Flags.LineBreak) flushLine();
987 } else {
988 // for vertical box, it is as easy as this
989 clp.tempLineBreak = true;
990 maxW = max(maxW, clp.position.w+bpadLeft+bpadRight);
991 maxH += clp.position.h+(lineCount ? bspc : 0);
992 ++lineCount;
994 cidx = clp.nextSibling;
996 if (hbox) flushLine(); // flush last list for horizontal box (it is safe to flush empty line)
998 // grow box or clamp max size
999 // but only if size is not defined; in other cases our size is changed by parent to fit in
1000 if (lp.position.w == 0) lp.position.w = min(max(0, lp.minSize.w, maxW), lp.maxSize.w);
1001 if (lp.position.h == 0) lp.position.h = min(max(0, lp.minSize.h, maxH), lp.maxSize.h);
1002 maxH = lp.position.h;
1003 maxW = lp.position.w;
1005 int flexTotal, flexBoxCount, curSpc, spaceLeft;
1007 if (hbox) {
1008 // layout horizontal box; we should do this for each line separately
1009 int lineStartY = bpadTop;
1011 void resetLine () {
1012 flexTotal = 0;
1013 flexBoxCount = 0;
1014 curSpc = 0;
1015 spaceLeft = lp.position.w-(bpadLeft+bpadRight);
1016 lineH = 0;
1019 int lstart = lp.firstChild;
1020 int lineNum = 0;
1021 for (;;) {
1022 if (layprops(lstart) is null) break;
1023 // calculate flex variables and line height
1024 --lineCount; // so 0 will be "last line"
1025 version(fui_many_asserts) assert(lineCount >= 0);
1026 resetLine();
1027 cidx = lstart;
1028 for (;;) {
1029 auto clp = layprops(cidx);
1030 if (clp is null) break;
1031 auto dim = clp.position.w+curSpc;
1032 spaceLeft -= dim;
1033 lineH = max(lineH, clp.position.h);
1034 // process flex
1035 if (clp.flex > 0) { flexTotal += clp.flex; ++flexBoxCount; }
1036 curSpc = bspc;
1037 if (clp.tempLineBreak) break; // no more in this line
1038 cidx = clp.nextSibling;
1040 if (lineCount == 0) lineH = max(lineH, lp.position.h-bpadBottom-lineStartY-lineH);
1041 debug(fui_layout) { import core.stdc.stdio : printf; printf("lineStartY=%d; lineH=%d\n", lineStartY, lineH); }
1043 // distribute flex space, fix coordinates
1044 debug(fui_layout) { import core.stdc.stdio : printf; printf("flexTotal=%d; flexBoxCount=%d; spaceLeft=%d\n", flexTotal, flexBoxCount, spaceLeft); }
1045 cidx = lstart;
1046 float flt = cast(float)flexTotal;
1047 float left = cast(float)spaceLeft;
1048 int curpos = bpadLeft;
1049 for (;;) {
1050 auto clp = layprops(cidx);
1051 if (clp is null) break;
1052 // fix packing coordinate
1053 clp.position.x = curpos;
1054 bool doChildrenRelayout = false;
1055 // fix non-packing coordinate (and, maybe, non-packing dimension)
1056 // fix y coord
1057 final switch (clp.aligning) {
1058 case FuiLayoutProps.Align.Start: clp.position.y = lineStartY; break;
1059 case FuiLayoutProps.Align.End: clp.position.y = (lineStartY+lineH)-clp.position.h; break;
1060 case FuiLayoutProps.Align.Center: clp.position.y = lineStartY+(lineH-clp.position.h)/2; break;
1061 case FuiLayoutProps.Align.Stretch:
1062 clp.position.y = lineStartY;
1063 int nd = min(max(0, lineH, clp.minSize.h), clp.maxSize.h);
1064 if (nd != clp.position.h) {
1065 // size changed, relayout children
1066 doChildrenRelayout = true;
1067 clp.position.h = nd;
1069 break;
1071 // fix flexbox size
1072 if (clp.flex > 0) {
1073 int toadd = cast(int)(left*cast(float)clp.flex/flt);
1074 if (toadd > 0) {
1075 // size changed, relayout children
1076 doChildrenRelayout = true;
1077 clp.position.wp += toadd;
1080 // advance packing coordinate
1081 curpos += clp.position.w+bspc;
1082 // relayout children if dimensions was changed
1083 if (doChildrenRelayout) layit(cidx);
1084 cidx = clp.nextSibling;
1085 if (clp.tempLineBreak) break; // next line, please!
1087 // yep, move to next line
1088 lstart = cidx;
1089 debug(fui_layout) { import core.stdc.stdio : printf; printf("lineStartY=%d; next lineStartY=%d\n", lineStartY, lineStartY+lineH+lp.lineSpacing); }
1090 lineStartY += lineH+lp.lineSpacing;
1092 } else {
1093 // layout vertical box, it is much easier
1095 // setup vars
1096 //flexTotal = 0;
1097 //flexBoxCount = 0;
1098 //curSpc = 0;
1099 spaceLeft = lp.position.h-(bpadTop+bpadBottom);
1101 // calculate flex variables
1102 cidx = lp.firstChild;
1103 for (;;) {
1104 auto clp = layprops(cidx);
1105 if (clp is null) break;
1106 auto dim = clp.position.h+curSpc;
1107 spaceLeft -= dim;
1108 // process flex
1109 if (clp.flex > 0) { flexTotal += clp.flex; ++flexBoxCount; }
1110 curSpc = bspc;
1111 cidx = clp.nextSibling;
1114 // distribute flex space, fix coordinates
1115 cidx = lp.firstChild;
1116 float flt = cast(float)flexTotal;
1117 float left = cast(float)spaceLeft;
1118 int curpos = bpadTop;
1119 for (;;) {
1120 auto clp = layprops(cidx);
1121 if (clp is null) break;
1122 // fix packing coordinate
1123 clp.position.y = curpos;
1124 bool doChildrenRelayout = false;
1125 // fix non-packing coordinate (and, maybe, non-packing dimension)
1126 // fix x coord
1127 final switch (clp.aligning) {
1128 case FuiLayoutProps.Align.Start: clp.position.x = bpadLeft; break;
1129 case FuiLayoutProps.Align.End: clp.position.x = lp.position.w-bpadRight-clp.position.w; break;
1130 case FuiLayoutProps.Align.Center: clp.position.x = (lp.position.w-clp.position.w)/2; break;
1131 case FuiLayoutProps.Align.Stretch:
1132 int nd = min(max(0, lp.position.w-(bpadLeft+bpadRight), clp.minSize.w), clp.maxSize.w);
1133 if (nd != clp.position.w) {
1134 // size changed, relayout children
1135 doChildrenRelayout = true;
1136 clp.position.w = nd;
1138 clp.position.x = bpadLeft;
1139 break;
1141 // fix flexbox size
1142 if (clp.flex > 0) {
1143 int toadd = cast(int)(left*cast(float)clp.flex/flt);
1144 if (toadd > 0) {
1145 // size changed, relayout children
1146 doChildrenRelayout = true;
1147 clp.position.hp += toadd;
1150 // advance packing coordinate
1151 curpos += clp.position.h+bspc;
1152 // relayout children if dimensions was changed
1153 if (doChildrenRelayout) layit(cidx);
1154 cidx = clp.nextSibling;
1156 // that's all for vertical boxes
1160 // main code
1161 if (ctxp == 0 || length < 1) return;
1163 resetValues();
1165 // do top-level packing
1166 layit(0);
1167 bool resetTouched = false;
1168 for (;;) {
1169 enum FixGroupsMixin(string group, string pkdim) = "{\n"~
1170 "int gidx = "~group~"Last;\n"~
1171 "while (layprops(gidx) !is null) {\n"~
1172 " int dim = 1;\n"~
1173 " int cidx = gidx;\n"~
1174 " // calcluate maximal dimension\n"~
1175 " for (;;) {\n"~
1176 " auto clp = layprops(cidx);\n"~
1177 " if (clp is null) break;\n"~
1178 " dim = max(dim, clp.position."~pkdim~");\n"~
1179 " if (clp."~group~"Sibling == -1) break;\n"~
1180 " cidx = clp."~group~"Sibling&0x7fff_ffff;\n"~
1181 " }\n"~
1182 " // fix dimensions\n"~
1183 " cidx = gidx;\n"~
1184 " for (;;) {\n"~
1185 " auto clp = layprops(cidx);\n"~
1186 " if (clp is null) break;\n"~
1187 " auto od = clp.position."~pkdim~";\n"~
1188 " clp.position."~pkdim~" = max(clp.position."~pkdim~", dim);\n"~
1189 " if (clp.maxSize."~pkdim~" > 0) clp.position."~pkdim~" = min(clp.position."~pkdim~", clp.maxSize."~pkdim~");\n"~
1190 " if (clp.position."~pkdim~" != od) {\n"~
1191 " doFix = true;\n"~
1192 " if (clp.parent == 0) doItAgain = true;\n"~
1193 " }\n"~
1194 " if (clp."~group~"Sibling == -1) break;\n"~
1195 " cidx = clp."~group~"Sibling&0x7fff_ffff;\n"~
1196 " }\n"~
1197 " // process next group\n"~
1198 " gidx = layprops(gidx)."~group~"Next;\n"~
1199 "}\n"~
1200 "}\n";
1201 bool doItAgain = false;
1202 bool doFix = false;
1203 // fix groups
1204 mixin(FixGroupsMixin!("hGroup", "w"));
1205 mixin(FixGroupsMixin!("vGroup", "h"));
1206 if (!doFix && !doItAgain) break; // nothing to do
1207 // reset "group touched" flag, if necessary
1208 if (resetTouched) {
1209 foreach (int idx; 0..length) layprops(idx).touchedByGroup = false;
1210 } else {
1211 resetTouched = true;
1213 // if we need to fix some parts of the layout, do it
1214 if (doFix) {
1215 foreach (int idx; 0..length) {
1216 auto lp = layprops(idx);
1217 version(fui_many_asserts) assert(lp !is null);
1218 if (lp.hGroupSibling != -1 || lp.vGroupSibling != -1) {
1219 int pidx = lp.parent;
1220 lp = layprops(pidx);
1221 version(fui_many_asserts) assert(lp !is null);
1222 if (!lp.touchedByGroup) {
1223 lp.touchedByGroup = true;
1224 layit(pidx);
1229 if (!doItAgain) break;
1233 debug void dumpLayout () const {
1234 import core.stdc.stdio : printf;
1236 static void ind (int indent) { foreach (immutable _; 0..indent) printf(" "); }
1238 void dumpItem (int idx, int indent) {
1239 auto lp = layprops(idx);
1240 if (lp is null) return;
1241 ind(indent);
1242 printf("Ctl#%d: position:(%d,%d); size:(%d,%d)\n", idx, lp.position.x, lp.position.y, lp.position.w, lp.position.h);
1243 idx = lp.firstChild;
1244 for (;;) {
1245 lp = layprops(idx);
1246 if (lp is null) break;
1247 dumpItem(idx, indent+2);
1248 idx = lp.nextSibling;
1252 dumpItem(0, 0);
1255 debug void dumpLayoutBack () const {
1256 import core.stdc.stdio : printf;
1258 static void ind (int indent) { foreach (immutable _; 0..indent) printf(" "); }
1260 void dumpItem (int idx, int indent) {
1261 auto lp = layprops(idx);
1262 if (lp is null) return;
1263 ind(indent);
1264 printf("Ctl#%d: position:(%d,%d); size:(%d,%d)\n", idx, lp.position.x, lp.position.y, lp.position.w, lp.position.h);
1265 idx = lp.lastChild;
1266 for (;;) {
1267 lp = layprops(idx);
1268 if (lp is null) break;
1269 dumpItem(idx, indent+2);
1270 idx = lp.prevSibling;
1274 dumpItem(0, 0);
1277 // -1 or item id, iterating items backwards (so last drawn will be first hit)
1278 int itemAt (FuiPoint pt) { pragma(inline, true); return (ctxp ? ctx.itemAt(pt) : -1); }
1280 // return id or -1
1281 // contrary to what you may think, `id` itself will be checked too
1282 int findNext (int id, scope bool delegate (int id) nothrow @nogc check) { pragma(inline, true); return (ctxp ? ctx.findNext(id, check) : -1); }
1284 // -1: none
1285 @property int focused () const { pragma(inline, true); return (ctxp ? ctx.focusedId : -1); }
1286 @property void focused (int id) { pragma(inline, true); if (ctxp) ctx.focused = id; }
1288 // external actions
1289 void keyboardEvent (KeyEvent ev) { pragma(inline, true); if (ctxp) ctx.keyboardEvent(ev); }
1290 void charEvent (dchar ch) { pragma(inline, true); if (ctxp) ctx.charEvent(ch); }
1291 void mouseEvent (MouseEvent ev) { pragma(inline, true); if (ctxp) ctx.mouseEvent(ev); }
1293 // don't pass anything to automatically calculate update delta
1294 void update (int msecDelta=-1) { pragma(inline, true); if (ctxp) ctx.update(msecDelta); }
1296 void queueEvent (int aitem, FuiEvent.Type atype, uint aparam0=0, uint aparam1=0) nothrow @trusted @nogc { pragma(inline, true); if (ctxp) ctx.queueEvent(aitem, atype, aparam0, aparam1); }
1297 bool hasEvents () const nothrow @trusted @nogc { pragma(inline, true); return (ctxp ? ctx.hasEvents() : false); }
1298 FuiEvent getEvent () nothrow @trusted @nogc { pragma(inline, true); return (ctxp ? ctx.getEvent() : FuiEvent.init); }
1300 @property NVGContext vg () nothrow @trusted @nogc { pragma(inline, true); return (ctxp ? ctx.vgc : null); }
1301 @property void vg (NVGContext v) nothrow @trusted @nogc { pragma(inline, true); if (ctxp) ctx.vgc = v; }
1303 @property ubyte lastButtons () nothrow @trusted @nogc { pragma(inline, true); return (ctxp ? ctx.lastButtons : 0); }
1304 @property ubyte lastMods () nothrow @trusted @nogc { pragma(inline, true); return (ctxp ? ctx.lastMods : 0); }