1 {$INCLUDE ../shared/a_modes.inc}
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
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
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)
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':
26 reset 'wrapping-changed'
31 set 'actualsize' and 'actualpos' to 'desiredsize' and 'desiredpos'
35 set 'startsize' to max(size, maxsize, 0)
36 if 'size' is negative:
37 set 'temp-flex' flag to 0
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)
44 set 'desiredmax' to min(desiredmax, maxsize)
45 set 'startsize' to 'desiredmax'
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
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
60 on redoing second pass, if 'laywrap' flag changed, set 'wrapping-changed' flag
65 control default size will be increased by margins
66 negative margins are ignored
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;
91 // ////////////////////////////////////////////////////////////////////////// //
93 generic TFlexLayouterBase
<ControlT
> = class
98 type LayControlIdx
= Integer;
101 class function nminX (a
, b
: Integer): Integer; inline;
106 FlagHorizBox
= LongWord(1) shl 0; // horizontal layout for children
107 FlagLineStart
= LongWord(1) shl 1;
108 FlagLineCanWrap
= LongWord(1) shl 2;
110 FlagLineDidWrap
= LongWord(1) shl 3; // will be set when line was wrapped
111 FlagInGroup
= LongWord(1) shl 4; // set if this control is a member of any group
112 FlagExpand
= LongWord(1) shl 5;
113 FlagLineFirst
= LongWord(1) shl 6;
117 PLayControl
= ^TLayControl
;
120 myidx
: LayControlIdx
;
122 flags
: LongWord; // see below
124 startsize
: TLaySize
; // current
125 desiredsize
: TLaySize
;
127 margins
: TLayMargins
; // can never be negative
130 parent
: LayControlIdx
; // = -1;
131 firstChild
: LayControlIdx
; // = -1;
132 nextSibling
: LayControlIdx
; // = -1;
135 function getDidWrap (): Boolean; inline;
136 procedure setDidWrap (v
: Boolean); inline;
139 procedure initialize (); inline;
141 function horizBox (): Boolean; inline;
142 function lineStart (): Boolean; inline;
143 function canWrap (): Boolean; inline;
144 function inGroup (): Boolean; inline;
145 function firstInLine (): Boolean; inline;
147 function getExpand (): Boolean; inline;
148 procedure setExpand (v
: Boolean); inline;
150 function alignLeft (): Boolean; inline;
151 function alignTop (): Boolean; inline;
152 function alignRight (): Boolean; inline;
153 function alignBottom (): Boolean; inline;
154 function alignCenter (): Boolean; inline;
157 property didWrap
: Boolean read getDidWrap write setDidWrap
;
158 property expand
: Boolean read getExpand write setExpand
;
161 PLayGroup
= ^TLayGroup
;
164 ctls
: array of LayControlIdx
;
167 TLayCtlArray
= array of TLayControl
;
168 TLayGrpArray
= array of TLayGroup
;
171 ctlist
: TLayCtlArray
;
172 groups
: array[0..1] of TLayGrpArray
; // horiz, vert
175 groupElementChanged
: Boolean;
176 wrappingChanged
: Boolean;
179 procedure fixFlags (cidx
: LayControlIdx
);
180 procedure doChildren (parent
: LayControlIdx
; child
: ControlT
);
181 procedure appendToGroup (const gname
: AnsiString;cidx
: LayControlIdx
;gidx
: Integer);
182 procedure setupGroups ();
184 // this also sets `tempFlex`
185 procedure calcMaxSizeInternal (cidx
: LayControlIdx
);
187 procedure fixLine (me
: PLayControl
; i0
, i1
: LayControlIdx
; var cury
: Integer; var spaceLeft
: Single);
188 // do box layouting; call `layBox()` recursively if necessary
189 procedure layBox (boxidx
: LayControlIdx
);
191 procedure firstPass ();
192 procedure secondPass ();
193 procedure thirdPass ();
194 procedure fourthPass ();
196 procedure dumpList (cidx
: LayControlIdx
; indent
: Integer);
200 TChildrenEnumerator
= record
206 constructor Create (constref actls
: TLayCtlArray
; acur
: Integer);
207 function moveNext (): Boolean; inline;
208 function getCurrent (): PLayControl
; inline;
209 function getEnumerator (): TChildrenEnumerator
; inline;
210 property current
: PLayControl read getCurrent
;
214 constructor Create ();
215 destructor Destroy (); override;
220 // build control and group lists
221 procedure setup (root
: ControlT
);
223 function forChildren (cidx
: LayControlIdx
): TChildrenEnumerator
; inline;
227 procedure dumpFlat ();
238 // ////////////////////////////////////////////////////////////////////////// //
239 class function TFlexLayouterBase
.nminX (a
, b
: Integer): Integer; inline;
241 if (a
< 0) then begin if (b
< 0) then result
:= 0 else result
:= b
; end
242 else if (b
< 0) or (a
< b
) then result
:= a
247 // ////////////////////////////////////////////////////////////////////////// //
248 procedure TFlexLayouterBase
.TLayControl
.initialize (); inline;
250 FillChar(self
, 0, sizeof(self
));
256 function TFlexLayouterBase
.TLayControl
.horizBox (): Boolean; inline; begin result
:= ((flags
and FlagHorizBox
) <> 0); end;
257 function TFlexLayouterBase
.TLayControl
.lineStart (): Boolean; inline; begin result
:= ((flags
and FlagLineStart
) <> 0); end;
258 function TFlexLayouterBase
.TLayControl
.canWrap (): Boolean; inline; begin result
:= ((flags
and FlagLineCanWrap
) <> 0); end;
259 function TFlexLayouterBase
.TLayControl
.inGroup (): Boolean; inline; begin result
:= ((flags
and FlagInGroup
) <> 0); end;
260 function TFlexLayouterBase
.TLayControl
.firstInLine (): Boolean; inline; begin result
:= ((flags
and FlagLineFirst
) <> 0); end;
262 function TFlexLayouterBase
.TLayControl
.getDidWrap (): Boolean; inline; begin result
:= ((flags
and FlagLineDidWrap
) <> 0); end;
263 procedure TFlexLayouterBase
.TLayControl
.setDidWrap (v
: Boolean); inline; begin if (v
) then flags
:= flags
or FlagLineDidWrap
else flags
:= flags
and (not FlagLineDidWrap
); end;
265 function TFlexLayouterBase
.TLayControl
.getExpand (): Boolean; inline; begin result
:= ((flags
and FlagExpand
) <> 0); end;
266 procedure TFlexLayouterBase
.TLayControl
.setExpand (v
: Boolean); inline; begin if (v
) then flags
:= flags
or FlagExpand
else flags
:= flags
and (not FlagExpand
); end;
268 function TFlexLayouterBase
.TLayControl
.alignLeft (): Boolean; inline; begin result
:= (aligndir
< 0); end;
269 function TFlexLayouterBase
.TLayControl
.alignTop (): Boolean; inline; begin result
:= (aligndir
< 0); end;
270 function TFlexLayouterBase
.TLayControl
.alignRight (): Boolean; inline; begin result
:= (aligndir
> 0); end;
271 function TFlexLayouterBase
.TLayControl
.alignBottom (): Boolean; inline; begin result
:= (aligndir
> 0); end;
272 function TFlexLayouterBase
.TLayControl
.alignCenter (): Boolean; inline; begin result
:= (aligndir
= 0); end;
275 // ////////////////////////////////////////////////////////////////////////// //
276 constructor TFlexLayouterBase
.TChildrenEnumerator
.Create (constref actls
: TLayCtlArray
; acur
: Integer);
283 function TFlexLayouterBase
.TChildrenEnumerator
.moveNext (): Boolean; inline;
287 if (cur
>= 0) and (cur
< Length(ctls
)) then cur
:= ctls
[cur
].firstChild
else cur
:= -1;
292 cur
:= ctls
[cur
].nextSibling
;
294 result
:= (cur
>= 0);
297 function TFlexLayouterBase
.TChildrenEnumerator
.getCurrent (): PLayControl
; inline;
299 result
:= @ctls
[cur
];
302 function TFlexLayouterBase
.TChildrenEnumerator
.getEnumerator (): TChildrenEnumerator
; inline;
308 // ////////////////////////////////////////////////////////////////////////// //
309 constructor TFlexLayouterBase
.Create ();
316 groupElementChanged
:= false;
317 wrappingChanged
:= false;
321 destructor TFlexLayouterBase
.Destroy ();
328 function TFlexLayouterBase
.forChildren (cidx
: LayControlIdx
): TChildrenEnumerator
; inline;
330 result
:= TChildrenEnumerator
.Create(ctlist
, cidx
);
334 procedure TFlexLayouterBase
.clear ();
342 procedure TFlexLayouterBase
.fixFlags (cidx
: LayControlIdx
);
346 assert((cidx
>= 0) and (cidx
< Length(ctlist
)));
349 if (lc
.ctl
.isHorizBox
) then lc
.flags
:= lc
.flags
or FlagHorizBox
;
350 if (lc
.ctl
.isLineStart
) then lc
.flags
:= lc
.flags
or FlagLineStart
;
351 if (lc
.ctl
.canWrap
) then lc
.flags
:= lc
.flags
or FlagLineCanWrap
;
352 if (lc
.ctl
.getExpand
) then lc
.flags
:= lc
.flags
or FlagExpand
;
353 lc
.aligndir
:= lc
.ctl
.getAlign
;
357 procedure TFlexLayouterBase
.doChildren (parent
: LayControlIdx
; child
: ControlT
);
359 cidx
: LayControlIdx
= -1;
362 assert((parent
>= 0) and (parent
< Length(ctlist
)));
363 assert(ctlist
[parent
].firstChild
= -1);
364 while (child
<> nil) do
367 SetLength(ctlist
, Length(ctlist
)+1);
368 lc
:= @ctlist
[High(ctlist
)];
372 cidx
:= LayControlIdx(High(ctlist
));
373 ctlist
[parent
].firstChild
:= cidx
;
374 // first child is always linestart
375 lc
.flags
:= lc
.flags
or FlagLineStart
or FlagLineFirst
;
379 ctlist
[cidx
].nextSibling
:= LayControlIdx(High(ctlist
));
380 cidx
:= LayControlIdx(High(ctlist
));
386 doChildren(cidx
, child
.firstChild
);
387 child
:= child
.nextSibling
;
392 procedure TFlexLayouterBase
.appendToGroup (const gname
: AnsiString; cidx
: LayControlIdx
; gidx
: Integer);
396 if (Length(gname
) = 0) then exit
;
397 assert((cidx
>= 0) and (cidx
< Length(ctlist
)));
398 assert((gidx
= 0) or (gidx
= 1));
399 ctlist
[cidx
].flags
:= ctlist
[cidx
].flags
or FlagInGroup
;
400 for f
:= 0 to High(groups
[gidx
]) do
402 if (groups
[gidx
][f
].name
= gname
) then
404 SetLength(groups
[gidx
][f
].ctls
, Length(groups
[gidx
][f
].ctls
)+1);
405 groups
[gidx
][f
].ctls
[High(groups
[gidx
][f
].ctls
)] := cidx
;
410 f
:= Length(groups
[gidx
]);
411 SetLength(groups
[gidx
], f
+1);
412 groups
[gidx
][f
].name
:= gname
;
413 SetLength(groups
[gidx
][f
].ctls
, Length(groups
[gidx
][f
].ctls
)+1);
414 groups
[gidx
][f
].ctls
[High(groups
[gidx
][f
].ctls
)] := cidx
;
418 procedure TFlexLayouterBase
.setupGroups ();
423 for idx
:= 0 to High(ctlist
) do
426 appendToGroup(lc
.ctl
.getHGroup
, LayControlIdx(idx
), 0);
427 appendToGroup(lc
.ctl
.getVGroup
, LayControlIdx(idx
), 1);
432 // build control and group lists
433 procedure TFlexLayouterBase
.setup (root
: ControlT
);
436 if (root
= nil) then exit
;
439 SetLength(ctlist
, 1);
440 ctlist
[0].initialize();
441 ctlist
[0].myidx
:= 0;
442 ctlist
[0].ctl
:= root
;
444 ctlist
[0].flags
:= ctlist
[0].flags
or FlagLineStart
or FlagLineFirst
;
445 doChildren(0, root
.firstChild
);
454 // this also sets `tempFlex`
455 procedure TFlexLayouterBase
.calcMaxSizeInternal (cidx
: LayControlIdx
);
461 curwdt
, curhgt
, totalhgt
: Integer;
464 if (cidx
< 0) or (cidx
>= Length(ctlist
)) then exit
;
467 msz
:= lc
.ctl
.getMaxSize
;
468 negw
:= (lc
.startsize
.w
< 0);
469 negh
:= (lc
.startsize
.h
< 0);
470 zerow
:= (lc
.startsize
.w
= 0);
472 lc
.tempFlex
:= lc
.ctl
.getFlex
;
474 for c
in forChildren(cidx
) do calcMaxSizeInternal(c
.myidx
);
476 if (lc
.horizBox
) then
479 if (negw
) then lc
.tempFlex
:= 0; // size is negative: don't expand
480 curwdt
:= lc
.margins
.horiz
;
482 totalhgt
:= lc
.margins
.vert
;
483 for c
in forChildren(cidx
) do
486 doWrap
:= (not c
.firstInLine
) and (c
.lineStart
);
488 if (not doWrap
) and zerow
and (lc
.canWrap
) and (c
.canWrap
) and (msz
.w
> 0) and (curwdt
+c
.startsize
.w
> lc
.startsize
.w
) then doWrap
:= true;
492 if (lc
.startsize
.w
< curwdt
) then lc
.startsize
.w
:= curwdt
;
496 curwdt
+= c
.startsize
.w
;
497 if (curhgt
< c
.startsize
.h
) then curhgt
:= c
.startsize
.h
;
499 //writeln('00: ', cidx, ': totalhgt=', totalhgt);
501 //writeln('01: ', cidx, ': totalhgt=', totalhgt);
502 if (lc
.startsize
.w
< curwdt
) then lc
.startsize
.w
:= curwdt
;
503 if (lc
.startsize
.h
< totalhgt
) then lc
.startsize
.h
:= totalhgt
;
508 if (negh
) then lc
.tempFlex
:= 0; // size is negative: don't expand
509 curhgt
:= lc
.margins
.vert
;
510 for c
in forChildren(cidx
) do
512 if (lc
.startsize
.w
< c
.startsize
.w
+lc
.margins
.horiz
) then lc
.startsize
.w
:= c
.startsize
.w
+lc
.margins
.horiz
;
513 curhgt
+= c
.startsize
.h
;
515 if (lc
.startsize
.h
< curhgt
) then lc
.startsize
.h
:= curhgt
;
517 if (lc
.startsize
.w
< 0) then lc
.startsize
.w
:= 0;
518 if (lc
.startsize
.h
< 0) then lc
.startsize
.h
:= 0;
520 if (lc
.maxsize
.w
< lc
.startsize
.w
) then begin if (lc
.maxsize
.w
>= 0) then lc
.maxsize
.w
:= lc
.startsize
.w
; end;
521 if (lc
.maxsize
.h
< lc
.startsize
.h
) then begin if (lc
.maxsize
.h
>= 0) then lc
.maxsize
.h
:= lc
.startsize
.h
; end;
525 procedure TFlexLayouterBase
.firstPass ();
528 needRecalcMaxSize
: Boolean;
536 // reset all 'laywrap' flags for controls, set initial 'startsize'
537 for f
:= 0 to High(ctlist
) do
539 ctlist
[f
].didWrap
:= false;
540 ctlist
[f
].startsize
:= ctlist
[f
].ctl
.getDefSize
;
541 mr
:= ctlist
[f
].ctl
.getMargins
;
542 ctlist
[f
].margins
:= mr
;
543 //ctlist[f].startsize.w += mr.horiz;
544 //ctlist[f].startsize.h += mr.vert;
547 calcMaxSizeInternal(0); // this also sets `tempFlex`
548 //writeln('=== calculated max size (0) ==='); dump();
549 // find max size for group, adjust 'startsize' controls to group max size
550 needRecalcMaxSize
:= false;
551 for gtype
:= 0 to 1 do
553 for f
:= 0 to High(groups
[gtype
]) do
555 grp
:= @groups
[gtype
][f
];
557 for c
:= 0 to High(grp
.ctls
) do
561 if (maxsz
< ct
.startsize
[gtype
]) then maxsz
:= ct
.startsize
[gtype
];
563 for c
:= 0 to High(grp
.ctls
) do
567 if (maxsz
<> ct
.startsize
[gtype
]) then
569 needRecalcMaxSize
:= true;
570 ct
.startsize
[gtype
] := maxsz
;
575 // recalc maxsize if necessary
576 if (needRecalcMaxSize
) then calcMaxSizeInternal(0);
577 // set "desired size" to "start size"
578 for f
:= 0 to High(ctlist
) do ctlist
[f
].desiredsize
:= ctlist
[f
].startsize
;
581 //writeln('=== calculated max size (final) ==='); dump();
585 procedure TFlexLayouterBase
.fixLine (me
: PLayControl
; i0
, i1
: LayControlIdx
; var cury
: Integer; var spaceLeft
: Single);
587 flexTotal
: Integer = 0; // total sum of flex fields
588 flexBoxCount
: Integer = 0; // number of boxes
596 curx
:= me
.margins
.left
;
598 // calc minimal line height, count flexboxes
603 lineh
:= nmax(lineh
, lc
.startsize
.h
);
604 if (lc
.tempFlex
> 0) then begin flexTotal
+= lc
.tempFlex
; flexBoxCount
+= 1; end;
605 i0
:= lc
.nextSibling
;
607 // distribute space, expand/align
612 osz
:= lc
.desiredsize
;
613 lc
.desiredsize
:= lc
.startsize
;
614 lc
.desiredpos
.x
:= curx
;
615 lc
.desiredpos
.y
:= cury
;
616 curx
+= lc
.desiredsize
.w
;
618 if (lc
.tempFlex
> 0) and (spaceLeft
> 0) then
620 toadd
:= trunc(spaceLeft
*lc
.tempFlex
/flexTotal
+0.5);
624 lc
.desiredsize
.w
+= toadd
;
626 // compensate (crudely) rounding errors
627 if (curx
> me
.desiredsize
.w
-me
.margins
.horiz
) then begin lc
.desiredsize
.w
-= 1; curx
-= 1; end;
629 layBox(lc
.firstChild
);
633 if (lc
.expand
) then lc
.desiredsize
.h
:= nminX(lc
.maxsize
.h
, lineh
) // expand
634 else if (lc
.alignBottom
) then lc
.desiredpos
.y
:= cury
+(lineh
-lc
.desiredsize
.h
) // bottom align
635 else if (lc
.alignCenter
) then lc
.desiredpos
.y
:= cury
+(lineh
-lc
.desiredsize
.h
) div 2; // center
636 if (not osz
.equals(lc
.desiredsize
)) then
638 if (lc
.inGroup
) then groupElementChanged
:= true;
640 layBox(lc
.firstChild
);
642 i0
:= lc
.nextSibling
;
646 spaceLeft
:= me
.desiredsize
.w
-me
.margins
.horiz
;
651 // do box layouting; call `layBox()` recursively if necessary
652 procedure TFlexLayouterBase
.layBox (boxidx
: LayControlIdx
);
655 flexTotal
: Integer; // total sum of flex fields
656 flexBoxCount
: Integer; // number of boxes
660 lineStartIdx
: LayControlIdx
;
666 if (boxidx
< 0) or (boxidx
>= Length(ctlist
)) then exit
;
667 me
:= @ctlist
[boxidx
];
669 // if we have no children, there's nothing to do
670 if (me
.firstChild
= -1) then exit
;
672 // first, layout all children
673 for lc
in forChildren(boxidx
) do layBox(lc
.myidx
);
675 // second, layout lines, distribute flex data
676 if (me
.horizBox
) then
679 cury
:= me
.margins
.top
;
681 fixLine(me
, -1, -1, cury
, spaceLeft
); //HACK!
683 lineStartIdx
:= me
.firstChild
;
684 for lc
in forChildren(boxidx
) do
687 doWrap
:= (not lc
.firstInLine
) and (lc
.lineStart
);
689 if (not doWrap
) and (lc
.canWrap
) and (lc
.canWrap
) and (lc
.desiredsize
.w
> 0) and (spaceLeft
< lc
.desiredsize
.w
) then doWrap
:= true;
692 // new line, fix this one
693 if (not lc
.didWrap
) then begin wrappingChanged
:= true; lc
.didWrap
:= true; end;
694 fixLine(me
, lineStartIdx
, lc
.myidx
, cury
, spaceLeft
);
695 lineStartIdx
:= lc
.myidx
;
699 if (lc
.didWrap
) then begin wrappingChanged
:= true; lc
.didWrap
:= false; end;
701 spaceLeft
-= lc
.desiredsize
.w
;
702 //if (maxhgt < lc.desiredsize.h) then maxhgt := lc.desiredsize.h;
703 //if (lc.tempFlex > 0) then begin flexTotal += lc.tempFlex; flexBoxCount += 1; end;
706 fixLine(me
, lineStartIdx
, -1, cury
, spaceLeft
);
714 spaceLeft
:= me
.desiredsize
.h
-me
.margins
.vert
;
717 for lc
in forChildren(boxidx
) do
719 spaceLeft
-= lc
.desiredsize
.h
;
720 if (maxwdt
< lc
.desiredsize
.w
) then maxwdt
:= lc
.desiredsize
.w
;
721 if (lc
.tempFlex
> 0) then begin flexTotal
+= lc
.tempFlex
; flexBoxCount
+= 1; end;
725 cury
:= me
.margins
.top
;
726 //writeln('me: ', boxidx, '; margins: ', me.margins.toString);
727 for lc
in forChildren(boxidx
) do
729 osz
:= lc
.desiredsize
;
730 lc
.desiredsize
:= lc
.startsize
;
731 lc
.desiredpos
.x
:= me
.margins
.left
;
732 lc
.desiredpos
.y
:= cury
;
733 cury
+= lc
.desiredsize
.h
;
735 if (lc
.tempFlex
> 0) and (spaceLeft
> 0) then
737 toadd
:= trunc(spaceLeft
*lc
.tempFlex
/flexTotal
+0.5);
741 lc
.desiredsize
.h
+= toadd
;
743 // compensate (crudely) rounding errors
744 if (cury
> me
.desiredsize
.h
-me
.margins
.vert
) then begin lc
.desiredsize
.h
-= 1; cury
-= 1; end;
748 if (lc
.expand
) then lc
.desiredsize
.w
:= nminX(lc
.maxsize
.w
, me
.desiredsize
.w
-me
.margins
.vert
) // expand
749 else if (lc
.alignRight
) then lc
.desiredpos
.x
:= me
.desiredsize
.w
-me
.margins
.right
-lc
.desiredsize
.w
// right align
750 else if (lc
.alignCenter
) then lc
.desiredpos
.x
:= (me
.desiredsize
.w
-lc
.desiredsize
.w
) div 2; // center
751 if (not osz
.equals(lc
.desiredsize
)) then
753 if (lc
.inGroup
) then groupElementChanged
:= true;
755 layBox(lc
.firstChild
);
764 calcluate desired sizes (process flexes) using 'startsize', set 'desiredsize' and 'desiredpos'
765 if control has children, call 'second pass' recursively with this control as parent
767 'group-element-changed', if any group element size was changed
768 'wrapping-changed', if not 'firsttime', and wrapping was changed (i.e. first pass will not set the flag)
770 procedure TFlexLayouterBase
.secondPass ();
773 groupElementChanged
:= false;
774 wrappingChanged
:= false;
776 if (Length(ctlist
) > 0) then
778 ctlist
[0].desiredpos
:= TLayPos
.Create(0, 0);
782 // fix 'wrapping-changed' flag
783 if (firstTime
) then begin wrappingChanged
:= false; firstTime
:= false; end;
789 if 'group-element-changed':
790 for each group: adjust controls to max desired size (startsize), set 'temp-flex' flags to 0 for 'em, set 'second-again' flag
791 for other controls: if 'desiredsize' > 'maxsize', set 'startsize' to 'maxsize', set 'temp-flex' flag to 0, set 'second-again' flag
792 if 'second-again' or 'wrapping-changed':
794 reset 'wrapping-changed'
798 procedure TFlexLayouterBase
.thirdPass ();
800 secondAgain
: Boolean;
811 secondAgain
:= false;
812 if (groupElementChanged
) then
815 // find max size for group, adjust 'startsize' controls to group max size
816 for gtype
:= 0 to 1 do
818 for f
:= 0 to High(groups
[gtype
]) do
820 grp
:= @groups
[gtype
][f
];
822 for c
:= 0 to High(grp
.ctls
) do
826 ct
.expand
:= false; // don't expand grouped controls anymore
827 if (maxsz
< ct
.startsize
[gtype
]) then maxsz
:= ct
.startsize
[gtype
];
829 for c
:= 0 to High(grp
.ctls
) do
833 ct
.startsize
[gtype
] := maxsz
;
834 ct
.desiredsize
[gtype
] := maxsz
;
835 ct
.tempFlex
:= 0; // don't change control size anymore
842 for f
:= 0 to High(ctlist
) do
847 ct
.expand
:= false; // don't expand grouped controls anymore
848 ct
.tempFlex
:= 0; // don't change control size anymore
852 if (ct
.maxsize
[c
] < 0) then continue
;
853 if (ct
.desiredsize
[c
] > ct
.maxsize
[c
]) then
855 //writeln('ctl #', f, '; dimension #', c, ': desired=', ctlist[f].desiredsize[c], '; max=', ctlist[f].maxsize[c]);
856 ct
.startsize
[c
] := ct
.maxsize
[c
];
857 ct
.desiredsize
[c
] := ct
.maxsize
[c
];
858 ct
.tempFlex
:= 0; // don't change control size anymore
864 if (not secondAgain
) and (not wrappingChanged
) then break
;
872 set 'actualsize' and 'actualpos' to 'desiredsize' and 'desiredpos'
875 procedure TFlexLayouterBase
.fourthPass ();
879 for f
:= 0 to High(ctlist
) do
881 ctlist
[f
].ctl
.setActualSizePos(ctlist
[f
].desiredpos
, ctlist
[f
].desiredsize
);
886 procedure TFlexLayouterBase
.layout ();
895 procedure TFlexLayouterBase
.dumpFlat ();
901 for f
:= 0 to High(ctlist
) do
904 ds
:= lc
.ctl
.getDefSize
;
905 ms
:= lc
.ctl
.getMaxSize
;
906 writeln(lc
.myidx
, ': startsize:', lc
.startsize
.toString(), '; desiredsize=', lc
.desiredsize
.toString(), '; maxsize=', lc
.maxsize
.toString(), '; tempFlex=', lc
.tempFlex
, '; flags=', lc
.flags
,
907 '; parent=', lc
.parent
, '; next=', lc
.nextSibling
, '; child=', lc
.firstChild
, '; ctl.size=', ds
.toString(), '; ctl.maxsize=', ms
.toString());
912 procedure TFlexLayouterBase
.dumpList (cidx
: LayControlIdx
; indent
: Integer);
920 for f
:= 0 to indent
do write(' ');
921 writeln(lc
.myidx
, ': startsize:', lc
.startsize
.toString
, '; desiredsize=', lc
.desiredsize
.toString
, '; maxsize=', lc
.maxsize
.toString
, '; tempFlex=', lc
.tempFlex
, '; despos=', lc
.desiredpos
.toString
);
922 dumpList(lc
.firstChild
, indent
+2);
923 cidx
:= lc
.nextSibling
;
928 procedure TFlexLayouterBase
.dump ();
934 // ////////////////////////////////////////////////////////////////////////// //
937 auto win := new GuiControl();
938 (win ~= new GuiControl()).mSize := TLaySize(10, 5);
939 (win ~= new GuiControl()).mSize := TLaySize(16, 8);
941 //win.mSize := TLaySize(40, 20);
943 auto lay := TFlexLayouterBase!GuiControl();
946 writeln('============================');
949 writeln('=== initial ===');
952 //lay.calcMaxSizeInternal(0);
955 writeln('=== after first pass ===');
959 writeln('=== after second pass ===');
963 writeln('=== final ===');