DEADSOFTWARE

FlexUI: don't change window size in "fit to screen" mode if size was already set
[d2df-sdl.git] / src / flexui / fui_flexlay.pas
1 (* coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *)
17 {$INCLUDE ../shared/a_modes.inc}
18 unit fui_flexlay;
19 (*
20 control default size will be increased by margins
21 negative margins are ignored
22 ControlT:
23 procedure layPrepare (); // called before registering control in layouter
24 function getDefSize (): TLaySize; // default size; <0: use max size
25 function getMargins (): TLayMargins;
26 function getMaxSize (): TLaySize; // max size; <0: set to some huge value
27 function getFlex (): Integer; // <=0: not flexible
28 function isHorizBox (): Boolean; // horizontal layout for children?
29 function canWrap (): Boolean; // for horizontal boxes: can wrap children? for child: `false` means 'nonbreakable at *next* ctl'
30 function isLineStart (): Boolean; // `true` if this ctl should start a new line; ignored for vertical boxes
31 function getAlign (): Integer; // aligning in non-main direction: <0: left/up; 0: center; >0: right/down
32 function getExpand (): Boolean; // expanding in non-main direction: `true` will ignore align and eat all available space
33 procedure setActualSizePos (constref apos: TLayPos; constref asize: TLaySize);
34 function getHGroup (): AnsiString; // empty: not grouped
35 function getVGroup (): AnsiString; // empty: not grouped
36 function nextSibling (): ControlT;
37 function firstChild (): ControlT;
38 *)
40 interface
42 uses
43 fui_common;
46 // ////////////////////////////////////////////////////////////////////////// //
47 type
48 generic TFlexLayouterBase<ControlT> = class
49 public
50 type CtlT = ControlT;
52 private
53 type LayControlIdx = Integer;
55 private
56 // flags
57 const
58 FlagHorizBox = LongWord(1) shl 0; // horizontal layout for children
59 FlagLineStart = LongWord(1) shl 1;
60 FlagLineCanWrap = LongWord(1) shl 2;
61 // internal
62 FlagLineDidWrap = LongWord(1) shl 3; // will be set when line was wrapped
63 FlagInGroup = LongWord(1) shl 4; // set if this control is a member of any group
64 FlagExpand = LongWord(1) shl 5;
65 FlagLineFirst = LongWord(1) shl 6;
67 private
68 type
69 PLayControl = ^TLayControl;
70 TLayControl = record
71 public
72 myidx: LayControlIdx;
73 tempFlex: Integer;
74 flags: LongWord; // see below
75 aligndir: Integer;
76 startsize: TLaySize; // current
77 desiredsize: TLaySize;
78 maxsize: TLaySize;
79 margins: TLayMargins; // can never be negative
80 desiredpos: TLayPos;
81 ctl: ControlT;
82 parent: LayControlIdx; // = -1;
83 firstChild: LayControlIdx; // = -1;
84 nextSibling: LayControlIdx; // = -1;
86 private
87 function getDidWrap (): Boolean; inline;
88 procedure setDidWrap (v: Boolean); inline;
90 public
91 procedure initialize (); inline;
93 function horizBox (): Boolean; inline;
94 function lineStart (): Boolean; inline;
95 function canWrap (): Boolean; inline;
96 function inGroup (): Boolean; inline;
97 function firstInLine (): Boolean; inline;
99 function getExpand (): Boolean; inline;
100 procedure setExpand (v: Boolean); inline;
102 function alignLeft (): Boolean; inline;
103 function alignTop (): Boolean; inline;
104 function alignRight (): Boolean; inline;
105 function alignBottom (): Boolean; inline;
106 function alignCenter (): Boolean; inline;
108 public
109 property didWrap: Boolean read getDidWrap write setDidWrap;
110 property expand: Boolean read getExpand write setExpand;
111 end;
113 PLayGroup = ^TLayGroup;
114 TLayGroup = record
115 name: AnsiString;
116 ctls: array of LayControlIdx;
117 end;
119 TLayCtlArray = array of TLayControl;
120 TLayGrpArray = array of TLayGroup;
122 private
123 ctlist: TLayCtlArray;
124 groups: array[0..1] of TLayGrpArray; // horiz, vert
126 firstTime: Boolean;
127 groupElementChanged: Boolean;
128 wrappingChanged: Boolean;
130 private
131 procedure fixFlags (cidx: LayControlIdx);
132 procedure doChildren (parent: LayControlIdx; child: ControlT);
133 procedure appendToGroup (const gname: AnsiString;cidx: LayControlIdx;gidx: Integer);
134 procedure setupGroups ();
136 // this also sets `tempFlex`
137 procedure calcMaxSizeInternal (cidx: LayControlIdx);
139 procedure fixLine (me: PLayControl; i0, i1: LayControlIdx; var cury: Integer; var spaceLeft: Single);
140 // do box layouting; call `layBox()` recursively if necessary
141 procedure layBox (boxidx: LayControlIdx);
143 procedure firstPass ();
144 procedure secondPass ();
145 procedure thirdPass ();
146 procedure fourthPass ();
148 procedure dumpList (cidx: LayControlIdx; indent: Integer);
150 public
151 type
152 TChildrenEnumerator = record
153 private
154 ctls: TLayCtlArray;
155 cur: Integer;
156 first: Boolean;
157 public
158 constructor Create (constref actls: TLayCtlArray; acur: Integer);
159 function moveNext (): Boolean; inline;
160 function getCurrent (): PLayControl; inline;
161 function getEnumerator (): TChildrenEnumerator; inline;
162 property current: PLayControl read getCurrent;
163 end;
165 public
166 constructor Create ();
167 destructor Destroy (); override;
169 // clear build lists
170 procedure clear ();
172 // build control and group lists
173 procedure setup (root: ControlT);
175 function forChildren (cidx: LayControlIdx): TChildrenEnumerator; inline;
177 procedure layout ();
179 procedure dumpFlat ();
180 procedure dump ();
181 end;
184 implementation
186 uses
187 utils;
190 // ////////////////////////////////////////////////////////////////////////// //
191 procedure TFlexLayouterBase.TLayControl.initialize (); inline;
192 begin
193 FillChar(self, 0, sizeof(self));
194 parent := -1;
195 firstChild := -1;
196 nextSibling := -1;
197 end;
199 function TFlexLayouterBase.TLayControl.horizBox (): Boolean; inline; begin result := ((flags and FlagHorizBox) <> 0); end;
200 function TFlexLayouterBase.TLayControl.lineStart (): Boolean; inline; begin result := ((flags and FlagLineStart) <> 0); end;
201 function TFlexLayouterBase.TLayControl.canWrap (): Boolean; inline; begin result := ((flags and FlagLineCanWrap) <> 0); end;
202 function TFlexLayouterBase.TLayControl.inGroup (): Boolean; inline; begin result := ((flags and FlagInGroup) <> 0); end;
203 function TFlexLayouterBase.TLayControl.firstInLine (): Boolean; inline; begin result := ((flags and FlagLineFirst) <> 0); end;
205 function TFlexLayouterBase.TLayControl.getDidWrap (): Boolean; inline; begin result := ((flags and FlagLineDidWrap) <> 0); end;
206 procedure TFlexLayouterBase.TLayControl.setDidWrap (v: Boolean); inline; begin if (v) then flags := flags or FlagLineDidWrap else flags := flags and (not FlagLineDidWrap); end;
208 function TFlexLayouterBase.TLayControl.getExpand (): Boolean; inline; begin result := ((flags and FlagExpand) <> 0); end;
209 procedure TFlexLayouterBase.TLayControl.setExpand (v: Boolean); inline; begin if (v) then flags := flags or FlagExpand else flags := flags and (not FlagExpand); end;
211 function TFlexLayouterBase.TLayControl.alignLeft (): Boolean; inline; begin result := (aligndir < 0); end;
212 function TFlexLayouterBase.TLayControl.alignTop (): Boolean; inline; begin result := (aligndir < 0); end;
213 function TFlexLayouterBase.TLayControl.alignRight (): Boolean; inline; begin result := (aligndir > 0); end;
214 function TFlexLayouterBase.TLayControl.alignBottom (): Boolean; inline; begin result := (aligndir > 0); end;
215 function TFlexLayouterBase.TLayControl.alignCenter (): Boolean; inline; begin result := (aligndir = 0); end;
218 // ////////////////////////////////////////////////////////////////////////// //
219 constructor TFlexLayouterBase.TChildrenEnumerator.Create (constref actls: TLayCtlArray; acur: Integer);
220 begin
221 ctls := actls;
222 cur := acur;
223 first := true;
224 end;
226 function TFlexLayouterBase.TChildrenEnumerator.moveNext (): Boolean; inline;
227 begin
228 if first then
229 begin
230 if (cur >= 0) and (cur < Length(ctls)) then cur := ctls[cur].firstChild else cur := -1;
231 first := false;
232 end
233 else
234 begin
235 cur := ctls[cur].nextSibling;
236 end;
237 result := (cur >= 0);
238 end;
240 function TFlexLayouterBase.TChildrenEnumerator.getCurrent (): PLayControl; inline;
241 begin
242 result := @ctls[cur];
243 end;
245 function TFlexLayouterBase.TChildrenEnumerator.getEnumerator (): TChildrenEnumerator; inline;
246 begin
247 result := self;
248 end;
251 // ////////////////////////////////////////////////////////////////////////// //
252 constructor TFlexLayouterBase.Create ();
253 begin
254 ctlist := nil;
255 groups[0] := nil;
256 groups[1] := nil;
258 firstTime := false;
259 groupElementChanged := false;
260 wrappingChanged := false;
261 end;
264 destructor TFlexLayouterBase.Destroy ();
265 begin
266 clear();
267 inherited;
268 end;
271 function TFlexLayouterBase.forChildren (cidx: LayControlIdx): TChildrenEnumerator; inline;
272 begin
273 result := TChildrenEnumerator.Create(ctlist, cidx);
274 end;
277 procedure TFlexLayouterBase.clear ();
278 begin
279 ctlist := nil;
280 groups[0] := nil;
281 groups[1] := nil;
282 end;
285 procedure TFlexLayouterBase.fixFlags (cidx: LayControlIdx);
286 var
287 lc: PLayControl;
288 begin
289 assert((cidx >= 0) and (cidx < Length(ctlist)));
290 lc := @ctlist[cidx];
291 //lc.flags := 0;
292 if (lc.ctl.isHorizBox) then lc.flags := lc.flags or FlagHorizBox;
293 if (lc.ctl.isLineStart) then lc.flags := lc.flags or FlagLineStart;
294 if (lc.ctl.canWrap) then lc.flags := lc.flags or FlagLineCanWrap;
295 if (lc.ctl.getExpand) then lc.flags := lc.flags or FlagExpand;
296 lc.aligndir := lc.ctl.getAlign;
297 end;
300 procedure TFlexLayouterBase.doChildren (parent: LayControlIdx; child: ControlT);
301 var
302 cidx: LayControlIdx = -1;
303 lc: PLayControl;
304 begin
305 assert((parent >= 0) and (parent < Length(ctlist)));
306 assert(ctlist[parent].firstChild = -1);
307 while (child <> nil) do
308 begin
309 child.layPrepare;
310 SetLength(ctlist, Length(ctlist)+1);
311 lc := @ctlist[High(ctlist)];
312 lc.initialize();
313 if (cidx = -1) then
314 begin
315 cidx := LayControlIdx(High(ctlist));
316 ctlist[parent].firstChild := cidx;
317 // first child is always linestart
318 lc.flags := lc.flags or FlagLineStart or FlagLineFirst;
319 end
320 else
321 begin
322 ctlist[cidx].nextSibling := LayControlIdx(High(ctlist));
323 cidx := LayControlIdx(High(ctlist));
324 end;
325 lc.myidx := cidx;
326 lc.ctl := child;
327 lc.parent := parent;
328 fixFlags(cidx);
329 doChildren(cidx, child.firstChild);
330 child := child.nextSibling;
331 end;
332 end;
335 procedure TFlexLayouterBase.appendToGroup (const gname: AnsiString; cidx: LayControlIdx; gidx: Integer);
336 var
337 f: Integer;
338 begin
339 if (Length(gname) = 0) then exit;
340 assert((cidx >= 0) and (cidx < Length(ctlist)));
341 assert((gidx = 0) or (gidx = 1));
342 ctlist[cidx].flags := ctlist[cidx].flags or FlagInGroup;
343 for f := 0 to High(groups[gidx]) do
344 begin
345 if (groups[gidx][f].name = gname) then
346 begin
347 SetLength(groups[gidx][f].ctls, Length(groups[gidx][f].ctls)+1);
348 groups[gidx][f].ctls[High(groups[gidx][f].ctls)] := cidx;
349 exit;
350 end;
351 end;
352 // new group
353 f := Length(groups[gidx]);
354 SetLength(groups[gidx], f+1);
355 groups[gidx][f].name := gname;
356 SetLength(groups[gidx][f].ctls, Length(groups[gidx][f].ctls)+1);
357 groups[gidx][f].ctls[High(groups[gidx][f].ctls)] := cidx;
358 end;
361 procedure TFlexLayouterBase.setupGroups ();
362 var
363 idx: Integer;
364 lc: PLayControl;
365 begin
366 for idx := 0 to High(ctlist) do
367 begin
368 lc := @ctlist[idx];
369 appendToGroup(lc.ctl.getHGroup, LayControlIdx(idx), 0);
370 appendToGroup(lc.ctl.getVGroup, LayControlIdx(idx), 1);
371 end;
372 end;
375 // build control and group lists
376 procedure TFlexLayouterBase.setup (root: ControlT);
377 begin
378 clear();
379 if (root = nil) then exit;
380 root.layPrepare;
381 try
382 SetLength(ctlist, 1);
383 ctlist[0].initialize();
384 ctlist[0].myidx := 0;
385 ctlist[0].ctl := root;
386 fixFlags(0);
387 ctlist[0].flags := ctlist[0].flags or FlagLineStart or FlagLineFirst;
388 doChildren(0, root.firstChild);
389 setupGroups();
390 except
391 clear();
392 raise;
393 end;
394 end;
397 // this also sets `tempFlex`
398 procedure TFlexLayouterBase.calcMaxSizeInternal (cidx: LayControlIdx);
399 var
400 lc, c: PLayControl;
401 msz: TLaySize;
402 negw, negh: Boolean;
403 zerow: Boolean;
404 curwdt, curhgt, totalhgt: Integer;
405 doWrap: Boolean;
406 begin
407 if (cidx < 0) or (cidx >= Length(ctlist)) then exit;
409 lc := @ctlist[cidx];
410 msz := lc.ctl.getMaxSize;
411 negw := (lc.startsize.w < 0);
412 negh := (lc.startsize.h < 0);
413 zerow := (lc.startsize.w = 0);
415 lc.tempFlex := lc.ctl.getFlex;
417 for c in forChildren(cidx) do calcMaxSizeInternal(c.myidx);
419 if (lc.horizBox) then
420 begin
421 // horizontal boxes
422 if (negw) then lc.tempFlex := 0; // size is negative: don't expand
423 curwdt := lc.margins.horiz;
424 curhgt := 0;
425 totalhgt := lc.margins.vert;
426 for c in forChildren(cidx) do
427 begin
428 // new line?
429 doWrap := (not c.firstInLine) and (c.lineStart);
430 // need to wrap?
431 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;
432 if (doWrap) then
433 begin
434 totalhgt += curhgt;
435 if (lc.startsize.w < curwdt) then lc.startsize.w := curwdt;
436 curwdt := 0;
437 curhgt := 0;
438 end;
439 curwdt += c.startsize.w;
440 if (curhgt < c.startsize.h) then curhgt := c.startsize.h;
441 end;
442 //writeln('00: ', cidx, ': totalhgt=', totalhgt);
443 totalhgt += curhgt;
444 //writeln('01: ', cidx, ': totalhgt=', totalhgt);
445 if (lc.startsize.w < curwdt) then lc.startsize.w := curwdt;
446 if (lc.startsize.h < totalhgt) then lc.startsize.h := totalhgt;
447 end
448 else
449 begin
450 // vertical boxes
451 if (negh) then lc.tempFlex := 0; // size is negative: don't expand
452 curhgt := lc.margins.vert;
453 for c in forChildren(cidx) do
454 begin
455 if (lc.startsize.w < c.startsize.w+lc.margins.horiz) then lc.startsize.w := c.startsize.w+lc.margins.horiz;
456 curhgt += c.startsize.h;
457 end;
458 if (lc.startsize.h < curhgt) then lc.startsize.h := curhgt;
459 end;
460 if (lc.startsize.w < 0) then lc.startsize.w := 0;
461 if (lc.startsize.h < 0) then lc.startsize.h := 0;
463 lc.maxsize := msz;
464 if (lc.maxsize.w < lc.startsize.w) then begin if (lc.maxsize.w >= 0) then lc.maxsize.w := lc.startsize.w; end;
465 if (lc.maxsize.h < lc.startsize.h) then begin if (lc.maxsize.h >= 0) then lc.maxsize.h := lc.startsize.h; end;
467 if (msz.w < 0) then msz.w := lc.startsize.w;
468 if (msz.h < 0) then msz.h := lc.startsize.h;
469 lc.maxsize := msz;
470 end;
473 procedure TFlexLayouterBase.firstPass ();
474 var
475 f, c: Integer;
476 needRecalcMaxSize: Boolean;
477 gtype: Integer;
478 grp: PLayGroup;
479 maxsz: Integer;
480 cidx: LayControlIdx;
481 ct: PLayControl;
482 mr: TLayMargins;
483 begin
484 // reset all 'laywrap' flags for controls, set initial 'startsize'
485 for f := 0 to High(ctlist) do
486 begin
487 ctlist[f].didWrap := false;
488 ctlist[f].startsize := ctlist[f].ctl.getDefSize;
489 mr := ctlist[f].ctl.getMargins;
490 ctlist[f].margins := mr;
491 //ctlist[f].startsize.w += mr.horiz;
492 //ctlist[f].startsize.h += mr.vert;
493 end;
494 // setup sizes
495 calcMaxSizeInternal(0); // this also sets `tempFlex`
496 //writeln('=== calculated max size (0) ==='); dump();
497 // find max size for group, adjust 'startsize' controls to group max size
498 needRecalcMaxSize := false;
499 for gtype := 0 to 1 do
500 begin
501 for f := 0 to High(groups[gtype]) do
502 begin
503 grp := @groups[gtype][f];
504 maxsz := 0;
505 for c := 0 to High(grp.ctls) do
506 begin
507 cidx := grp.ctls[c];
508 ct := @ctlist[cidx];
509 if (maxsz < ct.startsize[gtype]) then maxsz := ct.startsize[gtype];
510 end;
511 for c := 0 to High(grp.ctls) do
512 begin
513 cidx := grp.ctls[c];
514 ct := @ctlist[cidx];
515 if (maxsz <> ct.startsize[gtype]) then
516 begin
517 needRecalcMaxSize := true;
518 ct.startsize[gtype] := maxsz;
519 end;
520 end;
521 end;
522 end;
523 // recalc maxsize if necessary
524 if (needRecalcMaxSize) then calcMaxSizeInternal(0);
525 // set "desired size" to "start size"
526 for f := 0 to High(ctlist) do ctlist[f].desiredsize := ctlist[f].startsize;
527 // set flags
528 firstTime := true;
529 //writeln('=== calculated max size (final) ==='); dump();
530 end;
533 procedure TFlexLayouterBase.fixLine (me: PLayControl; i0, i1: LayControlIdx; var cury: Integer; var spaceLeft: Single);
534 var
535 flexTotal: Integer = 0; // total sum of flex fields
536 flexBoxCount: Integer = 0; // number of boxes
537 curx: Integer;
538 lc: PLayControl;
539 osz: TLaySize;
540 toadd: Integer;
541 sti0: Integer;
542 lineh: Integer;
543 begin
544 curx := me.margins.left;
545 sti0 := i0;
546 // calc minimal line height, count flexboxes
547 lineh := 0;
548 while (i0 <> i1) do
549 begin
550 lc := @ctlist[i0];
551 lineh := nmax(lineh, lc.startsize.h);
552 if (lc.tempFlex > 0) then begin flexTotal += lc.tempFlex; flexBoxCount += 1; end;
553 i0 := lc.nextSibling;
554 end;
555 // distribute space, expand/align
556 i0 := sti0;
557 while (i0 <> i1) do
558 begin
559 lc := @ctlist[i0];
560 osz := lc.desiredsize;
561 lc.desiredsize := lc.startsize;
562 lc.desiredpos.x := curx;
563 lc.desiredpos.y := cury;
564 curx += lc.desiredsize.w;
565 // fix flexbox size
566 if (lc.tempFlex > 0) and (spaceLeft > 0) then
567 begin
568 toadd := trunc(spaceLeft*lc.tempFlex/flexTotal+0.5);
569 if (toadd > 0) then
570 begin
571 // size changed
572 lc.desiredsize.w += toadd;
573 curx += toadd;
574 // compensate (crudely) rounding errors
575 if (curx > me.desiredsize.w-me.margins.horiz) then begin lc.desiredsize.w -= 1; curx -= 1; end;
576 // relayout children
577 layBox(lc.firstChild);
578 end;
579 end;
580 // expand or align
581 if (lc.expand) then lc.desiredsize.h := nmax(1, lineh) // expand
582 else if (lc.alignBottom) then lc.desiredpos.y := cury+(lineh-lc.desiredsize.h) // bottom align
583 else if (lc.alignCenter) then lc.desiredpos.y := cury+(lineh-lc.desiredsize.h) div 2; // center
584 if (not osz.equals(lc.desiredsize)) then
585 begin
586 if (lc.inGroup) then groupElementChanged := true;
587 // relayout children
588 layBox(lc.firstChild);
589 end;
590 i0 := lc.nextSibling;
591 end;
592 flexTotal := 0;
593 flexBoxCount := 0;
594 spaceLeft := me.desiredsize.w-me.margins.horiz;
595 cury += lineh;
596 end;
599 // do box layouting; call `layBox()` recursively if necessary
600 procedure TFlexLayouterBase.layBox (boxidx: LayControlIdx);
601 var
602 me: PLayControl;
603 flexTotal: Integer; // total sum of flex fields
604 flexBoxCount: Integer; // number of boxes
605 spaceLeft: Single;
606 cury: Integer;
607 maxwdt: Integer;
608 lineStartIdx: LayControlIdx;
609 lc: PLayControl;
610 doWrap: Boolean;
611 toadd: Integer;
612 osz: TLaySize;
613 begin
614 if (boxidx < 0) or (boxidx >= Length(ctlist)) then exit;
615 me := @ctlist[boxidx];
617 // if we have no children, there's nothing to do
618 if (me.firstChild <> -1) then
619 begin
620 // first, layout all children
621 for lc in forChildren(boxidx) do layBox(lc.myidx);
623 // second, layout lines, distribute flex data
624 if (me.horizBox) then
625 begin
626 // horizontal boxes
627 cury := me.margins.top;
629 fixLine(me, -1, -1, cury, spaceLeft); //HACK!
631 lineStartIdx := me.firstChild;
632 for lc in forChildren(boxidx) do
633 begin
634 // new line?
635 doWrap := (not lc.firstInLine) and (lc.lineStart);
636 // need to wrap?
637 if (not doWrap) and (lc.canWrap) and (lc.canWrap) and (lc.desiredsize.w > 0) and (spaceLeft < lc.desiredsize.w) then doWrap := true;
638 if (doWrap) then
639 begin
640 // new line, fix this one
641 if (not lc.didWrap) then begin wrappingChanged := true; lc.didWrap := true; end;
642 fixLine(me, lineStartIdx, lc.myidx, cury, spaceLeft);
643 lineStartIdx := lc.myidx;
644 end
645 else
646 begin
647 if (lc.didWrap) then begin wrappingChanged := true; lc.didWrap := false; end;
648 end;
649 spaceLeft -= lc.desiredsize.w;
650 //if (maxhgt < lc.desiredsize.h) then maxhgt := lc.desiredsize.h;
651 //if (lc.tempFlex > 0) then begin flexTotal += lc.tempFlex; flexBoxCount += 1; end;
652 end;
653 // fix last line
654 fixLine(me, lineStartIdx, -1, cury, spaceLeft);
655 end
656 else
657 begin
658 // vertical boxes
659 maxwdt := 0;
660 flexTotal := 0;
661 flexBoxCount := 0;
662 spaceLeft := me.desiredsize.h-me.margins.vert;
664 // calc flex
665 for lc in forChildren(boxidx) do
666 begin
667 spaceLeft -= lc.desiredsize.h;
668 if (maxwdt < lc.desiredsize.w) then maxwdt := lc.desiredsize.w;
669 if (lc.tempFlex > 0) then begin flexTotal += lc.tempFlex; flexBoxCount += 1; end;
670 end;
672 // distribute space
673 cury := me.margins.top;
674 //writeln('me: ', boxidx, '; margins: ', me.margins.toString);
675 for lc in forChildren(boxidx) do
676 begin
677 osz := lc.desiredsize;
678 lc.desiredsize := lc.startsize;
679 lc.desiredpos.x := me.margins.left;
680 lc.desiredpos.y := cury;
681 cury += lc.desiredsize.h;
682 // fix flexbox size
683 if (lc.tempFlex > 0) and (spaceLeft > 0) then
684 begin
685 toadd := trunc(spaceLeft*lc.tempFlex/flexTotal+0.5);
686 if (toadd > 0) then
687 begin
688 // size changed
689 lc.desiredsize.h += toadd;
690 cury += toadd;
691 // compensate (crudely) rounding errors
692 if (cury > me.desiredsize.h-me.margins.vert) then begin lc.desiredsize.h -= 1; cury -= 1; end;
693 end;
694 end;
695 // expand or align
696 if (lc.expand) then lc.desiredsize.w := nmax(1, me.desiredsize.w-me.margins.vert) // expand
697 else if (lc.alignRight) then lc.desiredpos.x := me.desiredsize.w-me.margins.right-lc.desiredsize.w // right align
698 else if (lc.alignCenter) then lc.desiredpos.x := (me.desiredsize.w-lc.desiredsize.w) div 2; // center
699 if (not osz.equals(lc.desiredsize)) then
700 begin
701 if (lc.inGroup) then groupElementChanged := true;
702 // relayout children
703 layBox(lc.firstChild);
704 end;
705 end;
706 end;
707 end;
709 if (me.maxsize.w >= 0) and (me.desiredsize.w > me.maxsize.w) then me.desiredsize.w := me.maxsize.w;
710 if (me.maxsize.h >= 0) and (me.desiredsize.h > me.maxsize.h) then me.desiredsize.h := me.maxsize.h;
711 end;
714 procedure TFlexLayouterBase.secondPass ();
715 begin
716 // reset flags
717 groupElementChanged := false;
718 wrappingChanged := false;
720 if (Length(ctlist) > 0) then
721 begin
722 ctlist[0].desiredpos := TLayPos.Create(0, 0);
723 layBox(0);
724 end;
726 // fix 'wrapping-changed' flag
727 if (firstTime) then begin wrappingChanged := false; firstTime := false; end;
728 end;
731 procedure TFlexLayouterBase.thirdPass ();
732 var
733 secondAgain: Boolean;
734 gtype: Integer;
735 maxsz: Integer;
736 grp: PLayGroup;
737 f, c: Integer;
738 cidx: LayControlIdx;
739 ct: PLayControl;
740 begin
741 while true do
742 begin
743 secondPass();
744 secondAgain := false;
745 if (groupElementChanged) then
746 begin
747 secondAgain := true;
748 // find max size for group, adjust 'startsize' controls to group max size
749 for gtype := 0 to 1 do
750 begin
751 for f := 0 to High(groups[gtype]) do
752 begin
753 grp := @groups[gtype][f];
754 maxsz := 0;
755 for c := 0 to High(grp.ctls) do
756 begin
757 cidx := grp.ctls[c];
758 ct := @ctlist[cidx];
759 ct.expand := false; // don't expand grouped controls anymore
760 if (maxsz < ct.startsize[gtype]) then maxsz := ct.startsize[gtype];
761 end;
762 for c := 0 to High(grp.ctls) do
763 begin
764 cidx := grp.ctls[c];
765 ct := @ctlist[cidx];
766 ct.startsize[gtype] := maxsz;
767 ct.desiredsize[gtype] := maxsz;
768 ct.tempFlex := 0; // don't change control size anymore
769 end;
770 end;
771 end;
772 end
773 else
774 begin
775 for f := 0 to High(ctlist) do
776 begin
777 ct := @ctlist[f];
778 if (ct.inGroup) then
779 begin
780 ct.expand := false; // don't expand grouped controls anymore
781 ct.tempFlex := 0; // don't change control size anymore
782 end;
783 (*
784 for c := 0 to 1 do
785 begin
786 if (ct.maxsize[c] < 0) then continue;
787 if (ct.desiredsize[c] > ct.maxsize[c]) then
788 begin
789 //writeln('ctl #', f, '; dimension #', c, ': desired=', ctlist[f].desiredsize[c], '; max=', ctlist[f].maxsize[c]);
790 ct.startsize[c] := ct.maxsize[c];
791 ct.desiredsize[c] := ct.maxsize[c];
792 ct.tempFlex := 0; // don't change control size anymore
793 secondAgain := true;
794 end;
795 end;
796 *)
797 end;
798 end;
799 if (not secondAgain) and (not wrappingChanged) then break;
800 firstTime := false;
801 end;
802 end;
805 (*
806 fourth pass:
807 set 'actualsize' and 'actualpos' to 'desiredsize' and 'desiredpos'
808 return
809 *)
810 procedure TFlexLayouterBase.fourthPass ();
811 var
812 f: Integer;
813 begin
814 for f := 0 to High(ctlist) do
815 begin
816 ctlist[f].ctl.setActualSizePos(ctlist[f].desiredpos, ctlist[f].desiredsize);
817 end;
818 end;
821 procedure TFlexLayouterBase.layout ();
822 begin
823 firstPass();
824 secondPass();
825 thirdPass();
826 fourthPass();
827 end;
830 procedure TFlexLayouterBase.dumpFlat ();
831 var
832 f: Integer;
833 lc: PLayControl;
834 ds, ms: TLaySize;
835 begin
836 for f := 0 to High(ctlist) do
837 begin
838 lc := @ctlist[f];
839 ds := lc.ctl.getDefSize;
840 ms := lc.ctl.getMaxSize;
841 writeln(lc.myidx, ': startsize:', lc.startsize.toString(), '; desiredsize=', lc.desiredsize.toString(), '; maxsize=', lc.maxsize.toString(), '; tempFlex=', lc.tempFlex, '; flags=', lc.flags,
842 '; parent=', lc.parent, '; next=', lc.nextSibling, '; child=', lc.firstChild, '; ctl.size=', ds.toString(), '; ctl.maxsize=', ms.toString());
843 end;
844 end;
847 procedure TFlexLayouterBase.dumpList (cidx: LayControlIdx; indent: Integer);
848 var
849 lc: PLayControl;
850 f: Integer;
851 begin
852 while (cidx >= 0) do
853 begin
854 lc := @ctlist[cidx];
855 for f := 0 to indent do write(' ');
856 writeln(lc.myidx, ': startsize:', lc.startsize.toString, '; desiredsize=', lc.desiredsize.toString, '; maxsize=', lc.maxsize.toString, '; tempFlex=', lc.tempFlex, '; despos=', lc.desiredpos.toString);
857 dumpList(lc.firstChild, indent+2);
858 cidx := lc.nextSibling;
859 end;
860 end;
863 procedure TFlexLayouterBase.dump ();
864 begin
865 dumpList(0, 0);
866 end;
869 // ////////////////////////////////////////////////////////////////////////// //
870 (*
871 void main () begin
872 auto win := new GuiControl();
873 (win ~= new GuiControl()).mSize := TLaySize(10, 5);
874 (win ~= new GuiControl()).mSize := TLaySize(16, 8);
876 //win.mSize := TLaySize(40, 20);
878 auto lay := TFlexLayouterBase!GuiControl();
879 lay.setup(win);
881 writeln('============================');
882 lay.dumpFlat();
884 writeln('=== initial ===');
885 lay.dump();
887 //lay.calcMaxSizeInternal(0);
888 /*
889 lay.firstPass();
890 writeln('=== after first pass ===');
891 lay.dump();
893 lay.secondPass();
894 writeln('=== after second pass ===');
895 lay.dump();
896 */
897 lay.layout();
898 writeln('=== final ===');
899 lay.dump();
900 *)
901 end.