1 {$INCLUDE ../shared/a_modes.inc}
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
94 public
97 private
100 private
103 private
104 // flags
105 const
109 // internal
115 private
116 type
119 public
134 private
138 public
156 public
170 private
178 private
184 // this also sets `tempFlex`
187 procedure fixLine (me: PLayControl; i0, i1: LayControlIdx; var cury: Integer; var spaceLeft: Single);
188 // do box layouting; call `layBox()` recursively if necessary
198 public
199 type
201 private
205 public
213 public
217 // clear build lists
220 // build control and group lists
232 implementation
234 uses
235 utils;
238 // ////////////////////////////////////////////////////////////////////////// //
240 begin
247 // ////////////////////////////////////////////////////////////////////////// //
249 begin
256 function TFlexLayouterBase.TLayControl.horizBox (): Boolean; inline; begin result := ((flags and FlagHorizBox) <> 0); end;
257 function TFlexLayouterBase.TLayControl.lineStart (): Boolean; inline; begin result := ((flags and FlagLineStart) <> 0); end;
258 function TFlexLayouterBase.TLayControl.canWrap (): Boolean; inline; begin result := ((flags and FlagLineCanWrap) <> 0); end;
259 function TFlexLayouterBase.TLayControl.inGroup (): Boolean; inline; begin result := ((flags and FlagInGroup) <> 0); end;
260 function TFlexLayouterBase.TLayControl.firstInLine (): Boolean; inline; begin result := ((flags and FlagLineFirst) <> 0); end;
262 function TFlexLayouterBase.TLayControl.getDidWrap (): Boolean; inline; begin result := ((flags and FlagLineDidWrap) <> 0); end;
263 procedure TFlexLayouterBase.TLayControl.setDidWrap (v: Boolean); inline; begin if (v) then flags := flags or FlagLineDidWrap else flags := flags and (not FlagLineDidWrap); end;
265 function TFlexLayouterBase.TLayControl.getExpand (): Boolean; inline; begin result := ((flags and FlagExpand) <> 0); end;
266 procedure TFlexLayouterBase.TLayControl.setExpand (v: Boolean); inline; begin if (v) then flags := flags or FlagExpand else flags := flags and (not FlagExpand); end;
268 function TFlexLayouterBase.TLayControl.alignLeft (): Boolean; inline; begin result := (aligndir < 0); end;
269 function TFlexLayouterBase.TLayControl.alignTop (): Boolean; inline; begin result := (aligndir < 0); end;
270 function TFlexLayouterBase.TLayControl.alignRight (): Boolean; inline; begin result := (aligndir > 0); end;
271 function TFlexLayouterBase.TLayControl.alignBottom (): Boolean; inline; begin result := (aligndir > 0); end;
272 function TFlexLayouterBase.TLayControl.alignCenter (): Boolean; inline; begin result := (aligndir = 0); end;
275 // ////////////////////////////////////////////////////////////////////////// //
276 constructor TFlexLayouterBase.TChildrenEnumerator.Create (constref actls: TLayCtlArray; acur: Integer);
277 begin
284 begin
286 begin
289 end
290 else
291 begin
298 begin
303 begin
308 // ////////////////////////////////////////////////////////////////////////// //
310 begin
322 begin
329 begin
335 begin
343 var
345 begin
348 //lc.flags := 0;
358 var
361 begin
365 begin
371 begin
374 // first child is always linestart
376 end
377 else
378 begin
392 procedure TFlexLayouterBase.appendToGroup (const gname: AnsiString; cidx: LayControlIdx; gidx: Integer);
393 var
395 begin
401 begin
403 begin
406 exit;
409 // new group
419 var
422 begin
424 begin
432 // build control and group lists
434 begin
438 try
447 except
454 // this also sets `tempFlex`
456 var
463 begin
477 begin
478 // horizontal boxes
484 begin
485 // new line?
487 // need to wrap?
488 if (not doWrap) and zerow and (lc.canWrap) and (c.canWrap) and (msz.w > 0) and (curwdt+c.startsize.w > lc.startsize.w) then doWrap := true;
490 begin
499 //writeln('00: ', cidx, ': totalhgt=', totalhgt);
501 //writeln('01: ', cidx, ': totalhgt=', totalhgt);
504 end
505 else
506 begin
507 // vertical boxes
511 begin
512 if (lc.startsize.w < c.startsize.w+lc.margins.horiz) then lc.startsize.w := c.startsize.w+lc.margins.horiz;
520 if (lc.maxsize.w < lc.startsize.w) then begin if (lc.maxsize.w >= 0) then lc.maxsize.w := lc.startsize.w; end;
521 if (lc.maxsize.h < lc.startsize.h) then begin if (lc.maxsize.h >= 0) then lc.maxsize.h := lc.startsize.h; end;
526 var
535 begin
536 // reset all 'laywrap' flags for controls, set initial 'startsize'
538 begin
543 //ctlist[f].startsize.w += mr.horiz;
544 //ctlist[f].startsize.h += mr.vert;
546 // setup sizes
548 //writeln('=== calculated max size (0) ==='); dump();
549 // find max size for group, adjust 'startsize' controls to group max size
552 begin
554 begin
558 begin
564 begin
568 begin
575 // recalc maxsize if necessary
577 // set "desired size" to "start size"
579 // set flags
581 //writeln('=== calculated max size (final) ==='); dump();
585 procedure TFlexLayouterBase.fixLine (me: PLayControl; i0, i1: LayControlIdx; var cury: Integer; var spaceLeft: Single);
586 var
595 begin
598 // calc minimal line height, count flexboxes
601 begin
607 // distribute space, expand/align
610 begin
617 // fix flexbox size
619 begin
622 begin
623 // size changed
626 // compensate (crudely) rounding errors
628 // relayout children
632 // expand or align
637 begin
639 // relayout children
651 // do box layouting; call `layBox()` recursively if necessary
653 var
665 begin
669 // if we have no children, there's nothing to do
672 // first, layout all children
675 // second, layout lines, distribute flex data
677 begin
678 // horizontal boxes
685 begin
686 // new line?
688 // need to wrap?
689 if (not doWrap) and (lc.canWrap) and (lc.canWrap) and (lc.desiredsize.w > 0) and (spaceLeft < lc.desiredsize.w) then doWrap := true;
691 begin
692 // new line, fix this one
696 end
697 else
698 begin
702 //if (maxhgt < lc.desiredsize.h) then maxhgt := lc.desiredsize.h;
703 //if (lc.tempFlex > 0) then begin flexTotal += lc.tempFlex; flexBoxCount += 1; end;
705 // fix last line
707 end
708 else
709 begin
710 // vertical boxes
716 // calc flex
718 begin
724 // distribute space
726 //writeln('me: ', boxidx, '; margins: ', me.margins.toString);
728 begin
734 // fix flexbox size
736 begin
739 begin
740 // size changed
743 // compensate (crudely) rounding errors
747 // expand or align
748 if (lc.expand) then lc.desiredsize.w := nminX(lc.maxsize.w, me.desiredsize.w-me.margins.vert) // expand
749 else if (lc.alignRight) then lc.desiredpos.x := me.desiredsize.w-me.margins.right-lc.desiredsize.w // right align
750 else if (lc.alignCenter) then lc.desiredpos.x := (me.desiredsize.w-lc.desiredsize.w) div 2; // center
752 begin
754 // relayout children
762 (*
763 second pass:
764 calcluate desired sizes (process flexes) using 'startsize', set 'desiredsize' and 'desiredpos'
765 if control has children, call 'second pass' recursively with this control as parent
766 flags set:
767 'group-element-changed', if any group element size was changed
768 'wrapping-changed', if not 'firsttime', and wrapping was changed (i.e. first pass will not set the flag)
769 *)
771 begin
772 // reset flags
777 begin
782 // fix 'wrapping-changed' flag
787 (*
788 third pass:
789 if 'group-element-changed':
790 for each group: adjust controls to max desired size (startsize), set 'temp-flex' flags to 0 for 'em, set 'second-again' flag
791 for other controls: if 'desiredsize' > 'maxsize', set 'startsize' to 'maxsize', set 'temp-flex' flag to 0, set 'second-again' flag
792 if 'second-again' or 'wrapping-changed':
793 reset 'second-again'
794 reset 'wrapping-changed'
795 reset 'firsttime'
796 goto second pass
797 *)
799 var
807 begin
809 begin
813 begin
815 // find max size for group, adjust 'startsize' controls to group max size
817 begin
819 begin
823 begin
830 begin
839 end
840 else
841 begin
843 begin
846 begin
851 begin
854 begin
855 //writeln('ctl #', f, '; dimension #', c, ': desired=', ctlist[f].desiredsize[c], '; max=', ctlist[f].maxsize[c]);
870 (*
871 fourth pass:
872 set 'actualsize' and 'actualpos' to 'desiredsize' and 'desiredpos'
873 return
874 *)
876 var
878 begin
880 begin
887 begin
896 var
900 begin
902 begin
906 writeln(lc.myidx, ': startsize:', lc.startsize.toString(), '; desiredsize=', lc.desiredsize.toString(), '; maxsize=', lc.maxsize.toString(), '; tempFlex=', lc.tempFlex, '; flags=', lc.flags,
907 '; parent=', lc.parent, '; next=', lc.nextSibling, '; child=', lc.firstChild, '; ctl.size=', ds.toString(), '; ctl.maxsize=', ms.toString());
913 var
916 begin
918 begin
921 writeln(lc.myidx, ': startsize:', lc.startsize.toString, '; desiredsize=', lc.desiredsize.toString, '; maxsize=', lc.maxsize.toString, '; tempFlex=', lc.tempFlex, '; despos=', lc.desiredpos.toString);
929 begin
934 // ////////////////////////////////////////////////////////////////////////// //
935 (*
936 void main () begin
937 auto win := new GuiControl();
938 (win ~= new GuiControl()).mSize := TLaySize(10, 5);
939 (win ~= new GuiControl()).mSize := TLaySize(16, 8);
941 //win.mSize := TLaySize(40, 20);
943 auto lay := TFlexLayouterBase!GuiControl();
944 lay.setup(win);
946 writeln('============================');
947 lay.dumpFlat();
949 writeln('=== initial ===');
950 lay.dump();
952 //lay.calcMaxSizeInternal(0);
953 /*
954 lay.firstPass();
955 writeln('=== after first pass ===');
956 lay.dump();
958 lay.secondPass();
959 writeln('=== after second pass ===');
960 lay.dump();
961 */
962 lay.layout();
963 writeln('=== final ===');
964 lay.dump();
965 *)