DEADSOFTWARE

9692f0d1353f0319fb407204f7feb37dcf8338de
[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 function alignLeft (): Boolean; inline;
148 function alignTop (): Boolean; inline;
149 function alignRight (): Boolean; inline;
150 function alignBottom (): Boolean; inline;
151 function alignCenter (): Boolean; inline;
153 public
154 property didWrap: Boolean read getDidWrap write setDidWrap;
155 property expand: Boolean read getExpand write setExpand;
156 end;
158 PLayGroup = ^TLayGroup;
159 TLayGroup = record
160 name: AnsiString;
161 ctls: array of LayControlIdx;
162 end;
164 TLayCtlArray = array of TLayControl;
165 TLayGrpArray = array of TLayGroup;
167 private
168 ctlist: TLayCtlArray;
169 groups: array[0..1] of TLayGrpArray; // horiz, vert
171 firstTime: Boolean;
172 groupElementChanged: Boolean;
173 wrappingChanged: Boolean;
175 private
176 procedure fixFlags (cidx: LayControlIdx);
177 procedure doChildren (parent: LayControlIdx; child: ControlT);
178 procedure appendToGroup (const gname: AnsiString;cidx: LayControlIdx;gidx: Integer);
179 procedure setupGroups ();
181 // this also sets `tempFlex`
182 procedure calcMaxSizeInternal (cidx: LayControlIdx);
184 procedure fixLine (me: PLayControl; i0, i1: LayControlIdx; var cury: Integer; var spaceLeft: Single);
185 // do box layouting; call `layBox()` recursively if necessary
186 procedure layBox (boxidx: LayControlIdx);
188 procedure firstPass ();
189 procedure secondPass ();
190 procedure thirdPass ();
191 procedure fourthPass ();
193 procedure dumpList (cidx: LayControlIdx; indent: Integer);
195 public
196 type
197 TChildrenEnumerator = record
198 private
199 ctls: TLayCtlArray;
200 cur: Integer;
201 first: Boolean;
202 public
203 constructor Create (constref actls: TLayCtlArray; acur: Integer);
204 function moveNext (): Boolean; inline;
205 function getCurrent (): PLayControl; inline;
206 function getEnumerator (): TChildrenEnumerator; inline;
207 property current: PLayControl read getCurrent;
208 end;
210 public
211 constructor Create ();
212 destructor Destroy (); override;
214 // clear build lists
215 procedure clear ();
217 // build control and group lists
218 procedure setup (root: ControlT);
220 function forChildren (cidx: LayControlIdx): TChildrenEnumerator; inline;
222 procedure layout ();
224 procedure dumpFlat ();
225 procedure dump ();
226 end;
229 implementation
231 uses
232 utils;
235 // ////////////////////////////////////////////////////////////////////////// //
236 procedure TFlexLayouterBase.TLayControl.initialize (); inline;
237 begin
238 FillChar(self, 0, sizeof(self));
239 parent := -1;
240 firstChild := -1;
241 nextSibling := -1;
242 end;
244 function TFlexLayouterBase.TLayControl.horizBox (): Boolean; inline; begin result := ((flags and FlagHorizBox) <> 0); end;
245 function TFlexLayouterBase.TLayControl.lineStart (): Boolean; inline; begin result := ((flags and FlagLineStart) <> 0); end;
246 function TFlexLayouterBase.TLayControl.canWrap (): Boolean; inline; begin result := ((flags and FlagLineCanWrap) <> 0); end;
247 function TFlexLayouterBase.TLayControl.inGroup (): Boolean; inline; begin result := ((flags and FlagInGroup) <> 0); end;
248 function TFlexLayouterBase.TLayControl.firstInLine (): Boolean; inline; begin result := ((flags and FlagLineFirst) <> 0); end;
250 function TFlexLayouterBase.TLayControl.getDidWrap (): Boolean; inline; begin result := ((flags and FlagLineDidWrap) <> 0); end;
251 procedure TFlexLayouterBase.TLayControl.setDidWrap (v: Boolean); inline; begin if (v) then flags := flags or FlagLineDidWrap else flags := flags and (not FlagLineDidWrap); end;
253 function TFlexLayouterBase.TLayControl.getExpand (): Boolean; inline; begin result := ((flags and FlagExpand) <> 0); end;
254 procedure TFlexLayouterBase.TLayControl.setExpand (v: Boolean); inline; begin if (v) then flags := flags or FlagExpand else flags := flags and (not FlagExpand); end;
256 function TFlexLayouterBase.TLayControl.alignLeft (): Boolean; inline; begin result := (aligndir < 0); end;
257 function TFlexLayouterBase.TLayControl.alignTop (): Boolean; inline; begin result := (aligndir < 0); end;
258 function TFlexLayouterBase.TLayControl.alignRight (): Boolean; inline; begin result := (aligndir > 0); end;
259 function TFlexLayouterBase.TLayControl.alignBottom (): Boolean; inline; begin result := (aligndir > 0); end;
260 function TFlexLayouterBase.TLayControl.alignCenter (): Boolean; inline; begin result := (aligndir = 0); end;
263 // ////////////////////////////////////////////////////////////////////////// //
264 constructor TFlexLayouterBase.TChildrenEnumerator.Create (constref actls: TLayCtlArray; acur: Integer);
265 begin
266 ctls := actls;
267 cur := acur;
268 first := true;
269 end;
271 function TFlexLayouterBase.TChildrenEnumerator.moveNext (): Boolean; inline;
272 begin
273 if first then
274 begin
275 if (cur >= 0) and (cur < Length(ctls)) then cur := ctls[cur].firstChild else cur := -1;
276 first := false;
277 end
278 else
279 begin
280 cur := ctls[cur].nextSibling;
281 end;
282 result := (cur >= 0);
283 end;
285 function TFlexLayouterBase.TChildrenEnumerator.getCurrent (): PLayControl; inline;
286 begin
287 result := @ctls[cur];
288 end;
290 function TFlexLayouterBase.TChildrenEnumerator.getEnumerator (): TChildrenEnumerator; inline;
291 begin
292 result := self;
293 end;
296 // ////////////////////////////////////////////////////////////////////////// //
297 constructor TFlexLayouterBase.Create ();
298 begin
299 ctlist := nil;
300 groups[0] := nil;
301 groups[1] := nil;
303 firstTime := false;
304 groupElementChanged := false;
305 wrappingChanged := false;
306 end;
309 destructor TFlexLayouterBase.Destroy ();
310 begin
311 clear();
312 inherited;
313 end;
316 function TFlexLayouterBase.forChildren (cidx: LayControlIdx): TChildrenEnumerator; inline;
317 begin
318 result := TChildrenEnumerator.Create(ctlist, cidx);
319 end;
322 procedure TFlexLayouterBase.clear ();
323 begin
324 ctlist := nil;
325 groups[0] := nil;
326 groups[1] := nil;
327 end;
330 procedure TFlexLayouterBase.fixFlags (cidx: LayControlIdx);
331 var
332 lc: PLayControl;
333 begin
334 assert((cidx >= 0) and (cidx < Length(ctlist)));
335 lc := @ctlist[cidx];
336 //lc.flags := 0;
337 if (lc.ctl.isHorizBox) then lc.flags := lc.flags or FlagHorizBox;
338 if (lc.ctl.isLineStart) then lc.flags := lc.flags or FlagLineStart;
339 if (lc.ctl.canWrap) then lc.flags := lc.flags or FlagLineCanWrap;
340 if (lc.ctl.getExpand) then lc.flags := lc.flags or FlagExpand;
341 lc.aligndir := lc.ctl.getAlign;
342 end;
345 procedure TFlexLayouterBase.doChildren (parent: LayControlIdx; child: ControlT);
346 var
347 cidx: LayControlIdx = -1;
348 lc: PLayControl;
349 begin
350 assert((parent >= 0) and (parent < Length(ctlist)));
351 assert(ctlist[parent].firstChild = -1);
352 while (child <> nil) do
353 begin
354 child.layPrepare;
355 SetLength(ctlist, Length(ctlist)+1);
356 lc := @ctlist[High(ctlist)];
357 lc.initialize();
358 if (cidx = -1) then
359 begin
360 cidx := LayControlIdx(High(ctlist));
361 ctlist[parent].firstChild := cidx;
362 // first child is always linestart
363 lc.flags := lc.flags or FlagLineStart or FlagLineFirst;
364 end
365 else
366 begin
367 ctlist[cidx].nextSibling := LayControlIdx(High(ctlist));
368 cidx := LayControlIdx(High(ctlist));
369 end;
370 lc.myidx := cidx;
371 lc.ctl := child;
372 lc.parent := parent;
373 fixFlags(cidx);
374 doChildren(cidx, child.firstChild);
375 child := child.nextSibling;
376 end;
377 end;
380 procedure TFlexLayouterBase.appendToGroup (const gname: AnsiString; cidx: LayControlIdx; gidx: Integer);
381 var
382 f: Integer;
383 begin
384 if (Length(gname) = 0) then exit;
385 assert((cidx >= 0) and (cidx < Length(ctlist)));
386 assert((gidx = 0) or (gidx = 1));
387 ctlist[cidx].flags := ctlist[cidx].flags or FlagInGroup;
388 for f := 0 to High(groups[gidx]) do
389 begin
390 if (groups[gidx][f].name = gname) then
391 begin
392 SetLength(groups[gidx][f].ctls, Length(groups[gidx][f].ctls)+1);
393 groups[gidx][f].ctls[High(groups[gidx][f].ctls)] := cidx;
394 exit;
395 end;
396 end;
397 // new group
398 f := Length(groups[gidx]);
399 SetLength(groups[gidx], f+1);
400 groups[gidx][f].name := gname;
401 SetLength(groups[gidx][f].ctls, Length(groups[gidx][f].ctls)+1);
402 groups[gidx][f].ctls[High(groups[gidx][f].ctls)] := cidx;
403 end;
406 procedure TFlexLayouterBase.setupGroups ();
407 var
408 idx: Integer;
409 lc: PLayControl;
410 begin
411 for idx := 0 to High(ctlist) do
412 begin
413 lc := @ctlist[idx];
414 appendToGroup(lc.ctl.getHGroup, LayControlIdx(idx), 0);
415 appendToGroup(lc.ctl.getVGroup, LayControlIdx(idx), 1);
416 end;
417 end;
420 // build control and group lists
421 procedure TFlexLayouterBase.setup (root: ControlT);
422 begin
423 clear();
424 if (root = nil) then exit;
425 root.layPrepare;
426 try
427 SetLength(ctlist, 1);
428 ctlist[0].initialize();
429 ctlist[0].myidx := 0;
430 ctlist[0].ctl := root;
431 fixFlags(0);
432 ctlist[0].flags := ctlist[0].flags or FlagLineStart or FlagLineFirst;
433 doChildren(0, root.firstChild);
434 setupGroups();
435 except
436 clear();
437 raise;
438 end;
439 end;
442 // this also sets `tempFlex`
443 procedure TFlexLayouterBase.calcMaxSizeInternal (cidx: LayControlIdx);
444 var
445 lc, c: PLayControl;
446 msz: TLaySize;
447 negw, negh: Boolean;
448 zerow: Boolean;
449 curwdt, curhgt, totalhgt: Integer;
450 doWrap: Boolean;
451 begin
452 if (cidx < 0) or (cidx >= Length(ctlist)) then exit;
454 lc := @ctlist[cidx];
455 msz := lc.ctl.getMaxSize;
456 negw := (lc.startsize.w < 0);
457 negh := (lc.startsize.h < 0);
458 zerow := (lc.startsize.w = 0);
460 lc.tempFlex := lc.ctl.getFlex;
462 for c in forChildren(cidx) do calcMaxSizeInternal(c.myidx);
464 if (lc.horizBox) then
465 begin
466 // horizontal boxes
467 if (negw) then lc.tempFlex := 0; // size is negative: don't expand
468 curwdt := lc.margins.horiz;
469 curhgt := 0;
470 totalhgt := lc.margins.vert;
471 for c in forChildren(cidx) do
472 begin
473 // new line?
474 doWrap := (not c.firstInLine) and (c.lineStart);
475 // need to wrap?
476 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;
477 if (doWrap) then
478 begin
479 totalhgt += curhgt;
480 if (lc.startsize.w < curwdt) then lc.startsize.w := curwdt;
481 curwdt := 0;
482 curhgt := 0;
483 end;
484 curwdt += c.startsize.w;
485 if (curhgt < c.startsize.h) then curhgt := c.startsize.h;
486 end;
487 //writeln('00: ', cidx, ': totalhgt=', totalhgt);
488 totalhgt += curhgt;
489 //writeln('01: ', cidx, ': totalhgt=', totalhgt);
490 if (lc.startsize.w < curwdt) then lc.startsize.w := curwdt;
491 if (lc.startsize.h < totalhgt) then lc.startsize.h := totalhgt;
492 end
493 else
494 begin
495 // vertical boxes
496 if (negh) then lc.tempFlex := 0; // size is negative: don't expand
497 curhgt := lc.margins.vert;
498 for c in forChildren(cidx) do
499 begin
500 if (lc.startsize.w < c.startsize.w+lc.margins.horiz) then lc.startsize.w := c.startsize.w+lc.margins.horiz;
501 curhgt += c.startsize.h;
502 end;
503 if (lc.startsize.h < curhgt) then lc.startsize.h := curhgt;
504 end;
505 if (lc.startsize.w < 0) then lc.startsize.w := 0;
506 if (lc.startsize.h < 0) then lc.startsize.h := 0;
507 lc.maxsize := msz;
508 if (lc.maxsize.w < lc.startsize.w) then lc.maxsize.w := lc.startsize.w;
509 if (lc.maxsize.h < lc.startsize.h) then lc.maxsize.h := lc.startsize.h;
510 end;
513 procedure TFlexLayouterBase.firstPass ();
514 var
515 f, c: Integer;
516 needRecalcMaxSize: Boolean;
517 gtype: Integer;
518 grp: PLayGroup;
519 maxsz: Integer;
520 cidx: LayControlIdx;
521 ct: PLayControl;
522 mr: TLayMargins;
523 begin
524 // reset all 'laywrap' flags for controls, set initial 'startsize'
525 for f := 0 to High(ctlist) do
526 begin
527 ctlist[f].didWrap := false;
528 ctlist[f].startsize := ctlist[f].ctl.getDefSize;
529 mr := ctlist[f].ctl.getMargins;
530 ctlist[f].margins := mr;
531 //ctlist[f].startsize.w += mr.horiz;
532 //ctlist[f].startsize.h += mr.vert;
533 end;
534 // setup sizes
535 calcMaxSizeInternal(0); // this also sets `tempFlex`
536 //writeln('=== calculated max size (0) ==='); dump();
537 // find max size for group, adjust 'startsize' controls to group max size
538 needRecalcMaxSize := false;
539 for gtype := 0 to 1 do
540 begin
541 for f := 0 to High(groups[gtype]) do
542 begin
543 grp := @groups[gtype][f];
544 maxsz := 0;
545 for c := 0 to High(grp.ctls) do
546 begin
547 cidx := grp.ctls[c];
548 ct := @ctlist[cidx];
549 if (maxsz < ct.startsize[gtype]) then maxsz := ct.startsize[gtype];
550 end;
551 for c := 0 to High(grp.ctls) do
552 begin
553 cidx := grp.ctls[c];
554 ct := @ctlist[cidx];
555 if (maxsz <> ct.startsize[gtype]) then
556 begin
557 needRecalcMaxSize := true;
558 ct.startsize[gtype] := maxsz;
559 end;
560 end;
561 end;
562 end;
563 // recalc maxsize if necessary
564 if (needRecalcMaxSize) then calcMaxSizeInternal(0);
565 // set "desired size" to "start size"
566 for f := 0 to High(ctlist) do ctlist[f].desiredsize := ctlist[f].startsize;
567 // set flags
568 firstTime := true;
569 //writeln('=== calculated max size (final) ==='); dump();
570 end;
573 procedure TFlexLayouterBase.fixLine (me: PLayControl; i0, i1: LayControlIdx; var cury: Integer; var spaceLeft: Single);
574 var
575 flexTotal: Integer = 0; // total sum of flex fields
576 flexBoxCount: Integer = 0; // number of boxes
577 curx: Integer;
578 lc: PLayControl;
579 osz: TLaySize;
580 toadd: Integer;
581 sti0: Integer;
582 lineh: Integer;
583 begin
584 curx := me.margins.left;
585 sti0 := i0;
586 // calc minimal line height, count flexboxes
587 lineh := 0;
588 while (i0 <> i1) do
589 begin
590 lc := @ctlist[i0];
591 lineh := nmax(lineh, lc.startsize.h);
592 if (lc.tempFlex > 0) then begin flexTotal += lc.tempFlex; flexBoxCount += 1; end;
593 i0 := lc.nextSibling;
594 end;
595 // distribute space, expand/align
596 i0 := sti0;
597 while (i0 <> i1) do
598 begin
599 lc := @ctlist[i0];
600 osz := lc.desiredsize;
601 lc.desiredsize := lc.startsize;
602 lc.desiredpos.x := curx;
603 lc.desiredpos.y := cury;
604 curx += lc.desiredsize.w;
605 // fix flexbox size
606 if (lc.tempFlex > 0) and (spaceLeft > 0) then
607 begin
608 toadd := trunc(spaceLeft*lc.tempFlex/flexTotal+0.5);
609 if (toadd > 0) then
610 begin
611 // size changed
612 lc.desiredsize.w += toadd;
613 curx += toadd;
614 // compensate (crudely) rounding errors
615 if (curx > me.desiredsize.w-me.margins.horiz) then begin lc.desiredsize.w -= 1; curx -= 1; end;
616 // relayout children
617 layBox(lc.firstChild);
618 end;
619 end;
620 // expand or align
621 if (lc.expand) then lc.desiredsize.h := nmin(lc.maxsize.h, lineh) // expand
622 else if (lc.alignBottom) then lc.desiredpos.y := cury+(lineh-lc.desiredsize.h) // bottom align
623 else if (lc.alignCenter) then lc.desiredpos.y := cury+(lineh-lc.desiredsize.h) div 2; // center
624 if (not osz.equals(lc.desiredsize)) then
625 begin
626 if (lc.inGroup) then groupElementChanged := true;
627 // relayout children
628 layBox(lc.firstChild);
629 end;
630 i0 := lc.nextSibling;
631 end;
632 flexTotal := 0;
633 flexBoxCount := 0;
634 spaceLeft := me.desiredsize.w-me.margins.horiz;
635 cury += lineh;
636 end;
639 // do box layouting; call `layBox()` recursively if necessary
640 procedure TFlexLayouterBase.layBox (boxidx: LayControlIdx);
641 var
642 me: PLayControl;
643 flexTotal: Integer; // total sum of flex fields
644 flexBoxCount: Integer; // number of boxes
645 spaceLeft: Single;
646 cury: Integer;
647 maxwdt: Integer;
648 lineStartIdx: LayControlIdx;
649 lc: PLayControl;
650 doWrap: Boolean;
651 toadd: Integer;
652 osz: TLaySize;
653 begin
654 if (boxidx < 0) or (boxidx >= Length(ctlist)) then exit;
655 me := @ctlist[boxidx];
657 // if we have no children, there's nothing to do
658 if (me.firstChild = -1) then exit;
660 // first, layout all children
661 for lc in forChildren(boxidx) do layBox(lc.myidx);
663 // second, layout lines, distribute flex data
664 if (me.horizBox) then
665 begin
666 // horizontal boxes
667 cury := me.margins.top;
669 fixLine(me, -1, -1, cury, spaceLeft); //HACK!
671 lineStartIdx := me.firstChild;
672 for lc in forChildren(boxidx) do
673 begin
674 // new line?
675 doWrap := (not lc.firstInLine) and (lc.lineStart);
676 // need to wrap?
677 if (not doWrap) and (lc.canWrap) and (lc.canWrap) and (lc.desiredsize.w > 0) and (spaceLeft < lc.desiredsize.w) then doWrap := true;
678 if (doWrap) then
679 begin
680 // new line, fix this one
681 if (not lc.didWrap) then begin wrappingChanged := true; lc.didWrap := true; end;
682 fixLine(me, lineStartIdx, lc.myidx, cury, spaceLeft);
683 lineStartIdx := lc.myidx;
684 end
685 else
686 begin
687 if (lc.didWrap) then begin wrappingChanged := true; lc.didWrap := false; end;
688 end;
689 spaceLeft -= lc.desiredsize.w;
690 //if (maxhgt < lc.desiredsize.h) then maxhgt := lc.desiredsize.h;
691 //if (lc.tempFlex > 0) then begin flexTotal += lc.tempFlex; flexBoxCount += 1; end;
692 end;
693 // fix last line
694 fixLine(me, lineStartIdx, -1, cury, spaceLeft);
695 end
696 else
697 begin
698 // vertical boxes
699 maxwdt := 0;
700 flexTotal := 0;
701 flexBoxCount := 0;
702 spaceLeft := me.desiredsize.h-me.margins.vert;
704 // calc flex
705 for lc in forChildren(boxidx) do
706 begin
707 spaceLeft -= lc.desiredsize.h;
708 if (maxwdt < lc.desiredsize.w) then maxwdt := lc.desiredsize.w;
709 if (lc.tempFlex > 0) then begin flexTotal += lc.tempFlex; flexBoxCount += 1; end;
710 end;
712 // distribute space
713 cury := me.margins.top;
714 //writeln('me: ', boxidx, '; margins: ', me.margins.toString);
715 for lc in forChildren(boxidx) do
716 begin
717 osz := lc.desiredsize;
718 lc.desiredsize := lc.startsize;
719 lc.desiredpos.x := me.margins.left;
720 lc.desiredpos.y := cury;
721 cury += lc.desiredsize.h;
722 // fix flexbox size
723 if (lc.tempFlex > 0) and (spaceLeft > 0) then
724 begin
725 toadd := trunc(spaceLeft*lc.tempFlex/flexTotal+0.5);
726 if (toadd > 0) then
727 begin
728 // size changed
729 lc.desiredsize.h += toadd;
730 cury += toadd;
731 // compensate (crudely) rounding errors
732 if (cury > me.desiredsize.h-me.margins.vert) then begin lc.desiredsize.h -= 1; cury -= 1; end;
733 end;
734 end;
735 // expand or align
736 if (lc.expand) then lc.desiredsize.w := nmin(lc.maxsize.w, me.desiredsize.w-me.margins.vert) // expand
737 else if (lc.alignRight) then lc.desiredpos.x := me.desiredsize.w-me.margins.right-lc.desiredsize.w // right align
738 else if (lc.alignCenter) then lc.desiredpos.x := (me.desiredsize.w-lc.desiredsize.w) div 2; // center
739 if (not osz.equals(lc.desiredsize)) then
740 begin
741 if (lc.inGroup) then groupElementChanged := true;
742 // relayout children
743 layBox(lc.firstChild);
744 end;
745 end;
746 end;
747 end;
750 (*
751 second pass:
752 calcluate desired sizes (process flexes) using 'startsize', set 'desiredsize' and 'desiredpos'
753 if control has children, call 'second pass' recursively with this control as parent
754 flags set:
755 'group-element-changed', if any group element size was changed
756 'wrapping-changed', if not 'firsttime', and wrapping was changed (i.e. first pass will not set the flag)
757 *)
758 procedure TFlexLayouterBase.secondPass ();
759 begin
760 // reset flags
761 groupElementChanged := false;
762 wrappingChanged := false;
764 if (Length(ctlist) > 0) then
765 begin
766 ctlist[0].desiredpos := TLayPos.Create(0, 0);
767 layBox(0);
768 end;
770 // fix 'wrapping-changed' flag
771 if (firstTime) then begin wrappingChanged := false; firstTime := false; end;
772 end;
775 (*
776 third pass:
777 if 'group-element-changed':
778 for each group: adjust controls to max desired size (startsize), set 'temp-flex' flags to 0 for 'em, set 'second-again' flag
779 for other controls: if 'desiredsize' > 'maxsize', set 'startsize' to 'maxsize', set 'temp-flex' flag to 0, set 'second-again' flag
780 if 'second-again' or 'wrapping-changed':
781 reset 'second-again'
782 reset 'wrapping-changed'
783 reset 'firsttime'
784 goto second pass
785 *)
786 procedure TFlexLayouterBase.thirdPass ();
787 var
788 secondAgain: Boolean;
789 gtype: Integer;
790 maxsz: Integer;
791 grp: PLayGroup;
792 f, c: Integer;
793 cidx: LayControlIdx;
794 ct: PLayControl;
795 begin
796 while true do
797 begin
798 secondPass();
799 secondAgain := false;
800 if (groupElementChanged) then
801 begin
802 secondAgain := true;
803 // find max size for group, adjust 'startsize' controls to group max size
804 for gtype := 0 to 1 do
805 begin
806 for f := 0 to High(groups[gtype]) do
807 begin
808 grp := @groups[gtype][f];
809 maxsz := 0;
810 for c := 0 to High(grp.ctls) do
811 begin
812 cidx := grp.ctls[c];
813 ct := @ctlist[cidx];
814 ct.expand := false; // don't expand grouped controls anymore
815 if (maxsz < ct.startsize[gtype]) then maxsz := ct.startsize[gtype];
816 end;
817 for c := 0 to High(grp.ctls) do
818 begin
819 cidx := grp.ctls[c];
820 ct := @ctlist[cidx];
821 ct.startsize[gtype] := maxsz;
822 ct.desiredsize[gtype] := maxsz;
823 ct.tempFlex := 0; // don't change control size anymore
824 end;
825 end;
826 end;
827 end
828 else
829 begin
830 for f := 0 to High(ctlist) do
831 begin
832 ct := @ctlist[f];
833 if (ct.inGroup) then
834 begin
835 ct.expand := false; // don't expand grouped controls anymore
836 ct.tempFlex := 0; // don't change control size anymore
837 end;
838 for c := 0 to 1 do
839 begin
840 if (ct.maxsize[c] < 0) then continue;
841 if (ct.desiredsize[c] > ct.maxsize[c]) then
842 begin
843 //writeln('ctl #', f, '; dimension #', c, ': desired=', ctlist[f].desiredsize[c], '; max=', ctlist[f].maxsize[c]);
844 ct.startsize[c] := ct.maxsize[c];
845 ct.desiredsize[c] := ct.maxsize[c];
846 ct.tempFlex := 0; // don't change control size anymore
847 secondAgain := true;
848 end;
849 end;
850 end;
851 end;
852 if (not secondAgain) and (not wrappingChanged) then break;
853 firstTime := false;
854 end;
855 end;
858 (*
859 fourth pass:
860 set 'actualsize' and 'actualpos' to 'desiredsize' and 'desiredpos'
861 return
862 *)
863 procedure TFlexLayouterBase.fourthPass ();
864 var
865 f: Integer;
866 begin
867 for f := 0 to High(ctlist) do
868 begin
869 ctlist[f].ctl.setActualSizePos(ctlist[f].desiredpos, ctlist[f].desiredsize);
870 end;
871 end;
874 procedure TFlexLayouterBase.layout ();
875 begin
876 firstPass();
877 secondPass();
878 thirdPass();
879 fourthPass();
880 end;
883 procedure TFlexLayouterBase.dumpFlat ();
884 var
885 f: Integer;
886 lc: PLayControl;
887 ds, ms: TLaySize;
888 begin
889 for f := 0 to High(ctlist) do
890 begin
891 lc := @ctlist[f];
892 ds := lc.ctl.getDefSize;
893 ms := lc.ctl.getMaxSize;
894 writeln(lc.myidx, ': startsize:', lc.startsize.toString(), '; desiredsize=', lc.desiredsize.toString(), '; maxsize=', lc.maxsize.toString(), '; tempFlex=', lc.tempFlex, '; flags=', lc.flags,
895 '; parent=', lc.parent, '; next=', lc.nextSibling, '; child=', lc.firstChild, '; ctl.size=', ds.toString(), '; ctl.maxsize=', ms.toString());
896 end;
897 end;
900 procedure TFlexLayouterBase.dumpList (cidx: LayControlIdx; indent: Integer);
901 var
902 lc: PLayControl;
903 f: Integer;
904 begin
905 while (cidx >= 0) do
906 begin
907 lc := @ctlist[cidx];
908 for f := 0 to indent do write(' ');
909 writeln(lc.myidx, ': startsize:', lc.startsize.toString, '; desiredsize=', lc.desiredsize.toString, '; maxsize=', lc.maxsize.toString, '; tempFlex=', lc.tempFlex, '; despos=', lc.desiredpos.toString);
910 dumpList(lc.firstChild, indent+2);
911 cidx := lc.nextSibling;
912 end;
913 end;
916 procedure TFlexLayouterBase.dump ();
917 begin
918 dumpList(0, 0);
919 end;
922 // ////////////////////////////////////////////////////////////////////////// //
923 (*
924 void main () begin
925 auto win := new GuiControl();
926 (win ~= new GuiControl()).mSize := TLaySize(10, 5);
927 (win ~= new GuiControl()).mSize := TLaySize(16, 8);
929 //win.mSize := TLaySize(40, 20);
931 auto lay := TFlexLayouterBase!GuiControl();
932 lay.setup(win);
934 writeln('============================');
935 lay.dumpFlat();
937 writeln('=== initial ===');
938 lay.dump();
940 //lay.calcMaxSizeInternal(0);
941 /*
942 lay.firstPass();
943 writeln('=== after first pass ===');
944 lay.dump();
946 lay.secondPass();
947 writeln('=== after second pass ===');
948 lay.dump();
949 */
950 lay.layout();
951 writeln('=== final ===');
952 lay.dump();
953 *)
954 end.