DEADSOFTWARE

mempool is optional now
[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 'wantsize' 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 'wantsize', 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 (wantsize), set 'temp-flex' flags to 0 for 'em, set 'second-again' flag
23 for other controls: if 'desiredsize' > 'maxsize', set 'wantsize' 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 'wantsize' 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 'wantsize'
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 'wantsize' 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 ControlT:
66 function getDefSize (): TLaySize; // default size; <0: use max size
67 function getMaxSize (): TLaySize; // max size; <0: set to some huge value
68 function getFlex (): Integer; // <=0: not flexible
69 function isHorizBox (): Boolean; // horizontal layout for children?
70 function canWrap (): Boolean; // for horizontal boxes: can wrap children? for child: `false` means 'nonbreakable at *next* ctl'
71 function isLineStart (): Boolean; // `true` if this ctl should start a new line; ignored for vertical boxes
72 function getAlign (): Integer; // aligning in non-main direction: <0: left/up; 0: center; >0: right/down
73 function getExpand (): Boolean; // expanding in non-main direction: `true` will ignore align and eat all available space
74 procedure setActualSizePos (constref apos: TLayPos; constref asize: TLaySize);
75 function getHGroup (): AnsiString; // empty: not grouped
76 function getVGroup (): AnsiString; // empty: not grouped
77 function nextSibling (): ControlT;
78 function firstChild (): ControlT;
79 *)
81 interface
83 uses
84 gh_ui_common;
87 // ////////////////////////////////////////////////////////////////////////// //
88 type
89 generic TFlexLayouterBase<ControlT> = class
90 public
91 type CtlT = ControlT;
93 private
94 type LayControlIdx = Integer;
96 private
97 // flags
98 const
99 FlagHorizBox = LongWord(1) shl 0; // horizontal layout for children
100 FlagLineStart = LongWord(1) shl 1;
101 FlagLineCanWrap = LongWord(1) shl 2;
102 // internal
103 FlagLineDidWrap = LongWord(1) shl 3; // will be set when line was wrapped
104 FlagInGroup = LongWord(1) shl 4; // set if this control is a member of any group
105 FlagExpand = LongWord(1) shl 5;
106 FlagLineFirst = LongWord(1) shl 6;
108 private
109 type
110 PLayControl = ^TLayControl;
111 TLayControl = record
112 public
113 myidx: LayControlIdx;
114 tempFlex: Integer;
115 flags: LongWord; // see below
116 aligndir: Integer;
117 wantsize, desiredsize, maxsize: TLaySize;
118 desiredpos: TLayPos;
119 ctl: ControlT;
120 parent: LayControlIdx; // = -1;
121 firstChild: LayControlIdx; // = -1;
122 nextSibling: LayControlIdx; // = -1;
124 private
125 function getDidWrap (): Boolean; inline;
126 procedure setDidWrap (v: Boolean); inline;
128 public
129 procedure initialize (); inline;
131 function horizBox (): Boolean; inline;
132 function lineStart (): Boolean; inline;
133 function canWrap (): Boolean; inline;
134 function inGroup (): Boolean; inline;
135 function expand (): Boolean; inline;
136 function firstInLine (): Boolean; inline;
138 public
139 property didWrap: Boolean read getDidWrap write setDidWrap;
140 end;
142 PLayGroup = ^TLayGroup;
143 TLayGroup = record
144 name: AnsiString;
145 ctls: array of LayControlIdx;
146 end;
148 TLayCtlArray = array of TLayControl;
149 TLayGrpArray = array of TLayGroup;
151 private
152 ctlist: TLayCtlArray;
153 groups: array[0..1] of TLayGrpArray; // horiz, vert
155 firstTime: Boolean;
156 groupElementChanged: Boolean;
157 wrappingChanged: Boolean;
159 private
160 procedure fixFlags (cidx: LayControlIdx);
161 procedure doChildren (parent: LayControlIdx; child: ControlT);
162 procedure appendToGroup (const gname: AnsiString;cidx: LayControlIdx;gidx: Integer);
163 procedure setupGroups ();
165 // this also sets `tempFlex`
166 procedure calcMaxSizeInternal (cidx: LayControlIdx);
168 procedure fixLine (me: PLayControl; i0, i1: LayControlIdx; cury: Integer; var spaceLeft: Single; var flexTotal: Integer; var flexBoxCount: Integer);
169 // do box layouting; call `layBox()` recursively if necessary
170 procedure layBox (boxidx: LayControlIdx);
172 procedure firstPass ();
173 procedure secondPass ();
174 procedure thirdPass ();
175 procedure fourthPass ();
177 procedure dumpList (cidx: LayControlIdx; indent: Integer);
179 public
180 type
181 TChildrenEnumerator = record
182 private
183 ctls: TLayCtlArray;
184 cur: Integer;
185 first: Boolean;
186 public
187 constructor Create (constref actls: TLayCtlArray; acur: Integer);
188 function moveNext (): Boolean; inline;
189 function getCurrent (): PLayControl; inline;
190 function getEnumerator (): TChildrenEnumerator; inline;
191 property current: PLayControl read getCurrent;
192 end;
194 public
195 constructor Create ();
196 destructor Destroy (); override;
198 // clear build lists
199 procedure clear ();
201 // build control and group lists
202 procedure setup (root: ControlT);
204 function forChildren (cidx: LayControlIdx): TChildrenEnumerator; inline;
206 procedure layout ();
208 procedure dumpFlat ();
209 procedure dump ();
210 end;
213 implementation
215 uses
216 utils;
219 // ////////////////////////////////////////////////////////////////////////// //
220 procedure TFlexLayouterBase.TLayControl.initialize (); inline;
221 begin
222 FillChar(self, 0, sizeof(self));
223 parent := -1;
224 firstChild := -1;
225 nextSibling := -1;
226 end;
228 function TFlexLayouterBase.TLayControl.horizBox (): Boolean; inline; begin result := ((flags and FlagHorizBox) <> 0); end;
229 function TFlexLayouterBase.TLayControl.lineStart (): Boolean; inline; begin result := ((flags and FlagLineStart) <> 0); end;
230 function TFlexLayouterBase.TLayControl.canWrap (): Boolean; inline; begin result := ((flags and FlagLineCanWrap) <> 0); end;
231 function TFlexLayouterBase.TLayControl.inGroup (): Boolean; inline; begin result := ((flags and FlagInGroup) <> 0); end;
232 function TFlexLayouterBase.TLayControl.expand (): Boolean; inline; begin result := ((flags and FlagExpand) <> 0); end;
233 function TFlexLayouterBase.TLayControl.firstInLine (): Boolean; inline; begin result := ((flags and FlagLineFirst) <> 0); end;
235 function TFlexLayouterBase.TLayControl.getDidWrap (): Boolean; inline; begin result := ((flags and FlagLineDidWrap) <> 0); end;
236 procedure TFlexLayouterBase.TLayControl.setDidWrap (v: Boolean); inline; begin if (v) then flags := flags or FlagLineDidWrap else flags := flags and (not FlagLineDidWrap); end;
239 // ////////////////////////////////////////////////////////////////////////// //
240 constructor TFlexLayouterBase.TChildrenEnumerator.Create (constref actls: TLayCtlArray; acur: Integer);
241 begin
242 ctls := actls;
243 cur := acur;
244 first := true;
245 end;
247 function TFlexLayouterBase.TChildrenEnumerator.moveNext (): Boolean; inline;
248 begin
249 if first then
250 begin
251 if (cur >= 0) and (cur < Length(ctls)) then cur := ctls[cur].firstChild else cur := -1;
252 first := false;
253 end
254 else
255 begin
256 cur := ctls[cur].nextSibling;
257 end;
258 result := (cur >= 0);
259 end;
261 function TFlexLayouterBase.TChildrenEnumerator.getCurrent (): PLayControl; inline;
262 begin
263 result := @ctls[cur];
264 end;
266 function TFlexLayouterBase.TChildrenEnumerator.getEnumerator (): TChildrenEnumerator; inline;
267 begin
268 result := self;
269 end;
272 // ////////////////////////////////////////////////////////////////////////// //
273 constructor TFlexLayouterBase.Create ();
274 begin
275 ctlist := nil;
276 groups[0] := nil;
277 groups[1] := nil;
279 firstTime := false;
280 groupElementChanged := false;
281 wrappingChanged := false;
282 end;
285 destructor TFlexLayouterBase.Destroy ();
286 begin
287 clear();
288 inherited;
289 end;
292 function TFlexLayouterBase.forChildren (cidx: LayControlIdx): TChildrenEnumerator; inline;
293 begin
294 result := TChildrenEnumerator.Create(ctlist, cidx);
295 end;
298 procedure TFlexLayouterBase.clear ();
299 begin
300 ctlist := nil;
301 groups[0] := nil;
302 groups[1] := nil;
303 end;
306 procedure TFlexLayouterBase.fixFlags (cidx: LayControlIdx);
307 var
308 lc: PLayControl;
309 begin
310 assert((cidx >= 0) and (cidx < Length(ctlist)));
311 lc := @ctlist[cidx];
312 //lc.flags := 0;
313 if (lc.ctl.isHorizBox) then lc.flags := lc.flags or FlagHorizBox;
314 if (lc.ctl.isLineStart) then lc.flags := lc.flags or FlagLineStart;
315 if (lc.ctl.canWrap) then lc.flags := lc.flags or FlagLineCanWrap;
316 if (lc.ctl.getExpand) then lc.flags := lc.flags or FlagExpand;
317 lc.aligndir := lc.ctl.getAlign;
318 end;
321 procedure TFlexLayouterBase.doChildren (parent: LayControlIdx; child: ControlT);
322 var
323 cidx: LayControlIdx = -1;
324 lc: PLayControl;
325 begin
326 assert((parent >= 0) and (parent < Length(ctlist)));
327 assert(ctlist[parent].firstChild = -1);
328 while (child <> nil) do
329 begin
330 SetLength(ctlist, Length(ctlist)+1);
331 lc := @ctlist[High(ctlist)];
332 if (cidx = -1) then
333 begin
334 cidx := LayControlIdx(High(ctlist));
335 ctlist[parent].firstChild := cidx;
336 // first child is always linestart
337 lc.flags := lc.flags or FlagLineStart or FlagLineFirst;
338 end
339 else
340 begin
341 ctlist[cidx].nextSibling := LayControlIdx(High(ctlist));
342 cidx := LayControlIdx(High(ctlist));
343 end;
344 lc.myidx := cidx;
345 lc.ctl := child;
346 lc.parent := parent;
347 fixFlags(cidx);
348 doChildren(cidx, child.firstChild);
349 child := child.nextSibling;
350 end;
351 end;
354 procedure TFlexLayouterBase.appendToGroup (const gname: AnsiString; cidx: LayControlIdx; gidx: Integer);
355 var
356 f: Integer;
357 begin
358 if (Length(gname) = 0) then exit;
359 assert((cidx >= 0) and (cidx < Length(ctlist)));
360 assert((gidx = 0) or (gidx = 1));
361 ctlist[cidx].flags := ctlist[cidx].flags or FlagInGroup;
362 for f := 0 to High(groups[gidx]) do
363 begin
364 if (groups[gidx][f].name = gname) then
365 begin
366 SetLength(groups[gidx][f].ctls, Length(groups[gidx][f].ctls)+1);
367 groups[gidx][f].ctls[High(groups[gidx][f].ctls)] := cidx;
368 exit;
369 end;
370 end;
371 // new group
372 f := Length(groups[gidx]);
373 SetLength(groups[gidx], f+1);
374 groups[gidx][f].name := gname;
375 SetLength(groups[gidx][f].ctls, Length(groups[gidx][f].ctls)+1);
376 groups[gidx][f].ctls[High(groups[gidx][f].ctls)] := cidx;
377 end;
380 procedure TFlexLayouterBase.setupGroups ();
381 var
382 idx: Integer;
383 lc: PLayControl;
384 begin
385 for idx := 0 to High(ctlist) do
386 begin
387 lc := @ctlist[idx];
388 appendToGroup(lc.ctl.getHGroup, LayControlIdx(idx), 0);
389 appendToGroup(lc.ctl.getVGroup, LayControlIdx(idx), 1);
390 end;
391 end;
394 // build control and group lists
395 procedure TFlexLayouterBase.setup (root: ControlT);
396 begin
397 clear();
398 if (root = nil) then exit;
399 try
400 SetLength(ctlist, 1);
401 ctlist[0].myidx := 0;
402 ctlist[0].ctl := root;
403 fixFlags(0);
404 ctlist[0].flags := ctlist[0].flags or FlagLineStart or FlagLineFirst;
405 doChildren(0, root.firstChild);
406 setupGroups();
407 except
408 clear();
409 raise;
410 end;
411 end;
414 // this also sets `tempFlex`
415 procedure TFlexLayouterBase.calcMaxSizeInternal (cidx: LayControlIdx);
416 var
417 lc, c: PLayControl;
418 msz: TLaySize;
419 negw{, negh}: Boolean;
420 curwdt, curhgt, totalhgt: Integer;
421 doWrap: Boolean;
422 begin
423 if (cidx < 0) or (cidx >= Length(ctlist)) then exit;
425 lc := @ctlist[cidx];
426 msz := lc.ctl.getMaxSize;
427 //lc.wantsize := lc.ctl.getDefSize;
428 negw := (lc.wantsize.w <= 0);
429 //negh := (lc.wantsize.h <= 0);
431 //if (lc.wantsize.w < msz.w) lc.wantsize.w := msz.w;
432 //if (lc.wantsize.h < msz.h) lc.wantsize.h := msz.h;
434 //writeln('calcsize #', cidx, '; wantsize=', lc.wantsize, '; ctl.maxsize=', msz);
436 lc.tempFlex := lc.ctl.getFlex;
438 for c in forChildren(cidx) do calcMaxSizeInternal(c.myidx);
440 if (lc.horizBox) then
441 begin
442 // horizontal boxes
443 if (negw) then lc.tempFlex := 0; // size is negative: don't expand
444 curwdt := 0;
445 curhgt := 0;
446 totalhgt := 0;
447 for c in forChildren(cidx) do
448 begin
449 // new line?
450 doWrap := (not c.firstInLine) and (c.lineStart);
451 // need to wrap?
452 if (not doWrap) and (lc.canWrap) and (c.canWrap) and (msz.w > 0) and (curwdt+c.wantsize.w > lc.wantsize.w) then doWrap := true;
453 if (doWrap) then
454 begin
455 totalhgt += curhgt;
456 if (lc.wantsize.w < curwdt) then lc.wantsize.w := curwdt;
457 curwdt := 0;
458 curhgt := 0;
459 end;
460 curwdt += c.wantsize.w;
461 if (curhgt < c.wantsize.h) then curhgt := c.wantsize.h;
462 end;
463 totalhgt += curhgt;
464 if (lc.wantsize.w < curwdt) then lc.wantsize.w := curwdt;
465 if (lc.wantsize.h < totalhgt) then lc.wantsize.h := totalhgt;
466 end
467 else
468 begin
469 // vertical boxes
470 curhgt := 0;
471 for c in forChildren(cidx) do
472 begin
473 if (lc.wantsize.w < c.wantsize.w) then lc.wantsize.w := c.wantsize.w;
474 curhgt += c.wantsize.h;
475 end;
476 if (lc.wantsize.h < curhgt) then lc.wantsize.h := curhgt;
477 end;
478 if (lc.wantsize.w < 1) then lc.wantsize.w := 1;
479 if (lc.wantsize.h < 1) then lc.wantsize.h := 1;
480 lc.maxsize := msz;
481 if (lc.maxsize.w < lc.wantsize.w) then lc.maxsize.w := lc.wantsize.w;
482 if (lc.maxsize.h < lc.wantsize.h) then lc.maxsize.h := lc.wantsize.h;
483 end;
486 procedure TFlexLayouterBase.firstPass ();
487 var
488 f, c: Integer;
489 needRecalcMaxSize: Boolean;
490 gtype: Integer;
491 grp: PLayGroup;
492 maxsz: Integer;
493 cidx: LayControlIdx;
494 begin
495 // reset all 'laywrap' flags for controls, set initial 'wantsize'
496 for f := 0 to High(ctlist) do
497 begin
498 ctlist[f].didWrap := false;
499 ctlist[f].wantsize := ctlist[f].ctl.getDefSize;
500 end;
501 // setup sizes
502 calcMaxSizeInternal(0); // this also sets `tempFlex`
503 // find max size for group, adjust 'wantsize' controls to group max size
504 needRecalcMaxSize := false;
505 for gtype := 0 to 1 do
506 begin
507 for f := 0 to High(groups[gtype]) do
508 begin
509 grp := @groups[gtype][f];
510 maxsz := 0;
511 for c := 0 to High(grp.ctls) do
512 begin
513 cidx := grp.ctls[c];
514 if (maxsz < ctlist[cidx].wantsize[gtype]) then maxsz := ctlist[cidx].wantsize[gtype];
515 end;
516 for c := 0 to High(grp.ctls) do
517 begin
518 cidx := grp.ctls[c];
519 if (maxsz <> ctlist[cidx].wantsize[gtype]) then
520 begin
521 needRecalcMaxSize := true;
522 ctlist[cidx].wantsize[gtype] := maxsz;
523 end;
524 end;
525 end;
526 end;
527 // recalc maxsize if necessary
528 if (needRecalcMaxSize) then calcMaxSizeInternal(0);
529 // set flags
530 firstTime := true;
531 end;
534 procedure TFlexLayouterBase.fixLine (me: PLayControl; i0, i1: LayControlIdx; cury: Integer; var spaceLeft: Single; var flexTotal: Integer; var flexBoxCount: Integer);
535 var
536 curx: Integer;
537 lc: PLayControl;
538 osz: TLaySize;
539 toadd: Integer;
540 begin
541 curx := 0;
542 while (i0 <> i1) do
543 begin
544 lc := @ctlist[i0];
545 osz := lc.desiredsize;
546 lc.desiredsize := lc.wantsize;
547 lc.desiredpos.x := curx;
548 lc.desiredpos.y := cury;
549 curx += lc.desiredsize.w;
550 // fix flexbox size
551 if (lc.tempFlex > 0) and (spaceLeft > 0) then
552 begin
553 toadd := trunc(spaceLeft*lc.tempFlex/flexTotal+0.5);
554 if (toadd > 0) then
555 begin
556 // size changed
557 lc.desiredsize.w += toadd;
558 curx += toadd;
559 // compensate (crudely) rounding errors
560 if (curx > me.desiredsize.w) then begin lc.desiredsize.w -= 1; curx -= 1; end;
561 // relayout children
562 layBox(lc.firstChild);
563 end;
564 end;
565 if (lc.inGroup) and (not lc.desiredsize.equals(osz)) then groupElementChanged := true;
566 i0 := lc.nextSibling;
567 end;
568 flexTotal := 0;
569 flexBoxCount := 0;
570 spaceLeft := me.wantsize.w;
571 end;
574 // do box layouting; call `layBox()` recursively if necessary
575 procedure TFlexLayouterBase.layBox (boxidx: LayControlIdx);
576 var
577 me: PLayControl;
578 flexTotal: Integer; // total sum of flex fields
579 flexBoxCount: Integer; // number of boxes
580 spaceLeft: Single;
581 cury: Integer;
582 maxwdt, maxhgt: Integer;
583 lineStartIdx: LayControlIdx;
584 lc: PLayControl;
585 doWrap: Boolean;
586 toadd: Integer;
587 begin
588 if (boxidx < 0) or (boxidx >= Length(ctlist)) then exit;
589 me := @ctlist[boxidx];
591 // if we have no children, just set desired size and exit
592 me.desiredsize := me.wantsize;
593 if (me.firstChild = -1) then exit;
595 // first, layout all children; also, gather some flex data
596 for lc in forChildren(boxidx) do layBox(lc.myidx);
598 // second, layout lines, distribute flex data
599 if (me.horizBox) then
600 begin
601 // horizontal boxes
602 cury := 0;
603 maxhgt := 0;
605 fixLine(me, -1, -1, cury, spaceLeft, flexTotal, flexBoxCount); //HACK!
607 lineStartIdx := me.firstChild;
609 for lc in forChildren(boxidx) do
610 begin
611 // new line?
612 doWrap := (not lc.firstInLine) and (lc.lineStart);
613 // need to wrap?
614 if (not doWrap) and (lc.canWrap) and (lc.canWrap) and (lc.desiredsize.w > 0) and (spaceLeft < lc.desiredsize.w) then doWrap := true;
615 if (doWrap) then
616 begin
617 // new line, fix this one
618 if (not lc.didWrap) then
619 begin
620 wrappingChanged := true;
621 lc.didWrap := true;
622 end;
623 fixLine(me, lineStartIdx, lc.myidx, cury, spaceLeft, flexTotal, flexBoxCount);
624 cury += maxhgt;
625 lineStartIdx := lc.myidx;
626 end
627 else
628 begin
629 if (lc.didWrap) then
630 begin
631 wrappingChanged := true;
632 lc.didWrap := false;
633 end;
634 end;
635 spaceLeft -= lc.desiredsize.w;
636 if (maxhgt < lc.desiredsize.h) then maxhgt := lc.desiredsize.h;
637 if (lc.tempFlex > 0) then
638 begin
639 flexTotal += lc.tempFlex;
640 flexBoxCount += 1;
641 end;
642 end;
643 // fix last line
644 fixLine(me, lineStartIdx, -1, cury, spaceLeft, flexTotal, flexBoxCount);
645 end
646 else
647 begin
648 // vertical boxes
649 maxwdt := 0;
650 flexTotal := 0;
651 flexBoxCount := 0;
652 spaceLeft := me.wantsize.h;
654 // calc flex
655 for lc in forChildren(boxidx) do
656 begin
657 spaceLeft -= lc.desiredsize.h;
658 if (maxwdt < lc.desiredsize.w) then maxwdt := lc.desiredsize.w;
659 if (lc.tempFlex > 0) then
660 begin
661 flexTotal += lc.tempFlex;
662 flexBoxCount += 1;
663 end;
664 end;
666 // distribute space
667 cury := 0;
668 for lc in forChildren(boxidx) do
669 begin
670 lc.desiredsize := lc.wantsize;
671 lc.desiredpos.x := 0;
672 lc.desiredpos.y := cury;
673 cury += lc.desiredsize.h;
674 // fix flexbox size
675 if (lc.tempFlex > 0) and (spaceLeft > 0) then
676 begin
677 toadd := trunc(spaceLeft*lc.tempFlex/flexTotal+0.5);
678 if (toadd > 0) then
679 begin
680 // size changed
681 lc.desiredsize.h += toadd;
682 cury += toadd;
683 // compensate (crudely) rounding errors
684 if (cury > me.desiredsize.h) then begin lc.desiredsize.h -= 1; cury -= 1; end;
685 // relayout children
686 layBox(lc.firstChild);
687 end;
688 end;
689 end;
690 end;
691 end;
694 (*
695 second pass:
696 calcluate desired sizes (process flexes) using 'wantsize', set 'desiredsize' and 'desiredpos'
697 if control has children, call 'second pass' recursively with this control as parent
698 flags set:
699 'group-element-changed', if any group element size was changed
700 'wrapping-changed', if not 'firsttime', and wrapping was changed (i.e. first pass will not set the flag)
701 *)
702 procedure TFlexLayouterBase.secondPass ();
703 begin
704 // reset flags
705 groupElementChanged := false;
706 wrappingChanged := false;
708 if (Length(ctlist) > 0) then
709 begin
710 ctlist[0].desiredpos := TLayPos.Create(0, 0);
711 layBox(0);
712 end;
714 // fix 'wrapping-changed' flag
715 if (firstTime) then begin wrappingChanged := false; firstTime := false; end;
716 end;
719 (*
720 third pass:
721 if 'group-element-changed':
722 for each group: adjust controls to max desired size (wantsize), set 'temp-flex' flags to 0 for 'em, set 'second-again' flag
723 for other controls: if 'desiredsize' > 'maxsize', set 'wantsize' to 'maxsize', set 'temp-flex' flag to 0, set 'second-again' flag
724 if 'second-again' or 'wrapping-changed':
725 reset 'second-again'
726 reset 'wrapping-changed'
727 reset 'firsttime'
728 goto second pass
729 *)
730 procedure TFlexLayouterBase.thirdPass ();
731 var
732 secondAgain: Boolean;
733 begin
734 while true do
735 begin
736 secondAgain := false;
737 if (groupElementChanged) then
738 begin
739 // do it
740 end;
741 if (not secondAgain) and (not wrappingChanged) then break;
742 firstTime := false;
743 secondPass();
744 end;
745 end;
748 (*
749 fourth pass:
750 set 'actualsize' and 'actualpos' to 'desiredsize' and 'desiredpos'
751 return
752 *)
753 procedure TFlexLayouterBase.fourthPass ();
754 var
755 f: Integer;
756 begin
757 for f := 0 to High(ctlist) do
758 begin
759 ctlist[f].ctl.setActualSizePos(ctlist[f].desiredpos, ctlist[f].desiredsize);
760 end;
761 end;
764 procedure TFlexLayouterBase.layout ();
765 begin
766 firstPass();
767 secondPass();
768 thirdPass();
769 fourthPass();
770 end;
773 procedure TFlexLayouterBase.dumpFlat ();
774 var
775 f: Integer;
776 lc: PLayControl;
777 ds, ms: TLaySize;
778 begin
779 for f := 0 to High(ctlist) do
780 begin
781 lc := @ctlist[f];
782 ds := lc.ctl.getDefSize;
783 ms := lc.ctl.getMaxSize;
784 writeln(lc.myidx, ': wantsize:', lc.wantsize.toString(), '; desiredsize=', lc.desiredsize.toString(), '; maxsize=', lc.maxsize.toString(), '; tempFlex=', lc.tempFlex, '; flags=', lc.flags,
785 '; parent=', lc.parent, '; next=', lc.nextSibling, '; child=', lc.firstChild, '; ctl.size=', ds.toString(), '; ctl.maxsize=', ms.toString());
786 end;
787 end;
790 procedure TFlexLayouterBase.dumpList (cidx: LayControlIdx; indent: Integer);
791 var
792 lc: PLayControl;
793 f: Integer;
794 begin
795 while (cidx >= 0) do
796 begin
797 lc := @ctlist[cidx];
798 for f := 0 to High(indent) do write(' ');
799 writeln(lc.myidx, ': wantsize:', lc.wantsize.toString, '; desiredsize=', lc.desiredsize.toString, '; maxsize=', lc.maxsize.toString, '; tempFlex=', lc.tempFlex, '; despos=', lc.desiredpos.toString);
800 dumpList(lc.firstChild, indent+2);
801 cidx := lc.nextSibling;
802 end;
803 end;
806 procedure TFlexLayouterBase.dump ();
807 begin
808 dumpList(0, 0);
809 end;
812 // ////////////////////////////////////////////////////////////////////////// //
813 (*
814 void main () begin
815 auto win := new GuiControl();
816 (win ~= new GuiControl()).mSize := TLaySize(10, 5);
817 (win ~= new GuiControl()).mSize := TLaySize(16, 8);
819 //win.mSize := TLaySize(40, 20);
821 auto lay := TFlexLayouterBase!GuiControl();
822 lay.setup(win);
824 writeln('============================');
825 lay.dumpFlat();
827 writeln('=== initial ===');
828 lay.dump();
830 //lay.calcMaxSizeInternal(0);
831 /*
832 lay.firstPass();
833 writeln('=== after first pass ===');
834 lay.dump();
836 lay.secondPass();
837 writeln('=== after second pass ===');
838 lay.dump();
839 */
840 lay.layout();
841 writeln('=== final ===');
842 lay.dump();
843 *)
844 end.