9692f0d1353f0319fb407204f7feb37dcf8338de
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
101 // flags
102 const
106 // internal
112 private
113 type
116 public
131 private
135 public
153 public
167 private
175 private
181 // this also sets `tempFlex`
184 procedure fixLine (me: PLayControl; i0, i1: LayControlIdx; var cury: Integer; var spaceLeft: Single);
185 // do box layouting; call `layBox()` recursively if necessary
195 public
196 type
198 private
202 public
210 public
214 // clear build lists
217 // build control and group lists
229 implementation
231 uses
232 utils;
235 // ////////////////////////////////////////////////////////////////////////// //
237 begin
244 function TFlexLayouterBase.TLayControl.horizBox (): Boolean; inline; begin result := ((flags and FlagHorizBox) <> 0); end;
245 function TFlexLayouterBase.TLayControl.lineStart (): Boolean; inline; begin result := ((flags and FlagLineStart) <> 0); end;
246 function TFlexLayouterBase.TLayControl.canWrap (): Boolean; inline; begin result := ((flags and FlagLineCanWrap) <> 0); end;
247 function TFlexLayouterBase.TLayControl.inGroup (): Boolean; inline; begin result := ((flags and FlagInGroup) <> 0); end;
248 function TFlexLayouterBase.TLayControl.firstInLine (): Boolean; inline; begin result := ((flags and FlagLineFirst) <> 0); end;
250 function TFlexLayouterBase.TLayControl.getDidWrap (): Boolean; inline; begin result := ((flags and FlagLineDidWrap) <> 0); end;
251 procedure TFlexLayouterBase.TLayControl.setDidWrap (v: Boolean); inline; begin if (v) then flags := flags or FlagLineDidWrap else flags := flags and (not FlagLineDidWrap); end;
253 function TFlexLayouterBase.TLayControl.getExpand (): Boolean; inline; begin result := ((flags and FlagExpand) <> 0); end;
254 procedure TFlexLayouterBase.TLayControl.setExpand (v: Boolean); inline; begin if (v) then flags := flags or FlagExpand else flags := flags and (not FlagExpand); end;
256 function TFlexLayouterBase.TLayControl.alignLeft (): Boolean; inline; begin result := (aligndir < 0); end;
257 function TFlexLayouterBase.TLayControl.alignTop (): Boolean; inline; begin result := (aligndir < 0); end;
258 function TFlexLayouterBase.TLayControl.alignRight (): Boolean; inline; begin result := (aligndir > 0); end;
259 function TFlexLayouterBase.TLayControl.alignBottom (): Boolean; inline; begin result := (aligndir > 0); end;
260 function TFlexLayouterBase.TLayControl.alignCenter (): Boolean; inline; begin result := (aligndir = 0); end;
263 // ////////////////////////////////////////////////////////////////////////// //
264 constructor TFlexLayouterBase.TChildrenEnumerator.Create (constref actls: TLayCtlArray; acur: Integer);
265 begin
272 begin
274 begin
277 end
278 else
279 begin
286 begin
291 begin
296 // ////////////////////////////////////////////////////////////////////////// //
298 begin
310 begin
317 begin
323 begin
331 var
333 begin
336 //lc.flags := 0;
346 var
349 begin
353 begin
359 begin
362 // first child is always linestart
364 end
365 else
366 begin
380 procedure TFlexLayouterBase.appendToGroup (const gname: AnsiString; cidx: LayControlIdx; gidx: Integer);
381 var
383 begin
389 begin
391 begin
394 exit;
397 // new group
407 var
410 begin
412 begin
420 // build control and group lists
422 begin
426 try
435 except
442 // this also sets `tempFlex`
444 var
451 begin
465 begin
466 // horizontal boxes
472 begin
473 // new line?
475 // need to wrap?
476 if (not doWrap) and zerow and (lc.canWrap) and (c.canWrap) and (msz.w > 0) and (curwdt+c.startsize.w > lc.startsize.w) then doWrap := true;
478 begin
487 //writeln('00: ', cidx, ': totalhgt=', totalhgt);
489 //writeln('01: ', cidx, ': totalhgt=', totalhgt);
492 end
493 else
494 begin
495 // vertical boxes
499 begin
500 if (lc.startsize.w < c.startsize.w+lc.margins.horiz) then lc.startsize.w := c.startsize.w+lc.margins.horiz;
514 var
523 begin
524 // reset all 'laywrap' flags for controls, set initial 'startsize'
526 begin
531 //ctlist[f].startsize.w += mr.horiz;
532 //ctlist[f].startsize.h += mr.vert;
534 // setup sizes
536 //writeln('=== calculated max size (0) ==='); dump();
537 // find max size for group, adjust 'startsize' controls to group max size
540 begin
542 begin
546 begin
552 begin
556 begin
563 // recalc maxsize if necessary
565 // set "desired size" to "start size"
567 // set flags
569 //writeln('=== calculated max size (final) ==='); dump();
573 procedure TFlexLayouterBase.fixLine (me: PLayControl; i0, i1: LayControlIdx; var cury: Integer; var spaceLeft: Single);
574 var
583 begin
586 // calc minimal line height, count flexboxes
589 begin
595 // distribute space, expand/align
598 begin
605 // fix flexbox size
607 begin
610 begin
611 // size changed
614 // compensate (crudely) rounding errors
616 // relayout children
620 // expand or align
625 begin
627 // relayout children
639 // do box layouting; call `layBox()` recursively if necessary
641 var
653 begin
657 // if we have no children, there's nothing to do
660 // first, layout all children
663 // second, layout lines, distribute flex data
665 begin
666 // horizontal boxes
673 begin
674 // new line?
676 // need to wrap?
677 if (not doWrap) and (lc.canWrap) and (lc.canWrap) and (lc.desiredsize.w > 0) and (spaceLeft < lc.desiredsize.w) then doWrap := true;
679 begin
680 // new line, fix this one
684 end
685 else
686 begin
690 //if (maxhgt < lc.desiredsize.h) then maxhgt := lc.desiredsize.h;
691 //if (lc.tempFlex > 0) then begin flexTotal += lc.tempFlex; flexBoxCount += 1; end;
693 // fix last line
695 end
696 else
697 begin
698 // vertical boxes
704 // calc flex
706 begin
712 // distribute space
714 //writeln('me: ', boxidx, '; margins: ', me.margins.toString);
716 begin
722 // fix flexbox size
724 begin
727 begin
728 // size changed
731 // compensate (crudely) rounding errors
735 // expand or align
736 if (lc.expand) then lc.desiredsize.w := nmin(lc.maxsize.w, me.desiredsize.w-me.margins.vert) // expand
737 else if (lc.alignRight) then lc.desiredpos.x := me.desiredsize.w-me.margins.right-lc.desiredsize.w // right align
738 else if (lc.alignCenter) then lc.desiredpos.x := (me.desiredsize.w-lc.desiredsize.w) div 2; // center
740 begin
742 // relayout children
750 (*
751 second pass:
752 calcluate desired sizes (process flexes) using 'startsize', set 'desiredsize' and 'desiredpos'
753 if control has children, call 'second pass' recursively with this control as parent
754 flags set:
755 'group-element-changed', if any group element size was changed
756 'wrapping-changed', if not 'firsttime', and wrapping was changed (i.e. first pass will not set the flag)
757 *)
759 begin
760 // reset flags
765 begin
770 // fix 'wrapping-changed' flag
775 (*
776 third pass:
777 if 'group-element-changed':
778 for each group: adjust controls to max desired size (startsize), set 'temp-flex' flags to 0 for 'em, set 'second-again' flag
779 for other controls: if 'desiredsize' > 'maxsize', set 'startsize' to 'maxsize', set 'temp-flex' flag to 0, set 'second-again' flag
780 if 'second-again' or 'wrapping-changed':
781 reset 'second-again'
782 reset 'wrapping-changed'
783 reset 'firsttime'
784 goto second pass
785 *)
787 var
795 begin
797 begin
801 begin
803 // find max size for group, adjust 'startsize' controls to group max size
805 begin
807 begin
811 begin
818 begin
827 end
828 else
829 begin
831 begin
834 begin
839 begin
842 begin
843 //writeln('ctl #', f, '; dimension #', c, ': desired=', ctlist[f].desiredsize[c], '; max=', ctlist[f].maxsize[c]);
858 (*
859 fourth pass:
860 set 'actualsize' and 'actualpos' to 'desiredsize' and 'desiredpos'
861 return
862 *)
864 var
866 begin
868 begin
875 begin
884 var
888 begin
890 begin
894 writeln(lc.myidx, ': startsize:', lc.startsize.toString(), '; desiredsize=', lc.desiredsize.toString(), '; maxsize=', lc.maxsize.toString(), '; tempFlex=', lc.tempFlex, '; flags=', lc.flags,
895 '; parent=', lc.parent, '; next=', lc.nextSibling, '; child=', lc.firstChild, '; ctl.size=', ds.toString(), '; ctl.maxsize=', ms.toString());
901 var
904 begin
906 begin
909 writeln(lc.myidx, ': startsize:', lc.startsize.toString, '; desiredsize=', lc.desiredsize.toString, '; maxsize=', lc.maxsize.toString, '; tempFlex=', lc.tempFlex, '; despos=', lc.desiredpos.toString);
917 begin
922 // ////////////////////////////////////////////////////////////////////////// //
923 (*
924 void main () begin
925 auto win := new GuiControl();
926 (win ~= new GuiControl()).mSize := TLaySize(10, 5);
927 (win ~= new GuiControl()).mSize := TLaySize(16, 8);
929 //win.mSize := TLaySize(40, 20);
931 auto lay := TFlexLayouterBase!GuiControl();
932 lay.setup(win);
934 writeln('============================');
935 lay.dumpFlat();
937 writeln('=== initial ===');
938 lay.dump();
940 //lay.calcMaxSizeInternal(0);
941 /*
942 lay.firstPass();
943 writeln('=== after first pass ===');
944 lay.dump();
946 lay.secondPass();
947 writeln('=== after second pass ===');
948 lay.dump();
949 */
950 lay.layout();
951 writeln('=== final ===');
952 lay.dump();
953 *)