DEADSOFTWARE

f9487028524e56367082f0211caa8a5805af6d98
[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 mr: TLayMargins;
506 begin
507 // reset all 'laywrap' flags for controls, set initial 'startsize'
508 for f := 0 to High(ctlist) do
509 begin
510 ctlist[f].didWrap := false;
511 ctlist[f].startsize := ctlist[f].ctl.getDefSize;
512 mr := ctlist[f].ctl.getMargins;
513 ctlist[f].margins := mr;
514 ctlist[f].startsize.w += mr.horiz;
515 ctlist[f].startsize.h += mr.vert;
516 end;
517 // setup sizes
518 calcMaxSizeInternal(0); // this also sets `tempFlex`
519 // find max size for group, adjust 'startsize' controls to group max size
520 needRecalcMaxSize := false;
521 for gtype := 0 to 1 do
522 begin
523 for f := 0 to High(groups[gtype]) do
524 begin
525 grp := @groups[gtype][f];
526 maxsz := 0;
527 for c := 0 to High(grp.ctls) do
528 begin
529 cidx := grp.ctls[c];
530 if (maxsz < ctlist[cidx].startsize[gtype]) then maxsz := ctlist[cidx].startsize[gtype];
531 end;
532 for c := 0 to High(grp.ctls) do
533 begin
534 cidx := grp.ctls[c];
535 if (maxsz <> ctlist[cidx].startsize[gtype]) then
536 begin
537 needRecalcMaxSize := true;
538 ctlist[cidx].startsize[gtype] := maxsz;
539 end;
540 end;
541 end;
542 end;
543 // recalc maxsize if necessary
544 if (needRecalcMaxSize) then calcMaxSizeInternal(0);
545 // set "desired size" to "start size"
546 for f := 0 to High(ctlist) do ctlist[f].desiredsize := ctlist[f].startsize;
547 // set flags
548 firstTime := true;
549 //writeln('=== calculated max size ===');
550 //dump();
551 end;
554 procedure TFlexLayouterBase.fixLine (me: PLayControl; i0, i1: LayControlIdx; cury: Integer; var spaceLeft: Single; var flexTotal: Integer; var flexBoxCount: Integer);
555 var
556 curx: Integer;
557 lc: PLayControl;
558 osz: TLaySize;
559 toadd: Integer;
560 sti0: Integer;
561 lineh: Integer;
562 begin
563 curx := me.margins.left;
564 sti0 := i0;
565 // calc minimal line height
566 lineh := 0;
567 while (i0 <> i1) do
568 begin
569 lc := @ctlist[i0];
570 lineh := nmax(lineh, lc.startsize.h);
571 i0 := lc.nextSibling;
572 end;
573 // distribute space, expand/align
574 i0 := sti0;
575 while (i0 <> i1) do
576 begin
577 lc := @ctlist[i0];
578 osz := lc.desiredsize;
579 lc.desiredsize := lc.startsize;
580 lc.desiredpos.x := curx;
581 lc.desiredpos.y := cury;
582 curx += lc.desiredsize.w;
583 // fix flexbox size
584 if (lc.tempFlex > 0) and (spaceLeft > 0) then
585 begin
586 toadd := trunc(spaceLeft*lc.tempFlex/flexTotal+0.5);
587 if (toadd > 0) then
588 begin
589 // size changed
590 lc.desiredsize.w += toadd;
591 curx += toadd;
592 // compensate (crudely) rounding errors
593 if (curx > me.desiredsize.w-me.margins.horiz) then begin lc.desiredsize.w -= 1; curx -= 1; end;
594 // relayout children
595 layBox(lc.firstChild);
596 end;
597 end;
598 // expand or align
599 if (lc.expand) then lc.desiredsize.h := nmin(lc.maxsize.h, lineh) // expand
600 else if (lc.aligndir > 0) then lc.desiredpos.y := cury+(lineh-lc.desiredsize.h) // bottom align
601 else if (lc.aligndir = 0) then lc.desiredpos.y := cury+(lineh-lc.desiredsize.h) div 2; // center
602 if (not osz.equals(lc.desiredsize)) then
603 begin
604 if (lc.inGroup) then groupElementChanged := true;
605 // relayout children
606 layBox(lc.firstChild);
607 end;
608 i0 := lc.nextSibling;
609 end;
610 flexTotal := 0;
611 flexBoxCount := 0;
612 spaceLeft := me.desiredsize.w-me.margins.horiz;
613 end;
616 // do box layouting; call `layBox()` recursively if necessary
617 procedure TFlexLayouterBase.layBox (boxidx: LayControlIdx);
618 var
619 me: PLayControl;
620 flexTotal: Integer; // total sum of flex fields
621 flexBoxCount: Integer; // number of boxes
622 spaceLeft: Single;
623 cury: Integer;
624 maxwdt, maxhgt: Integer;
625 lineStartIdx: LayControlIdx;
626 lc: PLayControl;
627 doWrap: Boolean;
628 toadd: Integer;
629 osz: TLaySize;
630 begin
631 if (boxidx < 0) or (boxidx >= Length(ctlist)) then exit;
632 me := @ctlist[boxidx];
634 // if we have no children, there's nothing to do
635 if (me.firstChild = -1) then exit;
637 // first, layout all children
638 for lc in forChildren(boxidx) do layBox(lc.myidx);
640 // second, layout lines, distribute flex data
641 if (me.horizBox) then
642 begin
643 // horizontal boxes
644 cury := me.margins.top;
645 maxhgt := 0;
647 fixLine(me, -1, -1, cury, spaceLeft, flexTotal, flexBoxCount); //HACK!
649 lineStartIdx := me.firstChild;
651 for lc in forChildren(boxidx) do
652 begin
653 // new line?
654 doWrap := (not lc.firstInLine) and (lc.lineStart);
655 // need to wrap?
656 if (not doWrap) and (lc.canWrap) and (lc.canWrap) and (lc.desiredsize.w > 0) and (spaceLeft < lc.desiredsize.w) then doWrap := true;
657 if (doWrap) then
658 begin
659 // new line, fix this one
660 if (not lc.didWrap) then
661 begin
662 wrappingChanged := true;
663 lc.didWrap := true;
664 end;
665 fixLine(me, lineStartIdx, lc.myidx, cury, spaceLeft, flexTotal, flexBoxCount);
666 cury += maxhgt;
667 lineStartIdx := lc.myidx;
668 end
669 else
670 begin
671 if (lc.didWrap) then
672 begin
673 wrappingChanged := true;
674 lc.didWrap := false;
675 end;
676 end;
677 spaceLeft -= lc.desiredsize.w;
678 if (maxhgt < lc.desiredsize.h) then maxhgt := lc.desiredsize.h;
679 if (lc.tempFlex > 0) then
680 begin
681 flexTotal += lc.tempFlex;
682 flexBoxCount += 1;
683 end;
684 end;
685 // fix last line
686 fixLine(me, lineStartIdx, -1, cury, spaceLeft, flexTotal, flexBoxCount);
687 end
688 else
689 begin
690 // vertical boxes
691 maxwdt := 0;
692 flexTotal := 0;
693 flexBoxCount := 0;
694 spaceLeft := me.desiredsize.h-me.margins.vert;
696 // calc flex
697 for lc in forChildren(boxidx) do
698 begin
699 spaceLeft -= lc.desiredsize.h;
700 if (maxwdt < lc.desiredsize.w) then maxwdt := lc.desiredsize.w;
701 if (lc.tempFlex > 0) then
702 begin
703 flexTotal += lc.tempFlex;
704 flexBoxCount += 1;
705 end;
706 end;
708 // distribute space
709 cury := me.margins.top;
710 //writeln('me: ', boxidx, '; margins: ', me.margins.toString);
711 for lc in forChildren(boxidx) do
712 begin
713 osz := lc.desiredsize;
714 lc.desiredsize := lc.startsize;
715 lc.desiredpos.x := me.margins.left;
716 lc.desiredpos.y := cury;
717 cury += lc.desiredsize.h;
718 // fix flexbox size
719 if (lc.tempFlex > 0) and (spaceLeft > 0) then
720 begin
721 toadd := trunc(spaceLeft*lc.tempFlex/flexTotal+0.5);
722 if (toadd > 0) then
723 begin
724 // size changed
725 lc.desiredsize.h += toadd;
726 cury += toadd;
727 // compensate (crudely) rounding errors
728 if (cury > me.desiredsize.h-me.margins.vert) then begin lc.desiredsize.h -= 1; cury -= 1; end;
729 end;
730 end;
731 // expand or align
732 if (lc.expand) then lc.desiredsize.w := nmin(lc.maxsize.w, me.desiredsize.w-me.margins.vert) // expand
733 else if (lc.aligndir > 0) then lc.desiredpos.x := me.desiredsize.w-me.margins.right-lc.desiredsize.w // right align
734 else if (lc.aligndir = 0) then lc.desiredpos.x := (me.desiredsize.w-me.margins.horiz-lc.desiredsize.w) div 2; // center
735 if (not osz.equals(lc.desiredsize)) then
736 begin
737 if (lc.inGroup) then groupElementChanged := true;
738 // relayout children
739 layBox(lc.firstChild);
740 end;
741 end;
742 end;
743 end;
746 (*
747 second pass:
748 calcluate desired sizes (process flexes) using 'startsize', set 'desiredsize' and 'desiredpos'
749 if control has children, call 'second pass' recursively with this control as parent
750 flags set:
751 'group-element-changed', if any group element size was changed
752 'wrapping-changed', if not 'firsttime', and wrapping was changed (i.e. first pass will not set the flag)
753 *)
754 procedure TFlexLayouterBase.secondPass ();
755 begin
756 // reset flags
757 groupElementChanged := false;
758 wrappingChanged := false;
760 if (Length(ctlist) > 0) then
761 begin
762 ctlist[0].desiredpos := TLayPos.Create(0, 0);
763 layBox(0);
764 end;
766 // fix 'wrapping-changed' flag
767 if (firstTime) then begin wrappingChanged := false; firstTime := false; end;
768 end;
771 (*
772 third pass:
773 if 'group-element-changed':
774 for each group: adjust controls to max desired size (startsize), set 'temp-flex' flags to 0 for 'em, set 'second-again' flag
775 for other controls: if 'desiredsize' > 'maxsize', set 'startsize' to 'maxsize', set 'temp-flex' flag to 0, set 'second-again' flag
776 if 'second-again' or 'wrapping-changed':
777 reset 'second-again'
778 reset 'wrapping-changed'
779 reset 'firsttime'
780 goto second pass
781 *)
782 procedure TFlexLayouterBase.thirdPass ();
783 var
784 secondAgain: Boolean;
785 gtype: Integer;
786 maxsz: Integer;
787 grp: PLayGroup;
788 f, c: Integer;
789 cidx: LayControlIdx;
790 ct: PLayControl;
791 begin
792 while true do
793 begin
794 secondPass();
795 secondAgain := false;
796 if (groupElementChanged) then
797 begin
798 secondAgain := true;
799 // find max size for group, adjust 'startsize' controls to group max size
800 for gtype := 0 to 1 do
801 begin
802 for f := 0 to High(groups[gtype]) do
803 begin
804 grp := @groups[gtype][f];
805 maxsz := 0;
806 for c := 0 to High(grp.ctls) do
807 begin
808 cidx := grp.ctls[c];
809 ct := @ctlist[cidx];
810 ct.expand := false; // don't expand grouped controls anymore
811 if (maxsz < ct.startsize[gtype]) then maxsz := ct.startsize[gtype];
812 end;
813 for c := 0 to High(grp.ctls) do
814 begin
815 cidx := grp.ctls[c];
816 ct := @ctlist[cidx];
817 ct.startsize[gtype] := maxsz;
818 ct.desiredsize[gtype] := maxsz;
819 ct.tempFlex := 0; // don't change control size anymore
820 end;
821 end;
822 end;
823 end
824 else
825 begin
826 for f := 0 to High(ctlist) do
827 begin
828 ct := @ctlist[f];
829 if (ct.inGroup) then
830 begin
831 ct.expand := false; // don't expand grouped controls anymore
832 ct.tempFlex := 0; // don't change control size anymore
833 end;
834 for c := 0 to 1 do
835 begin
836 if (ct.maxsize[c] <= 0) then continue;
837 if (ct.desiredsize[c] > ct.maxsize[c]) then
838 begin
839 //writeln('ctl #', f, '; dimension #', c, ': desired=', ctlist[f].desiredsize[c], '; max=', ctlist[f].maxsize[c]);
840 ct.startsize[c] := ct.maxsize[c];
841 ct.desiredsize[c] := ct.maxsize[c];
842 ct.tempFlex := 0; // don't change control size anymore
843 secondAgain := true;
844 end;
845 end;
846 end;
847 end;
848 if (not secondAgain) and (not wrappingChanged) then break;
849 firstTime := false;
850 end;
851 end;
854 (*
855 fourth pass:
856 set 'actualsize' and 'actualpos' to 'desiredsize' and 'desiredpos'
857 return
858 *)
859 procedure TFlexLayouterBase.fourthPass ();
860 var
861 f: Integer;
862 begin
863 for f := 0 to High(ctlist) do
864 begin
865 ctlist[f].ctl.setActualSizePos(ctlist[f].desiredpos, ctlist[f].desiredsize);
866 end;
867 end;
870 procedure TFlexLayouterBase.layout ();
871 begin
872 firstPass();
873 secondPass();
874 thirdPass();
875 fourthPass();
876 end;
879 procedure TFlexLayouterBase.dumpFlat ();
880 var
881 f: Integer;
882 lc: PLayControl;
883 ds, ms: TLaySize;
884 begin
885 for f := 0 to High(ctlist) do
886 begin
887 lc := @ctlist[f];
888 ds := lc.ctl.getDefSize;
889 ms := lc.ctl.getMaxSize;
890 writeln(lc.myidx, ': startsize:', lc.startsize.toString(), '; desiredsize=', lc.desiredsize.toString(), '; maxsize=', lc.maxsize.toString(), '; tempFlex=', lc.tempFlex, '; flags=', lc.flags,
891 '; parent=', lc.parent, '; next=', lc.nextSibling, '; child=', lc.firstChild, '; ctl.size=', ds.toString(), '; ctl.maxsize=', ms.toString());
892 end;
893 end;
896 procedure TFlexLayouterBase.dumpList (cidx: LayControlIdx; indent: Integer);
897 var
898 lc: PLayControl;
899 f: Integer;
900 begin
901 while (cidx >= 0) do
902 begin
903 lc := @ctlist[cidx];
904 for f := 0 to indent do write(' ');
905 writeln(lc.myidx, ': startsize:', lc.startsize.toString, '; desiredsize=', lc.desiredsize.toString, '; maxsize=', lc.maxsize.toString, '; tempFlex=', lc.tempFlex, '; despos=', lc.desiredpos.toString);
906 dumpList(lc.firstChild, indent+2);
907 cidx := lc.nextSibling;
908 end;
909 end;
912 procedure TFlexLayouterBase.dump ();
913 begin
914 dumpList(0, 0);
915 end;
918 // ////////////////////////////////////////////////////////////////////////// //
919 (*
920 void main () begin
921 auto win := new GuiControl();
922 (win ~= new GuiControl()).mSize := TLaySize(10, 5);
923 (win ~= new GuiControl()).mSize := TLaySize(16, 8);
925 //win.mSize := TLaySize(40, 20);
927 auto lay := TFlexLayouterBase!GuiControl();
928 lay.setup(win);
930 writeln('============================');
931 lay.dumpFlat();
933 writeln('=== initial ===');
934 lay.dump();
936 //lay.calcMaxSizeInternal(0);
937 /*
938 lay.firstPass();
939 writeln('=== after first pass ===');
940 lay.dump();
942 lay.secondPass();
943 writeln('=== after second pass ===');
944 lay.dump();
945 */
946 lay.layout();
947 writeln('=== final ===');
948 lay.dump();
949 *)
950 end.