DEADSOFTWARE

HolmesUI: ui parser fixes; vbox layouter fixes (centering control); scissoring fixes
[d2df-sdl.git] / src / gx / gh_flexlay.pas
1 {$INCLUDE ../shared/a_modes.inc}
2 unit gh_flexlay;
4 (*
5 first pass:
6 set all 'temp-flex' flags for controls to 'flex'
7 reset all 'laywrap' flags for controls
8 build group arrays; for each group: find max size for group, adjust 'startsize' controls to group max size
9 call 'calc max size' for top-level control
10 flags set:
11 'firsttime'
13 second pass:
14 calcluate desired sizes (process flexes) using 'startsize', set 'desiredsize' and 'desiredpos'
15 if control has children, call 'second pass' recursively with this control as parent
16 flags set:
17 'group-element-changed', if any group element size was changed
18 'wrapping-changed', if not 'firsttime', and wrapping was changed (i.e. first pass will not set the flag)
20 third pass:
21 if 'group-element-changed':
22 for each group: adjust controls to max desired size (startsize), set 'temp-flex' flags to 0 for 'em, set 'second-again' flag
23 for other controls: if 'desiredsize' > 'maxsize', set 'startsize' to 'maxsize', set 'temp-flex' flag to 0, set 'second-again' flag
24 if 'second-again' or 'wrapping-changed':
25 reset 'second-again'
26 reset 'wrapping-changed'
27 reset 'firsttime'
28 goto second pass
30 fourth pass:
31 set 'actualsize' and 'actualpos' to 'desiredsize' and 'desiredpos'
32 return
34 calc max size:
35 set 'startsize' to max(size, maxsize, 0)
36 if 'size' is negative:
37 set 'temp-flex' flag to 0
38 if has children:
39 call 'calc max size' for each child
40 set 'desiredmax' to 'startsize'
41 do lines, don't distribute space (i.e. calc only wrapping),
42 for each complete line, set 'desiredmax' to max(desiredmax, linesize)
43 if 'maxsize' >= 0:
44 set 'desiredmax' to min(desiredmax, maxsize)
45 set 'startsize' to 'desiredmax'
46 return
49 wrapping lines:
50 try to stuff controls in line until line width is less or equal to maxsize
51 distribute flex for filled line
52 continue until we still has something to stuff
55 for wrapping:
56 we'll hold 'laywrap' flag for each control; it will be set if this control
57 starts a new line (even if this is the first control in line, as it is obviously
58 starts a new line)
60 on redoing second pass, if 'laywrap' flag changed, set 'wrapping-changed' flag
61 *)
64 (*
65 control default size will be increased by margins
66 negative margins are ignored
67 ControlT:
68 procedure layPrepare (); // called before registering control in layouter
69 function getDefSize (): TLaySize; // default size; <0: use max size
70 function getMargins (): TLayMargins;
71 function getMaxSize (): TLaySize; // max size; <0: set to some huge value
72 function getFlex (): Integer; // <=0: not flexible
73 function isHorizBox (): Boolean; // horizontal layout for children?
74 function canWrap (): Boolean; // for horizontal boxes: can wrap children? for child: `false` means 'nonbreakable at *next* ctl'
75 function isLineStart (): Boolean; // `true` if this ctl should start a new line; ignored for vertical boxes
76 function getAlign (): Integer; // aligning in non-main direction: <0: left/up; 0: center; >0: right/down
77 function getExpand (): Boolean; // expanding in non-main direction: `true` will ignore align and eat all available space
78 procedure setActualSizePos (constref apos: TLayPos; constref asize: TLaySize);
79 function getHGroup (): AnsiString; // empty: not grouped
80 function getVGroup (): AnsiString; // empty: not grouped
81 function nextSibling (): ControlT;
82 function firstChild (): ControlT;
83 *)
85 interface
87 uses
88 gh_ui_common;
91 // ////////////////////////////////////////////////////////////////////////// //
92 type
93 generic TFlexLayouterBase<ControlT> = class
94 public
95 type CtlT = ControlT;
97 private
98 type LayControlIdx = Integer;
100 private
101 // flags
102 const
103 FlagHorizBox = LongWord(1) shl 0; // horizontal layout for children
104 FlagLineStart = LongWord(1) shl 1;
105 FlagLineCanWrap = LongWord(1) shl 2;
106 // internal
107 FlagLineDidWrap = LongWord(1) shl 3; // will be set when line was wrapped
108 FlagInGroup = LongWord(1) shl 4; // set if this control is a member of any group
109 FlagExpand = LongWord(1) shl 5;
110 FlagLineFirst = LongWord(1) shl 6;
112 private
113 type
114 PLayControl = ^TLayControl;
115 TLayControl = record
116 public
117 myidx: LayControlIdx;
118 tempFlex: Integer;
119 flags: LongWord; // see below
120 aligndir: Integer;
121 startsize: TLaySize; // current
122 desiredsize: TLaySize;
123 maxsize: TLaySize;
124 margins: TLayMargins; // can never be negative
125 desiredpos: TLayPos;
126 ctl: ControlT;
127 parent: LayControlIdx; // = -1;
128 firstChild: LayControlIdx; // = -1;
129 nextSibling: LayControlIdx; // = -1;
131 private
132 function getDidWrap (): Boolean; inline;
133 procedure setDidWrap (v: Boolean); inline;
135 public
136 procedure initialize (); inline;
138 function horizBox (): Boolean; inline;
139 function lineStart (): Boolean; inline;
140 function canWrap (): Boolean; inline;
141 function inGroup (): Boolean; inline;
142 function firstInLine (): Boolean; inline;
144 function getExpand (): Boolean; inline;
145 procedure setExpand (v: Boolean); inline;
147 public
148 property didWrap: Boolean read getDidWrap write setDidWrap;
149 property expand: Boolean read getExpand write setExpand;
150 end;
152 PLayGroup = ^TLayGroup;
153 TLayGroup = record
154 name: AnsiString;
155 ctls: array of LayControlIdx;
156 end;
158 TLayCtlArray = array of TLayControl;
159 TLayGrpArray = array of TLayGroup;
161 private
162 ctlist: TLayCtlArray;
163 groups: array[0..1] of TLayGrpArray; // horiz, vert
165 firstTime: Boolean;
166 groupElementChanged: Boolean;
167 wrappingChanged: Boolean;
169 private
170 procedure fixFlags (cidx: LayControlIdx);
171 procedure doChildren (parent: LayControlIdx; child: ControlT);
172 procedure appendToGroup (const gname: AnsiString;cidx: LayControlIdx;gidx: Integer);
173 procedure setupGroups ();
175 // this also sets `tempFlex`
176 procedure calcMaxSizeInternal (cidx: LayControlIdx);
178 procedure fixLine (me: PLayControl; i0, i1: LayControlIdx; cury: Integer; var spaceLeft: Single; var flexTotal: Integer; var flexBoxCount: Integer);
179 // do box layouting; call `layBox()` recursively if necessary
180 procedure layBox (boxidx: LayControlIdx);
182 procedure firstPass ();
183 procedure secondPass ();
184 procedure thirdPass ();
185 procedure fourthPass ();
187 procedure dumpList (cidx: LayControlIdx; indent: Integer);
189 public
190 type
191 TChildrenEnumerator = record
192 private
193 ctls: TLayCtlArray;
194 cur: Integer;
195 first: Boolean;
196 public
197 constructor Create (constref actls: TLayCtlArray; acur: Integer);
198 function moveNext (): Boolean; inline;
199 function getCurrent (): PLayControl; inline;
200 function getEnumerator (): TChildrenEnumerator; inline;
201 property current: PLayControl read getCurrent;
202 end;
204 public
205 constructor Create ();
206 destructor Destroy (); override;
208 // clear build lists
209 procedure clear ();
211 // build control and group lists
212 procedure setup (root: ControlT);
214 function forChildren (cidx: LayControlIdx): TChildrenEnumerator; inline;
216 procedure layout ();
218 procedure dumpFlat ();
219 procedure dump ();
220 end;
223 implementation
225 uses
226 utils;
229 // ////////////////////////////////////////////////////////////////////////// //
230 procedure TFlexLayouterBase.TLayControl.initialize (); inline;
231 begin
232 FillChar(self, 0, sizeof(self));
233 parent := -1;
234 firstChild := -1;
235 nextSibling := -1;
236 end;
238 function TFlexLayouterBase.TLayControl.horizBox (): Boolean; inline; begin result := ((flags and FlagHorizBox) <> 0); end;
239 function TFlexLayouterBase.TLayControl.lineStart (): Boolean; inline; begin result := ((flags and FlagLineStart) <> 0); end;
240 function TFlexLayouterBase.TLayControl.canWrap (): Boolean; inline; begin result := ((flags and FlagLineCanWrap) <> 0); end;
241 function TFlexLayouterBase.TLayControl.inGroup (): Boolean; inline; begin result := ((flags and FlagInGroup) <> 0); end;
242 function TFlexLayouterBase.TLayControl.firstInLine (): Boolean; inline; begin result := ((flags and FlagLineFirst) <> 0); end;
244 function TFlexLayouterBase.TLayControl.getDidWrap (): Boolean; inline; begin result := ((flags and FlagLineDidWrap) <> 0); end;
245 procedure TFlexLayouterBase.TLayControl.setDidWrap (v: Boolean); inline; begin if (v) then flags := flags or FlagLineDidWrap else flags := flags and (not FlagLineDidWrap); end;
247 function TFlexLayouterBase.TLayControl.getExpand (): Boolean; inline; begin result := ((flags and FlagExpand) <> 0); end;
248 procedure TFlexLayouterBase.TLayControl.setExpand (v: Boolean); inline; begin if (v) then flags := flags or FlagExpand else flags := flags and (not FlagExpand); end;
251 // ////////////////////////////////////////////////////////////////////////// //
252 constructor TFlexLayouterBase.TChildrenEnumerator.Create (constref actls: TLayCtlArray; acur: Integer);
253 begin
254 ctls := actls;
255 cur := acur;
256 first := true;
257 end;
259 function TFlexLayouterBase.TChildrenEnumerator.moveNext (): Boolean; inline;
260 begin
261 if first then
262 begin
263 if (cur >= 0) and (cur < Length(ctls)) then cur := ctls[cur].firstChild else cur := -1;
264 first := false;
265 end
266 else
267 begin
268 cur := ctls[cur].nextSibling;
269 end;
270 result := (cur >= 0);
271 end;
273 function TFlexLayouterBase.TChildrenEnumerator.getCurrent (): PLayControl; inline;
274 begin
275 result := @ctls[cur];
276 end;
278 function TFlexLayouterBase.TChildrenEnumerator.getEnumerator (): TChildrenEnumerator; inline;
279 begin
280 result := self;
281 end;
284 // ////////////////////////////////////////////////////////////////////////// //
285 constructor TFlexLayouterBase.Create ();
286 begin
287 ctlist := nil;
288 groups[0] := nil;
289 groups[1] := nil;
291 firstTime := false;
292 groupElementChanged := false;
293 wrappingChanged := false;
294 end;
297 destructor TFlexLayouterBase.Destroy ();
298 begin
299 clear();
300 inherited;
301 end;
304 function TFlexLayouterBase.forChildren (cidx: LayControlIdx): TChildrenEnumerator; inline;
305 begin
306 result := TChildrenEnumerator.Create(ctlist, cidx);
307 end;
310 procedure TFlexLayouterBase.clear ();
311 begin
312 ctlist := nil;
313 groups[0] := nil;
314 groups[1] := nil;
315 end;
318 procedure TFlexLayouterBase.fixFlags (cidx: LayControlIdx);
319 var
320 lc: PLayControl;
321 begin
322 assert((cidx >= 0) and (cidx < Length(ctlist)));
323 lc := @ctlist[cidx];
324 //lc.flags := 0;
325 if (lc.ctl.isHorizBox) then lc.flags := lc.flags or FlagHorizBox;
326 if (lc.ctl.isLineStart) then lc.flags := lc.flags or FlagLineStart;
327 if (lc.ctl.canWrap) then lc.flags := lc.flags or FlagLineCanWrap;
328 if (lc.ctl.getExpand) then lc.flags := lc.flags or FlagExpand;
329 lc.aligndir := lc.ctl.getAlign;
330 end;
333 procedure TFlexLayouterBase.doChildren (parent: LayControlIdx; child: ControlT);
334 var
335 cidx: LayControlIdx = -1;
336 lc: PLayControl;
337 begin
338 assert((parent >= 0) and (parent < Length(ctlist)));
339 assert(ctlist[parent].firstChild = -1);
340 while (child <> nil) do
341 begin
342 child.layPrepare;
343 SetLength(ctlist, Length(ctlist)+1);
344 lc := @ctlist[High(ctlist)];
345 lc.initialize();
346 if (cidx = -1) then
347 begin
348 cidx := LayControlIdx(High(ctlist));
349 ctlist[parent].firstChild := cidx;
350 // first child is always linestart
351 lc.flags := lc.flags or FlagLineStart or FlagLineFirst;
352 end
353 else
354 begin
355 ctlist[cidx].nextSibling := LayControlIdx(High(ctlist));
356 cidx := LayControlIdx(High(ctlist));
357 end;
358 lc.myidx := cidx;
359 lc.ctl := child;
360 lc.parent := parent;
361 fixFlags(cidx);
362 doChildren(cidx, child.firstChild);
363 child := child.nextSibling;
364 end;
365 end;
368 procedure TFlexLayouterBase.appendToGroup (const gname: AnsiString; cidx: LayControlIdx; gidx: Integer);
369 var
370 f: Integer;
371 begin
372 if (Length(gname) = 0) then exit;
373 assert((cidx >= 0) and (cidx < Length(ctlist)));
374 assert((gidx = 0) or (gidx = 1));
375 ctlist[cidx].flags := ctlist[cidx].flags or FlagInGroup;
376 for f := 0 to High(groups[gidx]) do
377 begin
378 if (groups[gidx][f].name = gname) then
379 begin
380 SetLength(groups[gidx][f].ctls, Length(groups[gidx][f].ctls)+1);
381 groups[gidx][f].ctls[High(groups[gidx][f].ctls)] := cidx;
382 exit;
383 end;
384 end;
385 // new group
386 f := Length(groups[gidx]);
387 SetLength(groups[gidx], f+1);
388 groups[gidx][f].name := gname;
389 SetLength(groups[gidx][f].ctls, Length(groups[gidx][f].ctls)+1);
390 groups[gidx][f].ctls[High(groups[gidx][f].ctls)] := cidx;
391 end;
394 procedure TFlexLayouterBase.setupGroups ();
395 var
396 idx: Integer;
397 lc: PLayControl;
398 begin
399 for idx := 0 to High(ctlist) do
400 begin
401 lc := @ctlist[idx];
402 appendToGroup(lc.ctl.getHGroup, LayControlIdx(idx), 0);
403 appendToGroup(lc.ctl.getVGroup, LayControlIdx(idx), 1);
404 end;
405 end;
408 // build control and group lists
409 procedure TFlexLayouterBase.setup (root: ControlT);
410 begin
411 clear();
412 if (root = nil) then exit;
413 root.layPrepare;
414 try
415 SetLength(ctlist, 1);
416 ctlist[0].initialize();
417 ctlist[0].myidx := 0;
418 ctlist[0].ctl := root;
419 fixFlags(0);
420 ctlist[0].flags := ctlist[0].flags or FlagLineStart or FlagLineFirst;
421 doChildren(0, root.firstChild);
422 setupGroups();
423 except
424 clear();
425 raise;
426 end;
427 end;
430 // this also sets `tempFlex`
431 procedure TFlexLayouterBase.calcMaxSizeInternal (cidx: LayControlIdx);
432 var
433 lc, c: PLayControl;
434 msz: TLaySize;
435 negw, negh: Boolean;
436 curwdt, curhgt, totalhgt: Integer;
437 doWrap: Boolean;
438 begin
439 if (cidx < 0) or (cidx >= Length(ctlist)) then exit;
441 lc := @ctlist[cidx];
442 msz := lc.ctl.getMaxSize;
443 negw := (lc.startsize.w <= 0);
444 negh := (lc.startsize.h <= 0);
446 lc.tempFlex := lc.ctl.getFlex;
448 for c in forChildren(cidx) do calcMaxSizeInternal(c.myidx);
450 if (lc.horizBox) then
451 begin
452 // horizontal boxes
453 if (negw) then lc.tempFlex := 0; // size is negative: don't expand
454 curwdt := lc.margins.horiz;
455 curhgt := lc.margins.vert;
456 totalhgt := 0;
457 for c in forChildren(cidx) do
458 begin
459 // new line?
460 doWrap := (not c.firstInLine) and (c.lineStart);
461 // need to wrap?
462 if (not doWrap) and (lc.canWrap) and (c.canWrap) and (msz.w > 0) and (curwdt+c.startsize.w > lc.startsize.w) then doWrap := true;
463 if (doWrap) then
464 begin
465 totalhgt += curhgt;
466 if (lc.startsize.w < curwdt) then lc.startsize.w := curwdt;
467 curwdt := 0;
468 curhgt := 0;
469 end;
470 curwdt += c.startsize.w;
471 if (curhgt < c.startsize.h) then curhgt := c.startsize.h;
472 end;
473 totalhgt += curhgt;
474 if (lc.startsize.w < curwdt) then lc.startsize.w := curwdt;
475 if (lc.startsize.h < totalhgt) then lc.startsize.h := totalhgt;
476 end
477 else
478 begin
479 // vertical boxes
480 if (negh) then lc.tempFlex := 0; // size is negative: don't expand
481 curhgt := lc.margins.vert;
482 for c in forChildren(cidx) do
483 begin
484 if (lc.startsize.w < c.startsize.w+lc.margins.horiz) then lc.startsize.w := c.startsize.w+lc.margins.horiz;
485 curhgt += c.startsize.h;
486 end;
487 if (lc.startsize.h < curhgt) then lc.startsize.h := curhgt;
488 end;
489 if (lc.startsize.w < 0) then lc.startsize.w := 0;
490 if (lc.startsize.h < 0) then lc.startsize.h := 0;
491 lc.maxsize := msz;
492 if (lc.maxsize.w < lc.startsize.w) then lc.maxsize.w := lc.startsize.w;
493 if (lc.maxsize.h < lc.startsize.h) then lc.maxsize.h := lc.startsize.h;
494 end;
497 procedure TFlexLayouterBase.firstPass ();
498 var
499 f, c: Integer;
500 needRecalcMaxSize: Boolean;
501 gtype: Integer;
502 grp: PLayGroup;
503 maxsz: Integer;
504 cidx: LayControlIdx;
505 ct: PLayControl;
506 mr: TLayMargins;
507 begin
508 // reset all 'laywrap' flags for controls, set initial 'startsize'
509 for f := 0 to High(ctlist) do
510 begin
511 ctlist[f].didWrap := false;
512 ctlist[f].startsize := ctlist[f].ctl.getDefSize;
513 mr := ctlist[f].ctl.getMargins;
514 ctlist[f].margins := mr;
515 ctlist[f].startsize.w += mr.horiz;
516 ctlist[f].startsize.h += mr.vert;
517 end;
518 // setup sizes
519 calcMaxSizeInternal(0); // this also sets `tempFlex`
520 // find max size for group, adjust 'startsize' controls to group max size
521 needRecalcMaxSize := false;
522 for gtype := 0 to 1 do
523 begin
524 for f := 0 to High(groups[gtype]) do
525 begin
526 grp := @groups[gtype][f];
527 maxsz := 0;
528 for c := 0 to High(grp.ctls) do
529 begin
530 cidx := grp.ctls[c];
531 ct := @ctlist[cidx];
532 if (maxsz < ct.startsize[gtype]) then maxsz := ct.startsize[gtype];
533 end;
534 for c := 0 to High(grp.ctls) do
535 begin
536 cidx := grp.ctls[c];
537 ct := @ctlist[cidx];
538 if (maxsz <> ct.startsize[gtype]) then
539 begin
540 needRecalcMaxSize := true;
541 ct.startsize[gtype] := maxsz;
542 end;
543 end;
544 end;
545 end;
546 // recalc maxsize if necessary
547 if (needRecalcMaxSize) then calcMaxSizeInternal(0);
548 // set "desired size" to "start size"
549 for f := 0 to High(ctlist) do ctlist[f].desiredsize := ctlist[f].startsize;
550 // set flags
551 firstTime := true;
552 //writeln('=== calculated max size ===');
553 //dump();
554 end;
557 procedure TFlexLayouterBase.fixLine (me: PLayControl; i0, i1: LayControlIdx; cury: Integer; var spaceLeft: Single; var flexTotal: Integer; var flexBoxCount: Integer);
558 var
559 curx: Integer;
560 lc: PLayControl;
561 osz: TLaySize;
562 toadd: Integer;
563 sti0: Integer;
564 lineh: Integer;
565 begin
566 curx := me.margins.left;
567 sti0 := i0;
568 // calc minimal line height
569 lineh := 0;
570 while (i0 <> i1) do
571 begin
572 lc := @ctlist[i0];
573 lineh := nmax(lineh, lc.startsize.h);
574 i0 := lc.nextSibling;
575 end;
576 // distribute space, expand/align
577 i0 := sti0;
578 while (i0 <> i1) do
579 begin
580 lc := @ctlist[i0];
581 osz := lc.desiredsize;
582 lc.desiredsize := lc.startsize;
583 lc.desiredpos.x := curx;
584 lc.desiredpos.y := cury;
585 curx += lc.desiredsize.w;
586 // fix flexbox size
587 if (lc.tempFlex > 0) and (spaceLeft > 0) then
588 begin
589 toadd := trunc(spaceLeft*lc.tempFlex/flexTotal+0.5);
590 if (toadd > 0) then
591 begin
592 // size changed
593 lc.desiredsize.w += toadd;
594 curx += toadd;
595 // compensate (crudely) rounding errors
596 if (curx > me.desiredsize.w-me.margins.horiz) then begin lc.desiredsize.w -= 1; curx -= 1; end;
597 // relayout children
598 layBox(lc.firstChild);
599 end;
600 end;
601 // expand or align
602 if (lc.expand) then lc.desiredsize.h := nmin(lc.maxsize.h, lineh) // expand
603 else if (lc.aligndir > 0) then lc.desiredpos.y := cury+(lineh-lc.desiredsize.h) // bottom align
604 else if (lc.aligndir = 0) then lc.desiredpos.y := cury+(lineh-lc.desiredsize.h) div 2; // center
605 if (not osz.equals(lc.desiredsize)) then
606 begin
607 if (lc.inGroup) then groupElementChanged := true;
608 // relayout children
609 layBox(lc.firstChild);
610 end;
611 i0 := lc.nextSibling;
612 end;
613 flexTotal := 0;
614 flexBoxCount := 0;
615 spaceLeft := me.desiredsize.w-me.margins.horiz;
616 end;
619 // do box layouting; call `layBox()` recursively if necessary
620 procedure TFlexLayouterBase.layBox (boxidx: LayControlIdx);
621 var
622 me: PLayControl;
623 flexTotal: Integer; // total sum of flex fields
624 flexBoxCount: Integer; // number of boxes
625 spaceLeft: Single;
626 cury: Integer;
627 maxwdt, maxhgt: Integer;
628 lineStartIdx: LayControlIdx;
629 lc: PLayControl;
630 doWrap: Boolean;
631 toadd: Integer;
632 osz: TLaySize;
633 begin
634 if (boxidx < 0) or (boxidx >= Length(ctlist)) then exit;
635 me := @ctlist[boxidx];
637 // if we have no children, there's nothing to do
638 if (me.firstChild = -1) then exit;
640 // first, layout all children
641 for lc in forChildren(boxidx) do layBox(lc.myidx);
643 // second, layout lines, distribute flex data
644 if (me.horizBox) then
645 begin
646 // horizontal boxes
647 cury := me.margins.top;
648 maxhgt := 0;
650 fixLine(me, -1, -1, cury, spaceLeft, flexTotal, flexBoxCount); //HACK!
652 lineStartIdx := me.firstChild;
654 for lc in forChildren(boxidx) do
655 begin
656 // new line?
657 doWrap := (not lc.firstInLine) and (lc.lineStart);
658 // need to wrap?
659 if (not doWrap) and (lc.canWrap) and (lc.canWrap) and (lc.desiredsize.w > 0) and (spaceLeft < lc.desiredsize.w) then doWrap := true;
660 if (doWrap) then
661 begin
662 // new line, fix this one
663 if (not lc.didWrap) then
664 begin
665 wrappingChanged := true;
666 lc.didWrap := true;
667 end;
668 fixLine(me, lineStartIdx, lc.myidx, cury, spaceLeft, flexTotal, flexBoxCount);
669 cury += maxhgt;
670 lineStartIdx := lc.myidx;
671 end
672 else
673 begin
674 if (lc.didWrap) then
675 begin
676 wrappingChanged := true;
677 lc.didWrap := false;
678 end;
679 end;
680 spaceLeft -= lc.desiredsize.w;
681 if (maxhgt < lc.desiredsize.h) then maxhgt := lc.desiredsize.h;
682 if (lc.tempFlex > 0) then
683 begin
684 flexTotal += lc.tempFlex;
685 flexBoxCount += 1;
686 end;
687 end;
688 // fix last line
689 fixLine(me, lineStartIdx, -1, cury, spaceLeft, flexTotal, flexBoxCount);
690 end
691 else
692 begin
693 // vertical boxes
694 maxwdt := 0;
695 flexTotal := 0;
696 flexBoxCount := 0;
697 spaceLeft := me.desiredsize.h-me.margins.vert;
699 // calc flex
700 for lc in forChildren(boxidx) do
701 begin
702 spaceLeft -= lc.desiredsize.h;
703 if (maxwdt < lc.desiredsize.w) then maxwdt := lc.desiredsize.w;
704 if (lc.tempFlex > 0) then
705 begin
706 flexTotal += lc.tempFlex;
707 flexBoxCount += 1;
708 end;
709 end;
711 // distribute space
712 cury := me.margins.top;
713 //writeln('me: ', boxidx, '; margins: ', me.margins.toString);
714 for lc in forChildren(boxidx) do
715 begin
716 osz := lc.desiredsize;
717 lc.desiredsize := lc.startsize;
718 lc.desiredpos.x := me.margins.left;
719 lc.desiredpos.y := cury;
720 cury += lc.desiredsize.h;
721 // fix flexbox size
722 if (lc.tempFlex > 0) and (spaceLeft > 0) then
723 begin
724 toadd := trunc(spaceLeft*lc.tempFlex/flexTotal+0.5);
725 if (toadd > 0) then
726 begin
727 // size changed
728 lc.desiredsize.h += toadd;
729 cury += toadd;
730 // compensate (crudely) rounding errors
731 if (cury > me.desiredsize.h-me.margins.vert) then begin lc.desiredsize.h -= 1; cury -= 1; end;
732 end;
733 end;
734 // expand or align
735 if (lc.expand) then lc.desiredsize.w := nmin(lc.maxsize.w, me.desiredsize.w-me.margins.vert) // expand
736 else if (lc.aligndir > 0) then lc.desiredpos.x := me.desiredsize.w-me.margins.right-lc.desiredsize.w // right align
737 else if (lc.aligndir = 0) then lc.desiredpos.x := (me.desiredsize.w-lc.desiredsize.w) div 2; // center
738 if (not osz.equals(lc.desiredsize)) then
739 begin
740 if (lc.inGroup) then groupElementChanged := true;
741 // relayout children
742 layBox(lc.firstChild);
743 end;
744 end;
745 end;
746 end;
749 (*
750 second pass:
751 calcluate desired sizes (process flexes) using 'startsize', set 'desiredsize' and 'desiredpos'
752 if control has children, call 'second pass' recursively with this control as parent
753 flags set:
754 'group-element-changed', if any group element size was changed
755 'wrapping-changed', if not 'firsttime', and wrapping was changed (i.e. first pass will not set the flag)
756 *)
757 procedure TFlexLayouterBase.secondPass ();
758 begin
759 // reset flags
760 groupElementChanged := false;
761 wrappingChanged := false;
763 if (Length(ctlist) > 0) then
764 begin
765 ctlist[0].desiredpos := TLayPos.Create(0, 0);
766 layBox(0);
767 end;
769 // fix 'wrapping-changed' flag
770 if (firstTime) then begin wrappingChanged := false; firstTime := false; end;
771 end;
774 (*
775 third pass:
776 if 'group-element-changed':
777 for each group: adjust controls to max desired size (startsize), set 'temp-flex' flags to 0 for 'em, set 'second-again' flag
778 for other controls: if 'desiredsize' > 'maxsize', set 'startsize' to 'maxsize', set 'temp-flex' flag to 0, set 'second-again' flag
779 if 'second-again' or 'wrapping-changed':
780 reset 'second-again'
781 reset 'wrapping-changed'
782 reset 'firsttime'
783 goto second pass
784 *)
785 procedure TFlexLayouterBase.thirdPass ();
786 var
787 secondAgain: Boolean;
788 gtype: Integer;
789 maxsz: Integer;
790 grp: PLayGroup;
791 f, c: Integer;
792 cidx: LayControlIdx;
793 ct: PLayControl;
794 begin
795 while true do
796 begin
797 secondPass();
798 secondAgain := false;
799 if (groupElementChanged) then
800 begin
801 secondAgain := true;
802 // find max size for group, adjust 'startsize' controls to group max size
803 for gtype := 0 to 1 do
804 begin
805 for f := 0 to High(groups[gtype]) do
806 begin
807 grp := @groups[gtype][f];
808 maxsz := 0;
809 for c := 0 to High(grp.ctls) do
810 begin
811 cidx := grp.ctls[c];
812 ct := @ctlist[cidx];
813 ct.expand := false; // don't expand grouped controls anymore
814 if (maxsz < ct.startsize[gtype]) then maxsz := ct.startsize[gtype];
815 end;
816 for c := 0 to High(grp.ctls) do
817 begin
818 cidx := grp.ctls[c];
819 ct := @ctlist[cidx];
820 ct.startsize[gtype] := maxsz;
821 ct.desiredsize[gtype] := maxsz;
822 ct.tempFlex := 0; // don't change control size anymore
823 end;
824 end;
825 end;
826 end
827 else
828 begin
829 for f := 0 to High(ctlist) do
830 begin
831 ct := @ctlist[f];
832 if (ct.inGroup) then
833 begin
834 ct.expand := false; // don't expand grouped controls anymore
835 ct.tempFlex := 0; // don't change control size anymore
836 end;
837 for c := 0 to 1 do
838 begin
839 if (ct.maxsize[c] < 0) then continue;
840 if (ct.desiredsize[c] > ct.maxsize[c]) then
841 begin
842 //writeln('ctl #', f, '; dimension #', c, ': desired=', ctlist[f].desiredsize[c], '; max=', ctlist[f].maxsize[c]);
843 ct.startsize[c] := ct.maxsize[c];
844 ct.desiredsize[c] := ct.maxsize[c];
845 ct.tempFlex := 0; // don't change control size anymore
846 secondAgain := true;
847 end;
848 end;
849 end;
850 end;
851 if (not secondAgain) and (not wrappingChanged) then break;
852 firstTime := false;
853 end;
854 end;
857 (*
858 fourth pass:
859 set 'actualsize' and 'actualpos' to 'desiredsize' and 'desiredpos'
860 return
861 *)
862 procedure TFlexLayouterBase.fourthPass ();
863 var
864 f: Integer;
865 begin
866 for f := 0 to High(ctlist) do
867 begin
868 ctlist[f].ctl.setActualSizePos(ctlist[f].desiredpos, ctlist[f].desiredsize);
869 end;
870 end;
873 procedure TFlexLayouterBase.layout ();
874 begin
875 firstPass();
876 secondPass();
877 thirdPass();
878 fourthPass();
879 end;
882 procedure TFlexLayouterBase.dumpFlat ();
883 var
884 f: Integer;
885 lc: PLayControl;
886 ds, ms: TLaySize;
887 begin
888 for f := 0 to High(ctlist) do
889 begin
890 lc := @ctlist[f];
891 ds := lc.ctl.getDefSize;
892 ms := lc.ctl.getMaxSize;
893 writeln(lc.myidx, ': startsize:', lc.startsize.toString(), '; desiredsize=', lc.desiredsize.toString(), '; maxsize=', lc.maxsize.toString(), '; tempFlex=', lc.tempFlex, '; flags=', lc.flags,
894 '; parent=', lc.parent, '; next=', lc.nextSibling, '; child=', lc.firstChild, '; ctl.size=', ds.toString(), '; ctl.maxsize=', ms.toString());
895 end;
896 end;
899 procedure TFlexLayouterBase.dumpList (cidx: LayControlIdx; indent: Integer);
900 var
901 lc: PLayControl;
902 f: Integer;
903 begin
904 while (cidx >= 0) do
905 begin
906 lc := @ctlist[cidx];
907 for f := 0 to indent do write(' ');
908 writeln(lc.myidx, ': startsize:', lc.startsize.toString, '; desiredsize=', lc.desiredsize.toString, '; maxsize=', lc.maxsize.toString, '; tempFlex=', lc.tempFlex, '; despos=', lc.desiredpos.toString);
909 dumpList(lc.firstChild, indent+2);
910 cidx := lc.nextSibling;
911 end;
912 end;
915 procedure TFlexLayouterBase.dump ();
916 begin
917 dumpList(0, 0);
918 end;
921 // ////////////////////////////////////////////////////////////////////////// //
922 (*
923 void main () begin
924 auto win := new GuiControl();
925 (win ~= new GuiControl()).mSize := TLaySize(10, 5);
926 (win ~= new GuiControl()).mSize := TLaySize(16, 8);
928 //win.mSize := TLaySize(40, 20);
930 auto lay := TFlexLayouterBase!GuiControl();
931 lay.setup(win);
933 writeln('============================');
934 lay.dumpFlat();
936 writeln('=== initial ===');
937 lay.dump();
939 //lay.calcMaxSizeInternal(0);
940 /*
941 lay.firstPass();
942 writeln('=== after first pass ===');
943 lay.dump();
945 lay.secondPass();
946 writeln('=== after second pass ===');
947 lay.dump();
948 */
949 lay.layout();
950 writeln('=== final ===');
951 lay.dump();
952 *)
953 end.