1 /* Invisible Vector Library
2 * simple FlexBox-based TUI engine
4 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
5 * Understanding is not required. Only obedience.
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, version 3 of the License ONLY.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 module iv
.egtui
.layout
/*is aliced*/;
25 import iv
.egtui
.types
;
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 {
152 enum Button
: ubyte {
160 ubyte clickMask
; // buttons that can be used to click this item to do some action
161 ubyte doubleMask
; // buttons that can be used to double-click this item to do some action
163 @property void hgroup (int parent
) nothrow @safe @nogc { setGroup(Group
.H
, parent
); }
164 @property void vgroup (int parent
) nothrow @safe @nogc { setGroup(Group
.V
, parent
); }
169 UserFlagsMask
= 0xffffu
,
171 Disabled
= 0x0001_0000u, // this item is dimed
172 LineBreak
= 0x0002_0000u, // layouter should start a new line after this item
173 Invisible
= 0x0004_0000u, // this item is used purely for layouting purposes
174 Hovered
= 0x0008_0000u, // this item is hovered
175 CanBeFocused
= 0x0010_0000u, // this item can be focused
176 Active
= 0x0020_0000u, // mouse is pressed on this
177 WantTab
= 0x0040_0000u, // want to receive tab key events
178 WantReturn
= 0x0080_0000u, // want to receive return key events
179 // internal flags for layouter
180 TempLineBreak
= 0x1000_0000u,
181 TouchedByGroup
= 0x2000_0000u,
188 enum Group
{ H
= 0, V
= 1 }
190 uint flags
= Flags
.None
; // see Flags
191 // "mark counter" for groups; also, bit 31 set means "group head"
192 int[2] groupNext
= -1; // next group head
193 int[2] groupSibling
= -1; // next item in this hgroup; not -1 and bit 31 set: head
196 void setGroup (int grp
, int parent
) nothrow @trusted @nogc {
197 if (ctx
is null || itemid
< 0 || parent
== itemid || grp
< 0 || grp
> 1) return;
198 auto lp
= ctx
.layprops(parent
);
199 if (lp
is null) return; //assert(0, "invalid parent for group");
200 if (lp
.groupSibling
.ptr
[grp
] == -1) {
201 // first item in new group
202 lp
.groupSibling
.ptr
[grp
] = itemid|
0x8000_0000;
205 auto it
= lp
.groupSibling
.ptr
[grp
]&0x7fff_ffff
;
206 version(fui_many_asserts
) assert(it
!= 0x7fff_ffff
);
208 auto clp
= ctx
.layprops(it
);
209 version(fui_many_asserts
) assert(clp
!is null);
210 if (clp
.groupSibling
.ptr
[grp
] == -1) {
211 clp
.groupSibling
.ptr
[grp
] = itemid
;
214 it
= clp
.groupSibling
.ptr
[grp
];
219 @property pure nothrow @safe @nogc {
220 void resetLayouterFlags () { pragma(inline
, true); flags
&= ~Flags
.LayouterFlagsMask
; }
222 bool tempLineBreak () const { pragma(inline
, true); return ((flags
&Flags
.TempLineBreak
) != 0); }
223 void tempLineBreak (bool v
) { pragma(inline
, true); if (v
) flags |
= Flags
.TempLineBreak
; else flags
&= ~Flags
.TempLineBreak
; }
225 bool touchedByGroup () const { pragma(inline
, true); return ((flags
&Flags
.TouchedByGroup
) != 0); }
226 void touchedByGroup (bool v
) { pragma(inline
, true); if (v
) flags |
= Flags
.TouchedByGroup
; else flags
&= ~Flags
.TouchedByGroup
; }
228 // this is strictly internal thing
229 void hovered (bool v
) { pragma(inline
, true); if (v
) flags |
= Flags
.Hovered
; else flags
&= ~Flags
.Hovered
; }
230 void active (bool v
) { pragma(inline
, true); if (v
) flags |
= Flags
.Active
; else flags
&= ~Flags
.Active
; }
235 // ////////////////////////////////////////////////////////////////////////// //
236 public static struct FuiEvent
{
238 None
, // just in case
239 Char
, // param0: dchar; param1: mods&buttons
240 Key
, // paramkey: key
241 Click
, // mouse click; param0: buttton index; param1: mods&buttons
242 Double
, // mouse double-click; param0: buttton index; param1: mods&buttons
243 Close
, // close dialog with param0 as result
253 short mx
, my
; // coordinates *inside* item
258 @property pure nothrow @safe @nogc:
259 ref TtyEvent
keyp () @trusted { pragma(inline
, true); return paramkey
; }
260 TtyEvent
key () @trusted { pragma(inline
, true); return paramkey
; }
262 @property const pure nothrow @safe @nogc:
263 ubyte mods () { pragma(inline
, true); return cast(ubyte)(param1
>>8); }
264 ubyte buts () { pragma(inline
, true); return cast(ubyte)param1
; }
266 dchar ch () { pragma(inline
, true); return cast(dchar)param0
; }
267 ubyte bidx () { pragma(inline
, true); return cast(ubyte)param0
; }
268 short x () { pragma(inline
, true); return mx
; }
269 short y () { pragma(inline
, true); return my
; }
271 int result () { pragma(inline
, true); return param0
; }
275 // ////////////////////////////////////////////////////////////////////////// //
276 // all controls lives here! ;-)
277 private struct FuiContextImpl
{
279 enum MaxQueuedEvents
= 16;
280 enum MaxQueuedExternalEvents
= 64;
283 uint rc
= 1; // refcount
284 ubyte* pmem
; // private memory: this holds controls
287 uint* pidx
; // this will hold offset of each item in `pmem`
288 uint pidxsize
; // in elements
289 int pcount
; // number of controls in this context
290 int focusedId
= -1; // what item is focused (i.e. will receive keyboard events)?
291 int lastHover
= -1; // for speed
292 ubyte lastButtons
, lastMods
;
293 FuiPoint lastMouse
= FuiPoint(-1, -1); // last mouse coordinates
294 short[8] lastClickDelta
= short.max
; // how much time passed since last click with the given button was registered?
295 int[8] lastClick
= -1; // on which item it was registered?
296 ubyte[8] beventCount
; // oooh...
297 FuiEvent
[MaxQueuedEvents
] events
;
298 uint eventHead
, eventPos
;
299 FuiSize mMaxDimensions
;
303 // contrary to what you may think, `id` itself will be checked too
304 int findNextEx (int id
, scope bool delegate (int id
) check
) {
305 if (id
== -1) id
= 0;
307 auto lp
= layprops(id
);
308 if (lp
is null) return -1;
309 if (lp
.firstChild
!= -1) {
310 // has children, descent
314 // no children, check ourself
315 if (check(id
)) return id
;
316 // go to next sibling
318 if (lp
.nextSibling
!= -1) { id
= lp
.nextSibling
; break; }
319 // no sibling, bubble and get next sibling
322 if (lp
is null) return -1;
329 void queueEvent (int aitem
, FuiEvent
.Type atype
, uint aparam0
=0, uint aparam1
=0, uint aparam2
=0) nothrow @trusted @nogc {
330 if (eventPos
>= events
.length
) return;
331 auto nn
= (eventHead
+eventPos
++)%events
.length
;
332 with (events
.ptr
[nn
]) {
337 mx
= cast(short)(aparam2
&0xffff);
338 my
= cast(short)((aparam2
>>16)&0xffff);
342 void queueEvent (int aitem
, FuiEvent
.Type atype
, TtyEvent akey
) nothrow @trusted @nogc {
343 if (eventPos
>= events
.length
) return;
344 auto nn
= (eventHead
+eventPos
++)%events
.length
;
345 with (events
.ptr
[nn
]) {
352 bool hasEvents () const pure nothrow @safe @nogc { pragma(inline
, true); return (eventPos
> 0); }
354 FuiEvent
getEvent () nothrow @trusted @nogc {
357 eventHead
= (eventHead
+1)%events
.length
;
359 return events
.ptr
[nn
];
361 return FuiEvent
.init
;
366 FuiPoint
toGlobal (int item
, FuiPoint pt
) {
368 if (auto lp
= layprops(item
)) {
369 pt
.x
+= lp
.position
.x
;
370 pt
.y
+= lp
.position
.y
;
371 if (item
== 0) break;
378 uint mouseXY (int item
) {
379 auto pt
= toGlobal(item
, FuiPoint(0, 0));
380 pt
= FuiPoint(lastMouse
.x
-pt
.x
, lastMouse
.y
-pt
.y
);
381 if (pt
.x
< short.min
) pt
.x
= short.min
;
382 if (pt
.x
> short.max
) pt
.x
= short.max
;
383 if (pt
.y
< short.min
) pt
.y
= short.min
;
384 if (pt
.y
> short.max
) pt
.y
= short.max
;
385 return cast(uint)(((pt
.y
&0xffff)<<16)|
(pt
.x
&0xffff));
389 void newButtonState (uint bidx
, bool down
) {
390 // 0: nothing was pressed or released yet
391 // 1: button was pressed for the first time
392 // 2: button was released for the first time
393 // 3: button was pressed for the second time
394 // 4: button was released for the second time
396 //debug(fui_mouse) { import core.stdc.stdio : printf; printf("NBS: bidx=%u; down=%d\n", bidx, cast(int)down); }
398 void resetActive() () {
399 auto i
= lastClick
.ptr
[bidx
];
401 foreach (immutable idx
, int lc
; lastClick
) {
402 if (idx
!= bidx
&& lc
== i
) return;
404 layprops(i
).active
= false;
407 void doRelease() () {
409 // did we released the button on the same control we pressed it?
410 if (beventCount
.ptr
[bidx
] == 0 || lastHover
== -1 ||
(lastHover
!= lastClick
.ptr
[bidx
])) {
411 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
]); }
412 // no, this is nothing, reset all info
413 lastClick
.ptr
[bidx
] = -1;
414 beventCount
.ptr
[bidx
] = 0;
417 auto lp
= layprops(lastHover
);
418 // yep, check which kind of event this is
419 if (beventCount
.ptr
[bidx
] == 3 && (lp
.doubleMask
&(1<<bidx
)) != 0) {
420 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
]); }
421 // we accepts doubleclicks, and this can be doubleclick
422 if (lastClickDelta
.ptr
[bidx
] <= fuiDoubleTime
) {
423 debug(fui_mouse
) { import core
.stdc
.stdio
: printf
; printf(" DOUBLE!\n"); }
424 // it comes right in time too
425 queueEvent(lastHover
, FuiEvent
.Type
.Double
, bidx
, lastButtons|
(lastMods
<<8), mouseXY(lastHover
));
426 // continue registering doubleclicks
427 lastClickDelta
.ptr
[bidx
] = 0;
428 beventCount
.ptr
[bidx
] = 2;
431 debug(fui_mouse
) { import core
.stdc
.stdio
: printf
; printf(" not double\n"); }
432 // this is invalid doubleclick, revert to simple click
433 beventCount
.ptr
[bidx
] = 1;
434 // start registering doubleclicks
435 lastClickDelta
.ptr
[bidx
] = 0;
437 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
]); }
439 if (beventCount
.ptr
[bidx
] == 1) {
440 debug(fui_mouse
) { import core
.stdc
.stdio
: printf
; printf(" SINGLE\n"); }
441 if (lp
.clickMask
&(1<<bidx
)) queueEvent(lastHover
, FuiEvent
.Type
.Click
, bidx
, lastButtons|
(lastMods
<<8), mouseXY(lastHover
));
442 // start doubleclick timer
443 beventCount
.ptr
[bidx
] = 2;
444 // start registering doubleclicks
445 lastClickDelta
.ptr
[bidx
] = 0;
448 debug(fui_mouse
) { import core
.stdc
.stdio
: printf
; printf(" UNEXPECTED\n"); }
449 // something unexpected, reset it all
450 lastClick
.ptr
[bidx
] = -1;
451 beventCount
.ptr
[bidx
] = 0;
452 lastClickDelta
.ptr
[bidx
] = lastClickDelta
[0].max
;
457 if (lastHover
== -1) {
459 debug(fui_mouse
) { import core
.stdc
.stdio
: printf
; printf("button #%u pressed at nowhere\n", bidx
); }
460 lastClick
.ptr
[bidx
] = -1;
461 beventCount
.ptr
[bidx
] = 0;
462 lastClickDelta
.ptr
[bidx
] = lastClickDelta
[0].max
;
466 if (beventCount
.ptr
[bidx
] == 0) {
467 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
]); }
469 lastClick
.ptr
[bidx
] = lastHover
;
470 beventCount
.ptr
[bidx
] = 1;
471 lastClickDelta
.ptr
[bidx
] = lastClickDelta
[0].max
;
472 auto lp
= layprops(lastHover
);
473 version(fui_many_asserts
) assert(lp
!is null);
474 if (lp
.canBeFocused
) focused
= lastHover
;
475 if ((lp
.clickMask
&(1<<bidx
)) != 0) lp
.active
= true;
479 if (beventCount
.ptr
[bidx
] == 2) {
480 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
]); }
481 //bool asDouble = false;
482 // start double if control is the same
483 if (lastClick
.ptr
[bidx
] == lastHover
) {
484 debug(fui_mouse
) { import core
.stdc
.stdio
: printf
; printf(" SAME\n"); }
486 if (lastClickDelta
.ptr
[bidx
] > fuiDoubleTime
) {
487 // reset double to single
488 beventCount
.ptr
[bidx
] = 1;
489 lastClickDelta
.ptr
[bidx
] = lastClickDelta
[0].max
;
492 beventCount
.ptr
[bidx
] = 3;
493 //lastClickDelta.ptr[bidx] = 0;
496 debug(fui_mouse
) { import core
.stdc
.stdio
: printf
; printf(" OTHER\n"); }
497 // other, reset to "first press"
498 lastClick
.ptr
[bidx
] = lastHover
;
499 beventCount
.ptr
[bidx
] = 1;
500 lastClickDelta
.ptr
[bidx
] = lastClickDelta
[0].max
;
502 auto lp
= layprops(lastHover
);
503 version(fui_many_asserts
) assert(lp
!is null);
504 if (lp
.canBeFocused
) focused
= lastHover
;
505 if (((lp
.doubleMask|lp
.clickMask
)&(1<<bidx
)) != 0) lp
.active
= true;
508 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
]); }
510 // something unexpected, reset all
511 lastClick
.ptr
[bidx
] = -1;
512 beventCount
.ptr
[bidx
] = 0;
513 lastClickDelta
.ptr
[bidx
] = lastClickDelta
[0].max
;
516 if (bidx
>= lastClickDelta
.length
) return;
519 if ((lastButtons
&(1<<bidx
)) != 0) return; // state didn't changed
520 lastButtons |
= cast(ubyte)(1<<bidx
);
521 debug(fui_mouse
) { import core
.stdc
.stdio
: printf
; printf("DOWN: bidx=%u; buts=0x%02x\n", bidx
, lastButtons
); }
525 if ((lastButtons
&(1<<bidx
)) == 0) return; // state didn't changed
526 lastButtons
&= cast(ubyte)~(1<<bidx
);
527 debug(fui_mouse
) { import core
.stdc
.stdio
: printf
; printf("UP : bidx=%u; buts=0x%02x\n", bidx
, lastButtons
); }
533 void mouseAt (in FuiPoint pt
) {
534 if (lastMouse
!= pt
) {
536 auto nh
= itemAt(pt
);
537 if (nh
!= lastHover
) {
538 if (auto lp
= layprops(lastHover
)) lp
.hovered
= false;
539 if (auto lp
= layprops(nh
)) { if (lp
.enabled
) lp
.hovered
= true; else nh
= -1; } else nh
= -1;
545 private import core
.time
;
546 private MonoTime lastUpdateTime
;
547 private bool updateWasCalled
;
549 static struct ExternalEvent
{
550 enum Type
{ Key
, Char
, Mouse
}
556 private ExternalEvent
[MaxQueuedExternalEvents
] extEvents
;
557 uint extevHead
, extevPos
;
559 void keyboardEvent (TtyEvent ev
) nothrow @trusted @nogc {
560 if (extevPos
>= extEvents
.length
) return;
561 if (ev
.key
== TtyEvent
.Key
.None
) return;
562 auto nn
= (extevHead
+extevPos
++)%extEvents
.length
;
563 if (ev
.key
== TtyEvent
.Key
.Char
) {
564 with (extEvents
.ptr
[nn
]) { type
= ExternalEvent
.Type
.Char
; cev
= ev
.ch
; }
565 } else if (ev
.mouse
) {
566 with (extEvents
.ptr
[nn
]) { type
= ExternalEvent
.Type
.Mouse
; kev
= ev
; }
568 with (extEvents
.ptr
[nn
]) { type
= ExternalEvent
.Type
.Key
; kev
= ev
; }
572 void charEvent (dchar ch
) nothrow @trusted @nogc {
573 if (extevPos
>= extEvents
.length || ch
== 0 || ch
> dchar.max
) return;
574 auto nn
= (extevHead
+extevPos
++)%extEvents
.length
;
575 with (extEvents
.ptr
[nn
]) { type
= ExternalEvent
.Type
.Char
; cev
= ch
; }
579 void mouseEvent (MouseEvent ev) nothrow @trusted @nogc {
580 if (extevPos >= extEvents.length) return;
581 auto nn = (extevHead+extevPos++)%extEvents.length;
582 with (extEvents.ptr[nn]) { type = ExternalEvent.Type.Mouse; mev = ev; }
586 //FIXME: write `findPrev()`!
589 auto lfc
= layprops(focused
);
591 findNext(-1, (int item
) {
592 if (auto lc
= layprops(item
)) {
593 if (item
== focused
) return true;
594 if (lc
.canBeFocused
&& lc
.visible
&& !lc
.disabled
) prevIt
= item
;
600 findNext(-1, (int item
) {
601 if (auto lc
= layprops(item
)) {
602 if (lc
.canBeFocused
&& lc
.visible
&& !lc
.disabled
) prevIt
= item
;
611 auto lfc
= layprops(focused
);
613 focused
= findNext(0, (int item
) {
614 if (auto lc
= layprops(item
)) return (lc
.canBeFocused
&& lc
.visible
&& !lc
.disabled
);
617 } else if (!lfc
.wantTab || lfc
.disabled
) {
618 focused
= findNext(focused
, (int item
) {
619 if (auto lc
= layprops(item
)) return (item
!= focused
&& lc
.canBeFocused
&& lc
.visible
&& !lc
.disabled
);
622 if (focused
== -1) focused
= findNext(0, (int item
) {
623 if (auto lc
= layprops(item
)) return (lc
.canBeFocused
&& lc
.visible
&& !lc
.disabled
);
629 // don't pass anything to automatically calculate update delta
630 void update (int msecDelta
) {
631 if (!updateWasCalled
) {
632 updateWasCalled
= true;
633 lastUpdateTime
= MonoTime
.currTime
;
636 auto ct
= MonoTime
.currTime
;
637 msecDelta
= cast(int)((ct
-lastUpdateTime
).total
!"msecs");
640 lastUpdateTime
= MonoTime
.currTime
;
642 //assert(msecDelta >= 0);
643 foreach (ref ltm
; lastClickDelta
) {
644 if (ltm
>= 0 && ltm
< lastClickDelta
[0].max
) {
645 auto nt
= ltm
+msecDelta
;
646 if (nt
< 0 || nt
> lastClickDelta
[0].max
) nt
= lastClickDelta
[0].max
;
650 while (extevPos
> 0) {
651 final switch (extEvents
.ptr
[extevHead
].type
) {
652 case ExternalEvent
.Type
.Char
:
653 if (auto lc
= layprops(focused
)) {
654 if (lc
.canBeFocused
) queueEvent(focused
, FuiEvent
.Type
.Char
, cast(uint)extEvents
.ptr
[extevHead
].cev
);
657 case ExternalEvent
.Type
.Key
:
658 //if (!extEvents.ptr[extevHead].kev.pressed) break;
659 if (extEvents
.ptr
[extevHead
].kev
.key
== TtyEvent
.Key
.Tab
) {
660 auto kk
= extEvents
.ptr
[extevHead
].kev
;
661 if (auto lc
= layprops(focused
)) {
662 if (lc
.visible
&& !lc
.disabled
&& lc
.wantTab
) break;
664 if (!kk
.ctrl
&& !kk
.alt
&& !kk
.shift
) focusNext();
665 if (!kk
.ctrl
&& !kk
.alt
&& kk
.shift
) focusPrev();
668 if (auto lc
= layprops(focused
)) {
669 if (lc
.canBeFocused
&& lc
.enabled
) queueEvent(focused
, FuiEvent
.Type
.Key
, extEvents
.ptr
[extevHead
].kev
);
672 case ExternalEvent
.Type
.Mouse
:
673 auto ev
= &extEvents
.ptr
[extevHead
].kev
;
674 mouseAt(FuiPoint(ev
.x
, ev
.y
));
675 if (ev
.button
!= TtyEvent
.MButton
.None
) {
676 if (ev
.mpress || ev
.mrelease
) {
677 newButtonState(ev
.button
-TtyEvent
.MButton
.First
, ev
.mpress
);
678 } else if (ev
.mwheel
) {
680 newButtonState(ev
.button
-TtyEvent
.MButton
.First
, true);
681 newButtonState(ev
.button
-TtyEvent
.MButton
.First
, false);
686 case MouseEventType.buttonPressed:
687 case MouseEventType.buttonReleased:
688 if (ev.button) newButtonState(cast(uint)ev.button-1, (ev.type == MouseEventType.buttonPressed));
690 case MouseEventType.motion:
691 //{ import std.stdio; writeln(ev.x, ",", ev.y); }
698 extevHead
= (extevHead
+1)%extEvents
.length
;
704 // return current offset in allocation buffer
705 uint allocOfs () const pure @safe { pragma(inline
, true); return pmemused
; }
707 T
* structAtOfs(T
) (uint ofs
) @trusted {
708 if (ofs
>= pmemused || ofs
+T
.sizeof
> pmemused
) return null; // simple sanity check
709 return cast(T
*)(pmem
+ofs
);
712 // will align size to 8
713 T
* xcalloc(T
) (int addsize
=0) if (!is(T
== class) && T
.sizeof
<= 65536) {
714 if (addsize
< 0 || addsize
> 0x100_0000) assert(0, "Fui: WTF?!");
715 if (cast(long)pmemused
+T
.sizeof
+addsize
> 0x1000_0000) assert(0, "Fui context too big");
716 uint asz
= cast(uint)T
.sizeof
+addsize
;
717 if (asz
&0x07) asz
= (asz|
0x07)+1;
720 import core.stdc.stdio;
721 auto fo = fopen("zx01", "a");
722 fo.fprintf("realloc: used=%u; size=%u; asize=%u\n", pmemused, pmemsize, cast(uint)(T.sizeof));
726 if (pmemused
+asz
> pmemsize
) {
727 import core
.stdc
.stdlib
: realloc
;
728 import core
.memory
: GC
;
729 uint newsz
= pmemused
+asz
;
730 if (asz
<= 4096) newsz
+= asz
*16; // add more space for such controls
731 //if (newsz&0xfff) newsz = (newsz|0xfff)+1; // align to 4KB
732 if (newsz
&0x7fff) newsz
= (newsz|
0x7fff)+1; // align to 32KB
733 if (pmem
!is null) GC
.removeRange(pmem
);
734 auto v
= cast(ubyte*)realloc(pmem
, newsz
);
735 if (v
is null) assert(0, "out of memory for Fui context");
738 import core.stdc.stdio;
739 auto fo = fopen("zx01", "a");
740 fo.fprintf("realloc: oldused=%u; oldsize=%u; newsize=%u; oldp=%p; newp=%p\n", pmemused, pmemsize, newsz, pmem, v);
745 pmem
[pmemsize
..newsz
] = 0;
747 GC
.addRange(cast(void*)v
, newsz
, typeid(void*));
749 version(fui_many_asserts
) assert(pmemsize
-pmemused
>= asz
);
750 assert(pmemused
%8 == 0);
751 ubyte* res
= pmem
+pmemused
;
754 static if (is(T
== struct)) {
755 import core
.stdc
.string
: memcpy
;
756 static immutable T i
= T
.init
;
757 memcpy(res
, &i
, T
.sizeof
);
762 @property int lastItemIndex () const pure { pragma(inline
, true); return pcount
-1; }
765 @property int focused () const pure { pragma(inline
, true); return focusedId
; }
766 @property void focused (int id
) pure { pragma(inline
, true); focusedId
= (id
>= 0 && id
< pcount ? id
: -1); }
768 // add new item; set it's offset to current memtop; return pointer to allocated data
769 T
* addItem(T
) () if (!is(T
== class) && T
.sizeof
<= 65536) {
770 if (pcount
>= 65535) assert(0, "too many controls in Fui context"); // arbitrary limit
772 auto res
= xcalloc
!T();
773 if (pidxsize
-pcount
< 1) {
774 import core
.stdc
.stdlib
: realloc
;
775 uint newsz
= cast(uint)(*pidx
).sizeof
*pidxsize
+128; // make room for more controls
776 auto v
= realloc(pidx
, newsz
);
777 if (v
is null) assert(0, "out of memory for Fui context");
779 pidxsize
= newsz
/cast(uint)(*pidx
).sizeof
;
781 version(fui_many_asserts
) assert(pidxsize
-pcount
> 0);
791 lastClickDelta
[] = short.max
;
792 lastClick
[] = -1; // on which item it was registered?
793 eventHead
= eventPos
= 0;
796 // this will clear only controls, use with care!
797 void clearControls () {
799 import core
.stdc
.string
: memset
;
800 memset(pmem
, 0, pmemused
);
807 @disable this (this); // no copies!
809 static void decRef (usize me
) {
811 auto nfo
= cast(FuiContextImpl
*)me
;
812 version(fui_many_asserts
) assert(nfo
.rc
);
814 import core
.stdc
.stdlib
: free
;
815 if (nfo
.pmem
!is null) {
816 import core
.memory
: GC
;
817 GC
.removeRange(nfo
.pmem
);
820 if (nfo
.pidx
!is null) free(nfo
.pidx
);
826 @property int length () pure const nothrow @safe @nogc { pragma(inline
, true); return pcount
; }
828 inout(FuiLayoutProps
)* layprops (int idx
) inout {
829 pragma(inline
, true);
830 return (idx
>= 0 && idx
< length ?
cast(typeof(return))(pmem
+pidx
[idx
]) : null);
833 // -1 or item id, iterating items backwards (so last drawn will be first hit)
835 int itemAt (FuiPoint pt
) {
836 int check() (int id
, FuiPoint g
) {
837 // go to last sibling
838 debug(fui_item_at
) { import core
.stdc
.stdio
: printf
; printf("startsib: %d\n", id
); }
840 auto lp
= layprops(id
);
841 version(fui_many_asserts
) assert(lp
!is null);
842 if (lp
.nextSibling
== -1) break;
845 debug(fui_item_at
) { import core
.stdc
.stdio
: printf
; printf("lastsib: %d\n", id
); }
846 // check all siblings from the last one
848 auto lp
= layprops(id
);
849 if (lp
is null) return -1;
851 auto rc
= lp
.position
;
856 if (lp
.firstChild
== -1) {
857 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
); }
858 return id
; // i found her!
860 debug(fui_item_at
) { import core
.stdc
.stdio
: printf
; printf("going down: fc=%d; lc=%d\n", lp
.firstChild
, lp
.lastChild
); }
861 auto res
= check(lp
.lastChild
, rc
.pos
);
862 return (res
!= -1 ? res
: id
); // i found her!
864 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
); }
867 // move to previous sibling
871 if (length
== 0 ||
!layprops(0).visible
) return -1;
872 return check(0, FuiPoint(0, 0));
876 // contrary to what you may think, `id` itself will be checked too
877 int findNext (int id
, scope bool delegate (int id
) nothrow @nogc check
) {
878 if (id
== -1) id
= 0;
880 auto lp
= layprops(id
);
881 if (lp
is null) return -1;
882 if (lp
.firstChild
!= -1) {
883 // has children, descent
887 // no children, check ourself
888 if (check(id
)) return id
;
889 // go to next sibling
891 if (lp
.nextSibling
!= -1) { id
= lp
.nextSibling
; break; }
892 // no sibling, bubble and get next sibling
895 if (lp
is null) return -1;
900 @property FuiSize
maxDimensions () const @trusted { pragma(inline
, true); return mMaxDimensions
; }
901 @property void maxDimensions (FuiSize v
) @trusted { pragma(inline
, true); mMaxDimensions
= v
; }
905 // ////////////////////////////////////////////////////////////////////////// //
906 // note that GC *WILL* *NOT* scan private context memory!
907 // also, item struct dtors/postblits *MAY* *NOT* *BE* *CALLED*!
909 static assert(usize
.sizeof
>= (void*).sizeof
);
912 usize ctxp
; // hide from GC
915 int findNextEx (int id
, scope bool delegate (int id
) check
) {
916 if (ctxp
== 0) return -1;
917 return ctx
.findNextEx(id
, check
);
922 inout(FuiContextImpl
)* ctx () inout { pragma(inline
, true); return cast(typeof(return))ctxp
; }
924 void decRef () { pragma(inline
, true); if (ctxp
) { FuiContextImpl
.decRef(ctxp
); ctxp
= 0; } }
925 void incRef () { pragma(inline
, true); if (ctxp
) ++(cast(FuiContextImpl
*)ctxp
).rc
; }
927 void addRootPanel () {
928 import iv
.egtui
.tui
: FuiCtlRootPanel
, FuiCtlType
;
929 assert(ctx
.length
== 0);
930 ctx
.addItem
!FuiLayoutProps();
931 auto lp
= ctx
.layprops(0);
942 lp
.maxSize
= ctx
.maxDimensions
;
943 if (lp
.maxSize
.w
< 1) lp
.maxSize
.w
= int.max
-1024; // arbitrary limit, you know
944 if (lp
.maxSize
.h
< 1) lp
.maxSize
.h
= int.max
-1024; // arbitrary limit, you know
946 ctx
.xcalloc
!FuiCtlRootPanel();
947 auto data
= itemIntr
!FuiCtlRootPanel(0);
948 data
.type
= FuiCtlType
.Box
;
952 // this will produce new context, ready to accept controls
953 static FuiContext
create () {
954 import core
.stdc
.stdlib
: malloc
;
955 import core
.stdc
.string
: memcpy
;
957 // each context always have top-level panel
958 auto ct
= cast(FuiContextImpl
*)malloc(FuiContextImpl
.sizeof
);
959 if (ct
is null) assert(0, "out of memory for Fui context");
960 static immutable FuiContextImpl i
= FuiContextImpl
.init
;
961 memcpy(ct
, &i
, FuiContextImpl
.sizeof
);
962 res
.ctxp
= cast(usize
)ct
;
968 // refcounting mechanics
969 this (in FuiContext csrc
) { ctxp
= csrc
.ctxp
; incRef(); }
970 ~this () { pragma(inline
, true); decRef(); }
971 this (this) { static if (__VERSION__
> 2071) pragma(inline
, true); incRef(); }
972 void opAssign (in FuiContext csrc
) {
974 // first increase refcounter for source
975 ++(cast(FuiContextImpl
*)csrc
.ctxp
).rc
;
976 // now decreare our refcounter
977 FuiContextImpl
.decRef(ctxp
);
978 // and copy source pointer
981 // assigning empty context
982 FuiContextImpl
.decRef(ctxp
);
988 @property int length () const { pragma(inline
, true); return (ctxp ? ctx
.length
: 0); }
989 alias opDollar
= length
;
991 @property bool valid () const { pragma(inline
, true); return (length
> 0); }
993 @property FuiSize
maxDimensions () const @trusted { pragma(inline
, true); return (ctxp ? ctx
.maxDimensions
: FuiSize
.init
); }
994 @property void maxDimensions (FuiSize v
) @trusted { pragma(inline
, true); if (ctxp
) ctx
.maxDimensions
= v
; }
996 // add new item; return pointer to allocated data
997 // in context implementation we place item data right after FuiLayoutProps
998 int addItem(T
) (int parent
=0, int addsize
=0) if (!is(T
== class) && T
.sizeof
<= 65536) {
999 if (ctxp
== 0) assert(0, "can't add item to uninitialized Fui context");
1000 if (length
== 0) assert(0, "invalid Fui context");
1002 if (parent
>= cidx
) assert(0, "invalid parent for Fui item");
1003 // add layouter properties
1004 ctx
.addItem
!FuiLayoutProps();
1005 auto clp
= layprops(cidx
);
1009 version(fui_many_asserts
) assert(clp
.prevSibling
== -1);
1010 version(fui_many_asserts
) assert(clp
.nextSibling
== -1);
1011 auto pp
= layprops(parent
);
1012 clp
.parent
= parent
;
1013 clp
.prevSibling
= pp
.lastChild
;
1014 if (pp
.firstChild
== -1) {
1016 version(fui_many_asserts
) assert(pp
.lastChild
== -1);
1017 pp
.firstChild
= pp
.lastChild
= cidx
;
1019 version(fui_many_asserts
) assert(pp
.lastChild
!= -1);
1020 layprops(pp
.lastChild
).nextSibling
= cidx
;
1021 pp
.lastChild
= cidx
;
1025 ctx
.xcalloc
!T(addsize
);
1029 // allocate structure, return pointer to it and offset
1030 T
* addStruct(T
) (out uint ofs
, int addsize
=0) if (is(T
== struct)) {
1031 if (ctxp
== 0) assert(0, "can't add struct to uninitialized Fui context");
1032 if (addsize
> 65536) assert(0, "structure too big");
1033 if (addsize
< 0) addsize
= 0;
1035 return ctx
.xcalloc
!T(addsize
);
1038 T
* structAtOfs(T
) (uint ofs
) @trusted if (is(T
== struct)) {
1039 pragma(inline
, true);
1040 return (ctxp ? ctx
.structAtOfs
!T(ofs
) : null);
1043 // this *WILL* *NOT* call item dtors!
1045 pragma(inline
, true);
1052 // this will clear only controls, use with care!
1053 void clearControls () {
1054 pragma(inline
, true);
1056 ctx
.clearControls();
1061 // copypaste to allow dmdfe to inline things
1062 package(iv
.egtui
) inout(T
)* itemIntr(T
) (int idx
) inout if (!is(T
== class)) {
1063 pragma(inline
, true);
1064 // size is aligned, so this static if
1065 static if (FuiLayoutProps
.sizeof
%8 != 0) {
1066 enum ofs
= ((cast(uint)FuiLayoutProps
.sizeof
)|
7)+1;
1068 enum ofs
= cast(uint)FuiLayoutProps
.sizeof
;
1070 return (ctxp
&& idx
>= 0 && idx
< length ?
cast(typeof(return))(ctx
.pmem
+ctx
.pidx
[idx
]+ofs
) : null);
1073 // copypaste to allow dmdfe to inline things
1074 inout(T
)* item(T
) (int idx
) inout if (!is(T
== class)) {
1075 pragma(inline
, true);
1076 // size is aligned, so this static if
1077 static if (FuiLayoutProps
.sizeof
%8 != 0) {
1078 enum ofs
= ((cast(uint)FuiLayoutProps
.sizeof
)|
7)+1;
1080 enum ofs
= cast(uint)FuiLayoutProps
.sizeof
;
1082 return (ctxp
&& idx
> 0 && idx
< length ?
cast(typeof(return))(ctx
.pmem
+ctx
.pidx
[idx
]+ofs
) : null);
1085 inout(FuiLayoutProps
)* layprops (int idx
) inout {
1086 pragma(inline
, true);
1087 return (ctxp
&& idx
>= 0 && idx
< length ?
cast(typeof(return))(ctx
.pmem
+ctx
.pidx
[idx
]) : null);
1090 // should be called after adding all controls, or when something was changed
1092 import std
.algorithm
: min
, max
;
1094 int[2] groupLast
= -1; // list tails
1096 void resetValues() () {
1097 // reset sizes and positions for all controls
1098 // also, find and fix hgroups and vgroups
1099 foreach (int idx
; 0..length
) {
1100 auto lp
= layprops(idx
);
1101 lp
.resetLayouterFlags();
1103 lp
.position
= lp
.position
.init
; // zero it out
1105 lp
.position
.size
= lp
.position
.size
.init
;
1107 // setup group lists
1108 foreach (immutable grp
; 0..2) {
1109 if (lp
.groupSibling
[grp
] != -1 && (cast(uint)(lp
.groupSibling
[grp
])&0x8000_0000)) {
1110 // group start, fix list
1111 lp
.groupNext
[grp
] = groupLast
[grp
];
1112 groupLast
[grp
] = idx
;
1117 { import core
.stdc
.stdio
: printf
; printf("hGroupLast=%d; vGroupLast=%d\n", groupLast
[0], groupLast
[1]); }
1118 for (int n
= groupLast
[0]; n
!= -1; n
= layprops(n
).groupNext
[0]) {
1119 import core
.stdc
.stdio
: printf
;
1120 printf("=== HGROUP #%d ===\n", n
);
1121 int id
= groupLast
[0];
1123 auto lp
= layprops(id
);
1124 if (lp
is null) break;
1125 printf(" item #%d\n", id
);
1126 if (lp
.groupSibling
[0] == -1) break;
1127 id
= lp
.groupSibling
[0]&0x7fff_ffff
;
1133 // layout children in this item
1134 // `spareGroups`: don't touch widget sizes for hv groups
1135 // `spareAll`: don't fix this widget's size
1136 void layit() (int topid
) {
1137 auto lp
= layprops(topid
);
1138 if (lp
is null) return;
1139 // if we do group relayouting, skip touched items
1142 immutable bpadLeft
= max(0, lp
.padding
.left
);
1143 immutable bpadRight
= max(0, lp
.padding
.right
);
1144 immutable bpadTop
= max(0, lp
.padding
.top
);
1145 immutable bpadBottom
= max(0, lp
.padding
.bottom
);
1146 immutable bspc
= max(0, lp
.spacing
);
1147 immutable hbox
= (lp
.orientation
== FuiLayoutProps
.Orientation
.Horizontal
);
1149 // widget can only grow, and while doing that, `maxSize` will be respected, so we don't need to fix it's size
1151 // layout children, insert line breaks, if necessary
1152 int curWidth
= bpadLeft
+bpadRight
, maxW
= bpadLeft
+bpadRight
, maxH
= bpadTop
+bpadBottom
;
1153 int lastCIdx
= -1; // last processed item for the current line
1154 int lineH
= 0; // for the current line
1156 //int lineStartIdx = 0;
1158 // unconditionally add current item to the current line
1159 void addToLine (FuiLayoutProps
* clp
, int cidx
) {
1160 clp
.tempLineBreak
= false;
1161 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)); }
1162 curWidth
+= clp
.position
.w
+(lastCIdx
!= -1 ? bspc
: 0);
1163 lineH
= max(lineH
, clp
.position
.h
);
1167 // flush current line
1169 if (lastCIdx
== -1) return;
1170 // mark last item as line break
1171 layprops(lastCIdx
).tempLineBreak
= true;
1172 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)); }
1173 //layprops(lineStartIdx).tempLineHeight = lineH;
1175 maxW
= max(maxW
, curWidth
);
1177 maxH
+= lineH
+(lineCount ? lp
.lineSpacing
: 0);
1179 curWidth
= bpadLeft
+bpadRight
;
1185 // put item, do line management
1186 void putItem (FuiLayoutProps
* clp
, int cidx
) {
1187 int nw
= curWidth
+clp
.position
.w
+(lastCIdx
!= -1 ? bspc
: 0);
1188 // do we neeed to start a new line?
1189 if (nw
<= (lp
.position
.w ? lp
.position
.w
: lp
.maxSize
.w
)) {
1190 // no, just put item into the current line
1191 addToLine(clp
, cidx
);
1194 // yes, check if we have at least one item in the current line
1195 if (lastCIdx
== -1) {
1196 // alas, no items in the current line, put one
1197 addToLine(clp
, cidx
);
1198 // and flush it immediately
1201 // flush current line
1203 // and add this item to it
1204 addToLine(clp
, cidx
);
1208 // layout children, insert "soft" line breaks
1209 int cidx
= lp
.firstChild
;
1211 auto clp
= layprops(cidx
);
1212 if (clp
is null) break;
1213 layit(cidx
); // layout children of this box
1215 // for horizontal box, logic is somewhat messy
1217 if (clp
.flags
&clp
.Flags
.LineBreak
) flushLine();
1219 // for vertical box, it is as easy as this
1220 clp
.tempLineBreak
= true;
1221 maxW
= max(maxW
, clp
.position
.w
+bpadLeft
+bpadRight
);
1222 maxH
+= clp
.position
.h
+(lineCount ? bspc
: 0);
1225 cidx
= clp
.nextSibling
;
1227 if (hbox
) flushLine(); // flush last list for horizontal box (it is safe to flush empty line)
1229 // grow box or clamp max size
1230 // but only if size is not defined; in other cases our size is changed by parent to fit in
1231 if (lp
.position
.w
== 0) lp
.position
.w
= min(max(0, lp
.minSize
.w
, maxW
), lp
.maxSize
.w
);
1232 if (lp
.position
.h
== 0) lp
.position
.h
= min(max(0, lp
.minSize
.h
, maxH
), lp
.maxSize
.h
);
1233 maxH
= lp
.position
.h
;
1234 maxW
= lp
.position
.w
;
1236 int flexTotal
; // total sum of flex fields
1237 int flexBoxCount
; // number of boxes
1238 int curSpc
; // "current" spacing in layout calculations (for bspc)
1242 // layout horizontal box; we should do this for each line separately
1243 int lineStartY
= bpadTop
;
1249 spaceLeft
= lp
.position
.w
-(bpadLeft
+bpadRight
);
1253 int lstart
= lp
.firstChild
;
1256 if (layprops(lstart
) is null) break;
1257 // calculate flex variables and line height
1258 --lineCount
; // so 0 will be "last line"
1259 version(fui_many_asserts
) assert(lineCount
>= 0);
1263 auto clp
= layprops(cidx
);
1264 if (clp
is null) break;
1265 auto dim
= clp
.position
.w
+curSpc
;
1267 lineH
= max(lineH
, clp
.position
.h
);
1269 if (clp
.flex
> 0) { flexTotal
+= clp
.flex
; ++flexBoxCount
; }
1270 if (clp
.tempLineBreak
) break; // no more in this line
1272 cidx
= clp
.nextSibling
;
1274 //spaceLeft += curSpc; // last control should not be "spaced after"
1275 if (lineCount
== 0) lineH
= max(lineH
, lp
.position
.h
-bpadBottom
-lineStartY
-lineH
);
1276 debug(fui_layout
) { import core
.stdc
.stdio
: printf
; printf("lineStartY=%d; lineH=%d\n", lineStartY
, lineH
); }
1278 // distribute flex space, fix coordinates
1279 debug(fui_layout
) { import core
.stdc
.stdio
: printf
; printf("flexTotal=%d; flexBoxCount=%d; spaceLeft=%d\n", flexTotal
, flexBoxCount
, spaceLeft
); }
1281 float flt
= cast(float)flexTotal
;
1282 float left
= cast(float)spaceLeft
;
1283 int curpos
= bpadLeft
;
1285 auto clp
= layprops(cidx
);
1286 if (clp
is null) break;
1287 // fix packing coordinate
1288 clp
.position
.x
= curpos
;
1289 bool doChildrenRelayout
= false;
1290 // fix non-packing coordinate (and, maybe, non-packing dimension)
1292 final switch (clp
.aligning
) {
1293 case FuiLayoutProps
.Align
.Start
: clp
.position
.y
= lineStartY
; break;
1294 case FuiLayoutProps
.Align
.End
: clp
.position
.y
= (lineStartY
+lineH
)-clp
.position
.h
; break;
1295 case FuiLayoutProps
.Align
.Center
: clp
.position
.y
= lineStartY
+(lineH
-clp
.position
.h
)/2; break;
1296 case FuiLayoutProps
.Align
.Stretch
:
1297 clp
.position
.y
= lineStartY
;
1298 int nd
= min(max(0, lineH
, clp
.minSize
.h
), clp
.maxSize
.h
);
1299 if (nd
!= clp
.position
.h
) {
1300 // size changed, relayout children
1301 doChildrenRelayout
= true;
1302 clp
.position
.h
= nd
;
1308 int toadd
= cast(int)(left
*cast(float)clp
.flex
/flt
+0.5);
1310 // size changed, relayout children
1311 doChildrenRelayout
= true;
1312 clp
.position
.wp
+= toadd
;
1313 // compensate (crudely) rounding errors
1314 if (toadd
> 1 && lp
.position
.w
-(curpos
+clp
.position
.w
) < 0) {
1315 clp
.position
.wp
-= 1;
1319 // advance packing coordinate
1320 curpos
+= clp
.position
.w
+bspc
;
1321 // relayout children if dimensions was changed
1322 if (doChildrenRelayout
) layit(cidx
);
1323 cidx
= clp
.nextSibling
;
1324 if (clp
.tempLineBreak
) break; // next line, please!
1326 // yep, move to next line
1328 debug(fui_layout
) { import core
.stdc
.stdio
: printf
; printf("lineStartY=%d; next lineStartY=%d\n", lineStartY
, lineStartY
+lineH
+lp
.lineSpacing
); }
1329 lineStartY
+= lineH
+lp
.lineSpacing
;
1332 // layout vertical box, it is much easier
1338 spaceLeft
= lp
.position
.h
-(bpadTop
+bpadBottom
);
1340 // calculate flex variables
1341 cidx
= lp
.firstChild
;
1343 auto clp
= layprops(cidx
);
1344 if (clp
is null) break;
1345 auto dim
= clp
.position
.h
+curSpc
;
1348 if (clp
.flex
> 0) { flexTotal
+= clp
.flex
; ++flexBoxCount
; }
1350 cidx
= clp
.nextSibling
;
1353 // distribute flex space, fix coordinates
1354 cidx
= lp
.firstChild
;
1355 float flt
= cast(float)flexTotal
;
1356 float left
= cast(float)spaceLeft
;
1357 int curpos
= bpadTop
;
1359 auto clp
= layprops(cidx
);
1360 if (clp
is null) break;
1361 // fix packing coordinate
1362 clp
.position
.y
= curpos
;
1363 bool doChildrenRelayout
= false;
1364 // fix non-packing coordinate (and, maybe, non-packing dimension)
1366 final switch (clp
.aligning
) {
1367 case FuiLayoutProps
.Align
.Start
: clp
.position
.x
= bpadLeft
; break;
1368 case FuiLayoutProps
.Align
.End
: clp
.position
.x
= lp
.position
.w
-bpadRight
-clp
.position
.w
; break;
1369 case FuiLayoutProps
.Align
.Center
: clp
.position
.x
= (lp
.position
.w
-clp
.position
.w
)/2; break;
1370 case FuiLayoutProps
.Align
.Stretch
:
1371 int nd
= min(max(0, lp
.position
.w
-(bpadLeft
+bpadRight
), clp
.minSize
.w
), clp
.maxSize
.w
);
1372 if (nd
!= clp
.position
.w
) {
1373 // size changed, relayout children
1374 doChildrenRelayout
= true;
1375 clp
.position
.w
= nd
;
1377 clp
.position
.x
= bpadLeft
;
1382 int toadd
= cast(int)(left
*cast(float)clp
.flex
/flt
);
1384 // size changed, relayout children
1385 doChildrenRelayout
= true;
1386 clp
.position
.hp
+= toadd
;
1389 // advance packing coordinate
1390 curpos
+= clp
.position
.h
+bspc
;
1391 // relayout children if dimensions was changed
1392 if (doChildrenRelayout
) layit(cidx
);
1393 cidx
= clp
.nextSibling
;
1395 // that's all for vertical boxes
1400 if (ctxp
== 0 || length
< 1) return;
1404 // do top-level packing
1406 bool resetTouched
= false;
1408 bool doItAgain
= false;
1411 //FIXME: mark changed items and process only those
1412 void fixGroups (int grp
, scope int delegate (int item
) nothrow @nogc getdim
, scope void delegate (int item
, int v
) nothrow @nogc setdim
, scope int delegate (int item
) nothrow @nogc getmax
) nothrow @nogc {
1413 int gidx
= groupLast
[grp
];
1414 while (layprops(gidx
) !is null) {
1417 // calcluate maximal dimension
1419 auto clp
= layprops(cidx
);
1420 if (clp
is null) break;
1421 dim
= max(dim
, getdim(cidx
));
1422 if (clp
.groupSibling
[grp
] == -1) break;
1423 cidx
= clp
.groupSibling
[grp
]&0x7fff_ffff
;
1428 auto clp
= layprops(cidx
);
1429 if (clp
is null) break;
1430 auto od
= getdim(cidx
);
1431 int nd
= max(od
, dim
);
1432 auto mx
= getmax(cidx
);
1433 if (mx
> 0) nd
= min(nd
, mx
);
1437 if (clp
.parent
== 0) doItAgain
= true;
1439 if (clp
.groupSibling
[grp
] == -1) break;
1440 cidx
= clp
.groupSibling
[grp
]&0x7fff_ffff
;
1442 // process next group
1443 gidx
= layprops(gidx
).groupNext
[grp
];
1447 fixGroups(FuiLayoutProps
.Group
.H
,
1448 (int item
) => ctx
.layprops(item
).position
.w
,
1449 (int item
, int v
) { ctx
.layprops(item
).position
.wp
= v
; },
1450 (int item
) => ctx
.layprops(item
).maxSize
.w
,
1452 fixGroups(FuiLayoutProps
.Group
.V
,
1453 (int item
) => ctx
.layprops(item
).position
.h
,
1454 (int item
, int v
) { ctx
.layprops(item
).position
.wp
= v
; },
1455 (int item
) => ctx
.layprops(item
).maxSize
.h
,
1457 if (!doFix
&& !doItAgain
) break; // nothing to do
1458 // reset "group touched" flag, if necessary
1460 foreach (int idx
; 0..length
) layprops(idx
).touchedByGroup
= false;
1462 resetTouched
= true;
1465 // if we need to fix some parts of the layout, do it
1467 foreach (int grp
; 0..2) {
1468 int gidx
= groupLast
[grp
];
1469 //{ import core.stdc.stdio : printf; printf("grp=%d; gidx=%d\n", grp, groupLast[grp]); }
1470 while (layprops(gidx
) !is null) {
1472 while (layprops(it
) !is null) {
1473 //{ import core.stdc.stdio : printf; printf(" === it=%d ===\n", it); }
1476 auto lp
= layprops(itt
);
1477 if (lp
is null) break;
1478 //{ import core.stdc.stdio : printf; printf(" itt=%d\n", itt); }
1479 if (!lp
.touchedByGroup
) {
1480 lp
.touchedByGroup
= true;
1481 auto ow
= lp
.position
.w
, oh
= lp
.position
.h
;
1483 if (itt
!= it
&& ow
== lp
.position
.w
&& oh
== lp
.position
.h
) break;
1489 auto lp
= layprops(it
);
1490 if (lp
.groupSibling
[grp
] == -1) break;
1491 it
= lp
.groupSibling
[grp
]&0x7fff_ffff
;
1493 gidx
= layprops(gidx
).groupNext
[grp
];
1498 if (!doItAgain
) break;
1502 debug(tui_dump
) void dumpLayout () const {
1503 import core
.stdc
.stdio
: fopen
, fclose
, fprintf
;
1505 auto fo
= fopen("zlay.bin", "w");
1506 if (fo
is null) return;
1507 scope(exit
) fclose(fo
);
1509 void ind (int indent
) { foreach (immutable _
; 0..indent
) fo
.fprintf(" "); }
1511 void dumpItem (int idx
, int indent
) {
1512 auto lp
= layprops(idx
);
1513 if (lp
is null) return;
1515 fo
.fprintf("Ctl#%d: position:(%d,%d); size:(%d,%d)\n", idx
, lp
.position
.x
, lp
.position
.y
, lp
.position
.w
, lp
.position
.h
);
1516 idx
= lp
.firstChild
;
1519 if (lp
is null) break;
1520 dumpItem(idx
, indent
+2);
1521 idx
= lp
.nextSibling
;
1528 debug void dumpLayoutBack () const {
1529 import core
.stdc
.stdio
: printf
;
1531 static void ind (int indent
) { foreach (immutable _
; 0..indent
) printf(" "); }
1533 void dumpItem (int idx
, int indent
) {
1534 auto lp
= layprops(idx
);
1535 if (lp
is null) return;
1537 printf("Ctl#%d: position:(%d,%d); size:(%d,%d)\n", idx
, lp
.position
.x
, lp
.position
.y
, lp
.position
.w
, lp
.position
.h
);
1541 if (lp
is null) break;
1542 dumpItem(idx
, indent
+2);
1543 idx
= lp
.prevSibling
;
1550 FuiPoint
toGlobal (int item
, FuiPoint pt
) {
1551 return (ctxp ? ctx
.toGlobal(item
, pt
) : pt
);
1554 // -1 or item id, iterating items backwards (so last drawn will be first hit)
1555 int itemAt (FuiPoint pt
) { pragma(inline
, true); return (ctxp ? ctx
.itemAt(pt
) : -1); }
1558 // contrary to what you may think, `id` itself will be checked too
1559 int findNext (int id
, scope bool delegate (int id
) nothrow @nogc check
) { pragma(inline
, true); return (ctxp ? ctx
.findNext(id
, check
) : -1); }
1561 void focusPrev () { pragma(inline
, true); if (ctxp
) ctx
.focusPrev(); }
1562 void focusNext () { pragma(inline
, true); if (ctxp
) ctx
.focusNext(); }
1565 @property int focused () const { pragma(inline
, true); return (ctxp ? ctx
.focusedId
: -1); }
1566 @property void focused (int id
) { pragma(inline
, true); if (ctxp
) ctx
.focused
= id
; }
1568 void setEnabled (int id
, bool v
) {
1569 if (auto lp
= layprops(id
)) {
1570 if (lp
.enabled
!= v
) {
1572 if (!v
&& focused
== id
) focusNext();
1578 void keyboardEvent (TtyEvent ev
) { pragma(inline
, true); if (ctxp
) ctx
.keyboardEvent(ev
); }
1579 //void charEvent (dchar ch) { pragma(inline, true); if (ctxp) ctx.charEvent(ch); }
1580 //void mouseEvent (MouseEvent ev) { pragma(inline, true); if (ctxp) ctx.mouseEvent(ev); }
1582 // don't pass anything to automatically calculate update delta
1583 void update (int msecDelta
=-1) { pragma(inline
, true); if (ctxp
) ctx
.update(msecDelta
); }
1585 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
); }
1586 bool hasEvents () const nothrow @trusted @nogc { pragma(inline
, true); return (ctxp ? ctx
.hasEvents() : false); }
1587 FuiEvent
getEvent () nothrow @trusted @nogc { pragma(inline
, true); return (ctxp ? ctx
.getEvent() : FuiEvent
.init
); }
1589 @property ubyte lastButtons () nothrow @trusted @nogc { pragma(inline
, true); return (ctxp ? ctx
.lastButtons
: 0); }
1590 @property ubyte lastMods () nothrow @trusted @nogc { pragma(inline
, true); return (ctxp ? ctx
.lastMods
: 0); }
1592 int opIndex (const(char)[] id
) nothrow @trusted @nogc {
1593 pragma(inline
, true);
1594 import iv
.egtui
.tui
: findById
;
1595 return this.findById(id
);