egra: prepare agg mini before calling `onPaint()` (and cleanup afterwards)
[iv.d.git] / egtui / layout.d
blobb148acdcf3be526c7c650e3aaccbe5ebb4e0b6f6
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*/;
21 import iv.alice;
22 import iv.strex;
23 import iv.rawtty;
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 {
36 align(1):
37 int x, y;
38 @property pure nothrow @safe @nogc:
39 bool inside (in FuiRect rc) const { pragma(inline, true); return (x >= rc.pos.x && y >= rc.pos.y && x < rc.pos.x+rc.size.w && y < rc.pos.y+rc.size.h); }
41 align(1) struct FuiSize { align(1): int w, h; }
42 align(1) struct FuiRect {
43 align(1):
44 FuiPoint pos;
45 FuiSize size;
46 @property pure nothrow @safe @nogc:
47 int x () const { pragma(inline, true); return pos.x; }
48 int y () const { pragma(inline, true); return pos.y; }
49 int w () const { pragma(inline, true); return size.w; }
50 int h () const { pragma(inline, true); return size.h; }
51 void x (int v) { pragma(inline, true); pos.x = v; }
52 void y (int v) { pragma(inline, true); pos.y = v; }
53 void w (int v) { pragma(inline, true); size.w = v; }
54 void h (int v) { pragma(inline, true); size.h = v; }
56 ref int xp () { pragma(inline, true); return pos.x; }
57 ref int yp () { pragma(inline, true); return pos.y; }
58 ref int wp () { pragma(inline, true); return size.w; }
59 ref int hp () { pragma(inline, true); return size.h; }
61 bool inside (in FuiPoint pt) const { pragma(inline, true); return (pt.x >= pos.x && pt.y >= pos.y && pt.x < pos.x+size.w && pt.y < pos.y+size.h); }
63 align(1) struct FuiMargin { align(1): int left, top, right, bottom; }
66 // ////////////////////////////////////////////////////////////////////////// //
67 // pporperties for layouter
68 public align(1) struct FuiLayoutProps {
69 align(1):
70 enum Orientation {
71 Horizontal,
72 Vertical,
75 // "NPD" means "non-packing direction"
76 enum Align {
77 Center, // the available space is divided evenly
78 Start, // the NPD edge of each box is placed along the NPD of the parent box
79 End, // the opposite-NPD edge of each box is placed along the opposite-NPD of the parent box
80 Stretch, // the NPD-size of each boxes is adjusted to fill the parent box
83 // flags accessors
84 @property pure nothrow @safe @nogc {
85 bool hovered () const { pragma(inline, true); return ((flags&Flags.Hovered) != 0); }
86 bool active () const { pragma(inline, true); return ((flags&Flags.Active) != 0); }
88 bool canBeFocused () const { pragma(inline, true); return ((flags&Flags.CanBeFocused) != 0); }
89 void canBeFocused (bool v) { pragma(inline, true); if (v) flags |= Flags.CanBeFocused; else flags &= ~Flags.CanBeFocused; }
91 bool enabled () const { pragma(inline, true); return ((flags&Flags.Disabled) == 0); }
92 void enabled (bool v) { pragma(inline, true); if (v) flags &= ~Flags.Disabled; else flags = (flags|Flags.Disabled)&~(Flags.Hovered|Flags.Active); }
94 bool disabled () const { pragma(inline, true); return ((flags&Flags.Disabled) != 0); }
95 void disabled (bool v) { pragma(inline, true); if (v) flags = (flags|Flags.Disabled)&~(Flags.Hovered|Flags.Active); else flags &= ~Flags.Disabled; }
97 bool visible () const { pragma(inline, true); return ((flags&Flags.Invisible) == 0); }
98 void visible (bool v) { pragma(inline, true); if (v) flags &= ~Flags.Invisible; else flags = (flags|Flags.Invisible)&~(Flags.Hovered|Flags.Active); }
100 bool hidden () const { pragma(inline, true); return ((flags&Flags.Invisible) != 0); }
101 void hidden (bool v) { pragma(inline, true); if (v) flags = (flags|Flags.Invisible)&~(Flags.Hovered|Flags.Active); else flags &= ~Flags.Invisible; }
103 bool lineBreak () const { pragma(inline, true); return ((flags&Flags.LineBreak) != 0); }
104 void lineBreak (bool v) { pragma(inline, true); if (v) flags |= Flags.LineBreak; else flags &= ~Flags.LineBreak; }
106 bool wantTab () const { pragma(inline, true); return ((flags&Flags.WantTab) != 0); }
107 void wantTab (bool v) { pragma(inline, true); if (v) flags |= Flags.WantTab; else flags &= ~Flags.WantTab; }
109 bool wantReturn () const { pragma(inline, true); return ((flags&Flags.WantReturn) != 0); }
110 void wantReturn (bool v) { pragma(inline, true); if (v) flags |= Flags.WantReturn; else flags &= ~Flags.WantReturn; }
112 ushort userFlags () const { pragma(inline, true); return (flags&Flags.UserFlagsMask); }
113 void userFlags (ushort v) { pragma(inline, true); flags = (flags&~Flags.UserFlagsMask)|v; }
116 // WARNING! don't change this fields from user code!
117 FuiRect position; // calculated item position
118 int itemid = -1;
119 int parent = -1; // item parent
120 int firstChild = -1;
121 int lastChild = -1;
122 int prevSibling = -1;
123 int nextSibling = -1;
125 Orientation orientation = Orientation.Horizontal; // box orientation
126 Align aligning = Align.Start; // NPD for children
127 int flex = 1; // default flex value
129 @property pure nothrow @safe @nogc {
130 bool horizontal () const { pragma(inline, true); return (orientation == Orientation.Horizontal); }
131 bool vertical () const { pragma(inline, true); return (orientation == Orientation.Vertical); }
133 void horizontal (bool v) { pragma(inline, true); orientation = (v ? Orientation.Horizontal : Orientation.Vertical); }
134 void vertical (bool v) { pragma(inline, true); orientation = (v ? Orientation.Vertical : Orientation.Horizontal); }
137 FuiSize minSize;
138 FuiSize maxSize = FuiSize(int.max-1024, int.max-1024); // arbitrary limit, you know
139 FuiMargin padding;
140 int spacing; // spacing for children
141 int lineSpacing; // line spacing for horizontal boxes
143 enum Buttons : ubyte {
144 None = 0,
145 Left = 0x01,
146 Right = 0x02,
147 Middle = 0x04,
148 WheelUp = 0x08,
149 WheelDown = 0x10,
152 enum Button : ubyte {
153 Left,
154 Right,
155 Middle,
156 WheelUp,
157 WheelDown,
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); }
166 private:
167 enum Flags : uint {
168 None = 0,
169 UserFlagsMask = 0xffffu,
170 // UI flags
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,
182 LayouterFlagsMask =
183 TempLineBreak|
184 TouchedByGroup|
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
194 FuiContextImpl* ctx;
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;
203 } else {
204 // append to group
205 auto it = lp.groupSibling.ptr[grp]&0x7fff_ffff;
206 version(fui_many_asserts) assert(it != 0x7fff_ffff);
207 for (;;) {
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;
212 return;
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 {
237 enum Type {
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
246 Type type;
247 int item;
249 private union {
250 struct {
251 uint param0;
252 uint param1;
253 short mx, my; // coordinates *inside* item
255 TtyEvent paramkey;
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 {
278 private:
279 enum MaxQueuedEvents = 16;
280 enum MaxQueuedExternalEvents = 64;
282 private:
283 uint rc = 1; // refcount
284 ubyte* pmem; // private memory: this holds controls
285 uint pmemused;
286 uint pmemsize;
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;
301 public:
302 // return id or -1
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;
306 for (;;) {
307 auto lp = layprops(id);
308 if (lp is null) return -1;
309 if (lp.firstChild != -1) {
310 // has children, descent
311 id = lp.firstChild;
312 continue;
314 // no children, check ourself
315 if (check(id)) return id;
316 // go to next sibling
317 for (;;) {
318 if (lp.nextSibling != -1) { id = lp.nextSibling; break; }
319 // no sibling, bubble and get next sibling
320 id = lp.parent;
321 lp = layprops(id);
322 if (lp is null) return -1;
327 nothrow @nogc:
328 private:
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]) {
333 type = atype;
334 item = aitem;
335 param0 = aparam0;
336 param1 = aparam1;
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]) {
346 type = atype;
347 item = aitem;
348 paramkey = akey;
352 bool hasEvents () const pure nothrow @safe @nogc { pragma(inline, true); return (eventPos > 0); }
354 FuiEvent getEvent () nothrow @trusted @nogc {
355 if (eventPos > 0) {
356 auto nn = eventHead;
357 eventHead = (eventHead+1)%events.length;
358 --eventPos;
359 return events.ptr[nn];
360 } else {
361 return FuiEvent.init;
365 // pt is inside item
366 FuiPoint toGlobal (int item, FuiPoint pt) {
367 while (item >= 0) {
368 if (auto lp = layprops(item)) {
369 pt.x += lp.position.x;
370 pt.y += lp.position.y;
371 if (item == 0) break;
372 item = lp.parent;
375 return pt;
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));
388 // [0..7]
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];
400 if (i == -1) return;
401 foreach (immutable idx, int lc; lastClick) {
402 if (idx != bidx && lc == i) return;
404 layprops(i).active = false;
407 void doRelease() () {
408 resetActive();
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;
415 return;
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;
429 return;
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]); }
438 // try single click
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;
446 return;
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;
455 void doPress() () {
456 // void?
457 if (lastHover == -1) {
458 // reset all
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;
463 return;
465 // first press?
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]); }
468 // start single
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;
476 return;
478 // second press?
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"); }
485 // same
486 if (lastClickDelta.ptr[bidx] > fuiDoubleTime) {
487 // reset double to single
488 beventCount.ptr[bidx] = 1;
489 lastClickDelta.ptr[bidx] = lastClickDelta[0].max;
490 } else {
491 //asDouble = true;
492 beventCount.ptr[bidx] = 3;
493 //lastClickDelta.ptr[bidx] = 0;
495 } else {
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;
506 return;
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]); }
509 resetActive();
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;
517 if (down) {
518 // button pressed
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); }
522 doPress();
523 } else {
524 // button released
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); }
528 doRelease();
532 // external actions
533 void mouseAt (in FuiPoint pt) {
534 if (lastMouse != pt) {
535 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;
540 lastHover = nh;
545 private import core.time;
546 private MonoTime lastUpdateTime;
547 private bool updateWasCalled;
549 static struct ExternalEvent {
550 enum Type { Key, Char, Mouse }
551 Type type;
552 TtyEvent kev;
553 //MouseEvent mev;
554 dchar cev;
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; }
567 } else {
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()`!
587 void focusPrev () {
588 int prevIt = -1;
589 auto lfc = layprops(focused);
590 if (lfc !is null) {
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;
596 return false;
599 if (prevIt == -1) {
600 findNext(-1, (int item) {
601 if (auto lc = layprops(item)) {
602 if (lc.canBeFocused && lc.visible && !lc.disabled) prevIt = item;
604 return false;
607 focused = prevIt;
610 void focusNext () {
611 auto lfc = layprops(focused);
612 if (lfc is null) {
613 focused = findNext(0, (int item) {
614 if (auto lc = layprops(item)) return (lc.canBeFocused && lc.visible && !lc.disabled);
615 return false;
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);
620 return false;
622 if (focused == -1) focused = findNext(0, (int item) {
623 if (auto lc = layprops(item)) return (lc.canBeFocused && lc.visible && !lc.disabled);
624 return false;
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;
635 if (msecDelta < 0) {
636 auto ct = MonoTime.currTime;
637 msecDelta = cast(int)((ct-lastUpdateTime).total!"msecs");
638 lastUpdateTime = ct;
639 } else {
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;
647 ltm = cast(short)nt;
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);
656 break;
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();
666 break;
668 if (auto lc = layprops(focused)) {
669 if (lc.canBeFocused && lc.enabled) queueEvent(focused, FuiEvent.Type.Key, extEvents.ptr[extevHead].kev);
671 break;
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) {
679 // rawtty workaround
680 newButtonState(ev.button-TtyEvent.MButton.First, true);
681 newButtonState(ev.button-TtyEvent.MButton.First, false);
685 switch (ev.type) {
686 case MouseEventType.buttonPressed:
687 case MouseEventType.buttonReleased:
688 if (ev.button) newButtonState(cast(uint)ev.button-1, (ev.type == MouseEventType.buttonPressed));
689 break;
690 case MouseEventType.motion:
691 //{ import std.stdio; writeln(ev.x, ",", ev.y); }
692 break;
693 default:
696 break;
698 extevHead = (extevHead+1)%extEvents.length;
699 --extevPos;
703 private:
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));
723 fclose(fo);
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);
741 fclose(fo);
744 pmem = v;
745 pmem[pmemsize..newsz] = 0;
746 pmemsize = newsz;
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;
752 res[0..asz] = 0;
753 pmemused += asz;
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);
759 return cast(T*)res;
762 @property int lastItemIndex () const pure { pragma(inline, true); return pcount-1; }
764 // -1: none
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
771 auto ofs = pmemused;
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");
778 pidx = cast(uint*)v;
779 pidxsize = newsz/cast(uint)(*pidx).sizeof;
781 version(fui_many_asserts) assert(pidxsize-pcount > 0);
782 pidx[pcount] = ofs;
783 ++pcount;
784 return res;
787 void clear () {
788 clearControls();
789 focusedId = -1;
790 lastHover = -1;
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 () {
798 if (pmemused > 0) {
799 import core.stdc.string : memset;
800 memset(pmem, 0, pmemused);
802 pmemused = 0;
803 pcount = 0;
806 public:
807 @disable this (this); // no copies!
809 static void decRef (usize me) {
810 if (me) {
811 auto nfo = cast(FuiContextImpl*)me;
812 version(fui_many_asserts) assert(nfo.rc);
813 if (--nfo.rc == 0) {
814 import core.stdc.stdlib : free;
815 if (nfo.pmem !is null) {
816 import core.memory : GC;
817 GC.removeRange(nfo.pmem);
818 free(nfo.pmem);
820 if (nfo.pidx !is null) free(nfo.pidx);
821 free(nfo);
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)
834 // `pt` is global
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); }
839 for (;;) {
840 auto lp = layprops(id);
841 version(fui_many_asserts) assert(lp !is null);
842 if (lp.nextSibling == -1) break;
843 id = lp.nextSibling;
845 debug(fui_item_at) { import core.stdc.stdio : printf; printf("lastsib: %d\n", id); }
846 // check all siblings from the last one
847 for (;;) {
848 auto lp = layprops(id);
849 if (lp is null) return -1;
850 if (lp.visible) {
851 auto rc = lp.position;
852 rc.xp += g.x;
853 rc.yp += g.y;
854 if (pt.inside(rc)) {
855 // inside, go on
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!
863 } else {
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
868 id = lp.prevSibling;
871 if (length == 0 || !layprops(0).visible) return -1;
872 return check(0, FuiPoint(0, 0));
875 // return id or -1
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;
879 for (;;) {
880 auto lp = layprops(id);
881 if (lp is null) return -1;
882 if (lp.firstChild != -1) {
883 // has children, descent
884 id = lp.firstChild;
885 continue;
887 // no children, check ourself
888 if (check(id)) return id;
889 // go to next sibling
890 for (;;) {
891 if (lp.nextSibling != -1) { id = lp.nextSibling; break; }
892 // no sibling, bubble and get next sibling
893 id = lp.parent;
894 lp = layprops(id);
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*!
908 struct FuiContext {
909 static assert(usize.sizeof >= (void*).sizeof);
911 private:
912 usize ctxp; // hide from GC
914 public:
915 int findNextEx (int id, scope bool delegate (int id) check) {
916 if (ctxp == 0) return -1;
917 return ctx.findNextEx(id, check);
920 nothrow @nogc:
921 private:
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);
932 lp.ctx = ctx;
933 with (lp) {
934 vertical = true;
935 padding.left = 3;
936 padding.right = 3;
937 padding.top = 2;
938 padding.bottom = 2;
939 spacing = 0;
940 itemid = 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
945 // add item data
946 ctx.xcalloc!FuiCtlRootPanel();
947 auto data = itemIntr!FuiCtlRootPanel(0);
948 data.type = FuiCtlType.Box;
951 public:
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;
956 FuiContext res;
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;
963 res.addRootPanel();
964 return res;
967 public:
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) {
973 if (csrc.ctxp) {
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
979 ctxp = csrc.ctxp;
980 } else if (ctxp) {
981 // assigning empty context
982 FuiContextImpl.decRef(ctxp);
983 ctxp = 0;
987 public:
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");
1001 auto cidx = length;
1002 if (parent >= cidx) assert(0, "invalid parent for Fui item");
1003 // add layouter properties
1004 ctx.addItem!FuiLayoutProps();
1005 auto clp = layprops(cidx);
1006 clp.ctx = ctx;
1007 clp.itemid = cidx;
1008 if (parent >= 0) {
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) {
1015 // no children
1016 version(fui_many_asserts) assert(pp.lastChild == -1);
1017 pp.firstChild = pp.lastChild = cidx;
1018 } else {
1019 version(fui_many_asserts) assert(pp.lastChild != -1);
1020 layprops(pp.lastChild).nextSibling = cidx;
1021 pp.lastChild = cidx;
1024 // add item data
1025 ctx.xcalloc!T(addsize);
1026 return cidx;
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;
1034 ofs = ctx.allocOfs;
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!
1044 void clear () {
1045 pragma(inline, true);
1046 if (ctxp) {
1047 ctx.clear();
1048 addRootPanel();
1052 // this will clear only controls, use with care!
1053 void clearControls () {
1054 pragma(inline, true);
1055 if (ctxp) {
1056 ctx.clearControls();
1057 addRootPanel();
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;
1067 } else {
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;
1079 } else {
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
1091 void relayout () {
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();
1102 if (idx > 0) {
1103 lp.position = lp.position.init; // zero it out
1104 } else {
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;
1116 version(none) {
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];
1122 for (;;) {
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
1141 // cache values
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
1155 int lineCount = 0;
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);
1164 lastCIdx = cidx;
1167 // flush current line
1168 void flushLine () {
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;
1174 // fix max width
1175 maxW = max(maxW, curWidth);
1176 // fix max height
1177 maxH += lineH+(lineCount ? lp.lineSpacing : 0);
1178 // restart line
1179 curWidth = bpadLeft+bpadRight;
1180 lastCIdx = -1;
1181 lineH = 0;
1182 ++lineCount;
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);
1192 return;
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
1199 flushLine();
1200 } else {
1201 // flush current line
1202 flushLine();
1203 // and add this item to it
1204 addToLine(clp, cidx);
1208 // layout children, insert "soft" line breaks
1209 int cidx = lp.firstChild;
1210 for (;;) {
1211 auto clp = layprops(cidx);
1212 if (clp is null) break;
1213 layit(cidx); // layout children of this box
1214 if (hbox) {
1215 // for horizontal box, logic is somewhat messy
1216 putItem(clp, cidx);
1217 if (clp.flags&clp.Flags.LineBreak) flushLine();
1218 } else {
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);
1223 ++lineCount;
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)
1239 int spaceLeft;
1241 if (hbox) {
1242 // layout horizontal box; we should do this for each line separately
1243 int lineStartY = bpadTop;
1245 void resetLine () {
1246 flexTotal = 0;
1247 flexBoxCount = 0;
1248 curSpc = 0;
1249 spaceLeft = lp.position.w-(bpadLeft+bpadRight);
1250 lineH = 0;
1253 int lstart = lp.firstChild;
1254 int lineNum = 0;
1255 for (;;) {
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);
1260 resetLine();
1261 cidx = lstart;
1262 for (;;) {
1263 auto clp = layprops(cidx);
1264 if (clp is null) break;
1265 auto dim = clp.position.w+curSpc;
1266 spaceLeft -= dim;
1267 lineH = max(lineH, clp.position.h);
1268 // process flex
1269 if (clp.flex > 0) { flexTotal += clp.flex; ++flexBoxCount; }
1270 if (clp.tempLineBreak) break; // no more in this line
1271 curSpc = bspc;
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); }
1280 cidx = lstart;
1281 float flt = cast(float)flexTotal;
1282 float left = cast(float)spaceLeft;
1283 int curpos = bpadLeft;
1284 for (;;) {
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)
1291 // fix y coord
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;
1304 break;
1306 // fix flexbox size
1307 if (clp.flex > 0) {
1308 int toadd = cast(int)(left*cast(float)clp.flex/flt+0.5);
1309 if (toadd > 0) {
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
1327 lstart = cidx;
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;
1331 } else {
1332 // layout vertical box, it is much easier
1334 // setup vars
1335 //flexTotal = 0;
1336 //flexBoxCount = 0;
1337 //curSpc = 0;
1338 spaceLeft = lp.position.h-(bpadTop+bpadBottom);
1340 // calculate flex variables
1341 cidx = lp.firstChild;
1342 for (;;) {
1343 auto clp = layprops(cidx);
1344 if (clp is null) break;
1345 auto dim = clp.position.h+curSpc;
1346 spaceLeft -= dim;
1347 // process flex
1348 if (clp.flex > 0) { flexTotal += clp.flex; ++flexBoxCount; }
1349 curSpc = bspc;
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;
1358 for (;;) {
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)
1365 // fix x coord
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;
1378 break;
1380 // fix flexbox size
1381 if (clp.flex > 0) {
1382 int toadd = cast(int)(left*cast(float)clp.flex/flt);
1383 if (toadd > 0) {
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
1399 // main code
1400 if (ctxp == 0 || length < 1) return;
1402 resetValues();
1404 // do top-level packing
1405 layit(0);
1406 bool resetTouched = false;
1407 for (;;) {
1408 bool doItAgain = false;
1409 bool doFix = 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) {
1415 int dim = 1;
1416 int cidx = gidx;
1417 // calcluate maximal dimension
1418 for (;;) {
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;
1425 // fix dimensions
1426 cidx = gidx;
1427 for (;;) {
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);
1434 if (od != nd) {
1435 doFix = true;
1436 setdim(cidx, nd);
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
1459 if (resetTouched) {
1460 foreach (int idx; 0..length) layprops(idx).touchedByGroup = false;
1461 } else {
1462 resetTouched = true;
1465 // if we need to fix some parts of the layout, do it
1466 if (doFix) {
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) {
1471 int it = gidx;
1472 while (layprops(it) !is null) {
1473 //{ import core.stdc.stdio : printf; printf(" === it=%d ===\n", it); }
1474 int itt = it;
1475 for (;;) {
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;
1482 layit(itt);
1483 if (itt != it && ow == lp.position.w && oh == lp.position.h) break;
1484 } else {
1485 break;
1487 itt = lp.parent;
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];
1496 //doItAgain = true;
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;
1514 ind(indent);
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;
1517 for (;;) {
1518 lp = layprops(idx);
1519 if (lp is null) break;
1520 dumpItem(idx, indent+2);
1521 idx = lp.nextSibling;
1525 dumpItem(0, 0);
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;
1536 ind(indent);
1537 printf("Ctl#%d: position:(%d,%d); size:(%d,%d)\n", idx, lp.position.x, lp.position.y, lp.position.w, lp.position.h);
1538 idx = lp.lastChild;
1539 for (;;) {
1540 lp = layprops(idx);
1541 if (lp is null) break;
1542 dumpItem(idx, indent+2);
1543 idx = lp.prevSibling;
1547 dumpItem(0, 0);
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); }
1557 // return id or -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(); }
1564 // -1: none
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) {
1571 lp.enabled = v;
1572 if (!v && focused == id) focusNext();
1577 // external actions
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);