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
;
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
{
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
{
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
{
75 // "NPD" means "non-packing direction"
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
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
119 int parent
= -1; // item parent
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
); }
138 FuiSize maxSize
= FuiSize(int.max
-1024, int.max
-1024); // arbitrary limit, you know
140 int spacing
; // spacing for children
141 int lineSpacing
; // line spacing for horizontal boxes
143 enum Buttons
: ubyte {
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"));
159 UserFlagsMask
= 0xffffu
,
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,
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
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"~
196 " // append to group\n"~
197 " auto it = lp."~gvar
~"Sibling&0x7fff_ffff;\n"~
198 " version(fui_many_asserts) assert(it != 0x7fff_ffff);\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"~
206 " it = clp."~gvar
~"Sibling;\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
{
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
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
{
257 enum MaxQueuedEvents
= 16;
258 enum MaxQueuedExternalEvents
= 64;
261 uint rc
= 1; // refcount
262 ubyte* pmem
; // private memory: this holds controls
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
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
]) {
292 bool hasEvents () const pure nothrow @safe @nogc { pragma(inline
, true); return (eventPos
> 0); }
294 FuiEvent
getEvent () nothrow @trusted @nogc {
297 eventHead
= (eventHead
+1)%events
.length
;
299 return events
.ptr
[nn
];
301 return FuiEvent
.init
;
305 void newMousePos (FuiPoint pt
) {
306 import std
.math
: abs
;
307 if (lastMouse
== pt
) return;
308 int hover
= itemAt(pt
);
310 if (auto lp
= layprops(lastHover
)) lp
.hovered
= false;
312 if (auto lp
= layprops(hover
)) if (lp
.enabled
) lp
.hovered
= true;
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
];
328 foreach (immutable idx
, int lc
; lastClick
) {
329 if (idx
!= bidx
&& lc
== i
) return;
331 layprops(i
).active
= false;
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;
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;
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
]); }
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;
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
;
384 if (lastHover
== -1) {
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
;
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
]); }
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;
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"); }
413 if (lastClickDelta
.ptr
[bidx
] > fuiDoubleTime
) {
414 // reset double to single
415 beventCount
.ptr
[bidx
] = 1;
416 lastClickDelta
.ptr
[bidx
] = lastClickDelta
[0].max
;
419 beventCount
.ptr
[bidx
] = 3;
420 //lastClickDelta.ptr[bidx] = 0;
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;
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
]); }
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;
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
); }
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
); }
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;
471 private import core
.time
;
472 private MonoTime lastUpdateTime
;
473 private bool updateWasCalled
;
475 static struct ExternalEvent
{
476 enum Type
{ Key
, Char
, Mouse
}
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
;
510 auto ct
= MonoTime
.currTime
;
511 msecDelta
= cast(int)((ct
-lastUpdateTime
).total
!"msecs");
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
;
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
);
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
);
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; });
543 if (auto lc
= layprops(focused
)) {
544 if (lc
.canBeFocused
&& lc
.enabled
) queueEvent(focused
, FuiEvent
.Type
.Key
, cast(uint)extEvents
.ptr
[extevHead
].kev
.key
);
547 case ExternalEvent
.Type
.Mouse
:
548 auto ev
= &extEvents
.ptr
[extevHead
].mev
;
549 mouseAt(FuiPoint(ev
.x
, ev
.y
));
551 case MouseEventType
.buttonPressed
:
552 case MouseEventType
.buttonReleased
:
553 if (ev
.button
) newButtonState(cast(uint)ev
.button
-1, (ev
.type
== MouseEventType
.buttonPressed
));
555 case MouseEventType
.motion
:
556 //{ import std.stdio; writeln(ev.x, ",", ev.y); }
562 extevHead
= (extevHead
+1)%extEvents
.length
;
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;
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
);
595 @property int lastItemIndex () const pure { pragma(inline
, true); return pcount
-1; }
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
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");
612 pidxsize
= newsz
/cast(uint)(*pidx
).sizeof
;
614 version(fui_many_asserts
) assert(pidxsize
-pcount
> 0);
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 () {
637 @disable this (this); // no copies!
639 static void decRef (usize me
) {
641 auto nfo
= cast(FuiContextImpl
*)me
;
642 version(fui_many_asserts
) assert(nfo
.rc
);
644 import core
.stdc
.stdlib
: free
;
645 if (nfo
.pmem
!is null) {
646 import core
.memory
: GC
;
647 GC
.removeRange(nfo
.pmem
);
650 if (nfo
.pidx
!is null) free(nfo
.pidx
);
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;
671 // go to last sibling
672 debug(fui_item_at
) { import core
.stdc
.stdio
: printf
; printf("startsib: %d\n", id
); }
674 auto lp
= layprops(id
);
675 version(fui_many_asserts
) assert(lp
!is null);
676 if (lp
.nextSibling
== -1) break;
679 debug(fui_item_at
) { import core
.stdc
.stdio
: printf
; printf("lastsib: %d\n", id
); }
680 // check all siblings from the last one
682 auto lp
= layprops(id
);
683 if (lp
is null) return -1;
685 auto rc
= lp
.position
;
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!
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
705 return check(0, layprops(0).position
.pos
);
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;
713 auto lp
= layprops(id
);
714 if (lp
is null) return -1;
715 if (lp
.firstChild
!= -1) {
716 // has children, descent
720 // no children, check ourself
721 if (check(id
)) return id
;
722 // go to next sibling
724 if (lp
.nextSibling
!= -1) { id
= lp
.nextSibling
; break; }
725 // no sibling, bubble and get next sibling
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*!
742 static assert(usize
.sizeof
>= (void*).sizeof
);
745 usize ctxp
; // hide from GC
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
; }
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
;
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
);
766 res
.ctxp
= cast(usize
)ct
;
768 res
.ctx
.addItem
!FuiLayoutProps();
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
) {
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
787 // assigning empty context
788 FuiContextImpl
.decRef(ctxp
);
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");
806 // add layouter properties
807 ctx
.addItem
!FuiLayoutProps();
808 auto clp
= layprops(cidx
);
812 version(fui_many_asserts
) assert(clp
.prevSibling
== -1);
813 version(fui_many_asserts
) assert(clp
.nextSibling
== -1);
814 auto pp
= layprops(parent
);
816 clp
.prevSibling
= pp
.lastChild
;
817 if (pp
.firstChild
== -1) {
819 version(fui_many_asserts
) assert(pp
.lastChild
== -1);
820 pp
.firstChild
= pp
.lastChild
= cidx
;
822 version(fui_many_asserts
) assert(pp
.lastChild
!= -1);
823 layprops(pp
.lastChild
).nextSibling
= cidx
;
832 // this *WILL* *NOT* call item dtors!
834 pragma(inline
, true);
838 ctx
.addItem
!FuiLayoutProps();
842 // this will clear only controls, use with care!
843 void clearControls () {
844 pragma(inline
, true);
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
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"~
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
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);
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
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
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
);
937 // flush current line
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;
945 maxW
= max(maxW
, curWidth
);
947 maxH
+= lineH
+(lineCount ? lp
.lineSpacing
: 0);
949 curWidth
= bpadLeft
+bpadRight
;
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
);
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
970 // flush current line
972 // and add this item to it
973 addToLine(clp
, cidx
);
977 // layout children, insert "soft" line breaks
978 int cidx
= lp
.firstChild
;
980 auto clp
= layprops(cidx
);
981 if (clp
is null) break;
982 layit(cidx
); // layout children of this box
984 // for horizontal box, logic is somewhat messy
986 if (clp
.flags
&clp
.Flags
.LineBreak
) flushLine();
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);
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
;
1008 // layout horizontal box; we should do this for each line separately
1009 int lineStartY
= bpadTop
;
1015 spaceLeft
= lp
.position
.w
-(bpadLeft
+bpadRight
);
1019 int lstart
= lp
.firstChild
;
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);
1029 auto clp
= layprops(cidx
);
1030 if (clp
is null) break;
1031 auto dim
= clp
.position
.w
+curSpc
;
1033 lineH
= max(lineH
, clp
.position
.h
);
1035 if (clp
.flex
> 0) { flexTotal
+= clp
.flex
; ++flexBoxCount
; }
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
); }
1046 float flt
= cast(float)flexTotal
;
1047 float left
= cast(float)spaceLeft
;
1048 int curpos
= bpadLeft
;
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)
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
;
1073 int toadd
= cast(int)(left
*cast(float)clp
.flex
/flt
);
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
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
;
1093 // layout vertical box, it is much easier
1099 spaceLeft
= lp
.position
.h
-(bpadTop
+bpadBottom
);
1101 // calculate flex variables
1102 cidx
= lp
.firstChild
;
1104 auto clp
= layprops(cidx
);
1105 if (clp
is null) break;
1106 auto dim
= clp
.position
.h
+curSpc
;
1109 if (clp
.flex
> 0) { flexTotal
+= clp
.flex
; ++flexBoxCount
; }
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
;
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)
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
;
1143 int toadd
= cast(int)(left
*cast(float)clp
.flex
/flt
);
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
1161 if (ctxp
== 0 || length
< 1) return;
1165 // do top-level packing
1167 bool resetTouched
= false;
1169 enum FixGroupsMixin(string group
, string pkdim
) = "{\n"~
1170 "int gidx = "~group
~"Last;\n"~
1171 "while (layprops(gidx) !is null) {\n"~
1173 " int cidx = gidx;\n"~
1174 " // calcluate maximal dimension\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"~
1182 " // fix dimensions\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"~
1192 " if (clp.parent == 0) doItAgain = true;\n"~
1194 " if (clp."~group
~"Sibling == -1) break;\n"~
1195 " cidx = clp."~group
~"Sibling&0x7fff_ffff;\n"~
1197 " // process next group\n"~
1198 " gidx = layprops(gidx)."~group
~"Next;\n"~
1201 bool doItAgain
= false;
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
1209 foreach (int idx
; 0..length
) layprops(idx
).touchedByGroup
= false;
1211 resetTouched
= true;
1213 // if we need to fix some parts of the layout, do it
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;
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;
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
;
1246 if (lp
is null) break;
1247 dumpItem(idx
, indent
+2);
1248 idx
= lp
.nextSibling
;
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;
1264 printf("Ctl#%d: position:(%d,%d); size:(%d,%d)\n", idx
, lp
.position
.x
, lp
.position
.y
, lp
.position
.w
, lp
.position
.h
);
1268 if (lp
is null) break;
1269 dumpItem(idx
, indent
+2);
1270 idx
= lp
.prevSibling
;
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); }
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); }
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
; }
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); }