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
147 public
161 private
169 private
175 // this also sets `tempFlex`
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
189 public
190 type
192 private
196 public
204 public
208 // clear build lists
211 // build control and group lists
223 implementation
225 uses
226 utils;
229 // ////////////////////////////////////////////////////////////////////////// //
231 begin
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
260 begin
262 begin
265 end
266 else
267 begin
274 begin
279 begin
284 // ////////////////////////////////////////////////////////////////////////// //
286 begin
298 begin
305 begin
311 begin
319 var
321 begin
324 //lc.flags := 0;
334 var
337 begin
341 begin
347 begin
350 // first child is always linestart
352 end
353 else
354 begin
368 procedure TFlexLayouterBase.appendToGroup (const gname: AnsiString; cidx: LayControlIdx; gidx: Integer);
369 var
371 begin
377 begin
379 begin
382 exit;
385 // new group
395 var
398 begin
400 begin
408 // build control and group lists
410 begin
414 try
423 except
430 // this also sets `tempFlex`
432 var
438 begin
451 begin
452 // horizontal boxes
458 begin
459 // new line?
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;
464 begin
476 end
477 else
478 begin
479 // vertical boxes
483 begin
484 if (lc.startsize.w < c.startsize.w+lc.margins.horiz) then lc.startsize.w := c.startsize.w+lc.margins.horiz;
498 var
507 begin
508 // reset all 'laywrap' flags for controls, set initial 'startsize'
510 begin
518 // setup sizes
520 // find max size for group, adjust 'startsize' controls to group max size
523 begin
525 begin
529 begin
535 begin
539 begin
546 // recalc maxsize if necessary
548 // set "desired size" to "start size"
550 // set flags
552 //writeln('=== calculated max size ===');
553 //dump();
557 procedure TFlexLayouterBase.fixLine (me: PLayControl; i0, i1: LayControlIdx; cury: Integer; var spaceLeft: Single; var flexTotal: Integer; var flexBoxCount: Integer);
558 var
565 begin
568 // calc minimal line height
571 begin
576 // distribute space, expand/align
579 begin
586 // fix flexbox size
588 begin
591 begin
592 // size changed
595 // compensate (crudely) rounding errors
597 // relayout children
601 // expand or align
604 else if (lc.aligndir = 0) then lc.desiredpos.y := cury+(lineh-lc.desiredsize.h) div 2; // center
606 begin
608 // relayout children
619 // do box layouting; call `layBox()` recursively if necessary
621 var
633 begin
637 // if we have no children, there's nothing to do
640 // first, layout all children
643 // second, layout lines, distribute flex data
645 begin
646 // horizontal boxes
655 begin
656 // new line?
658 // need to wrap?
659 if (not doWrap) and (lc.canWrap) and (lc.canWrap) and (lc.desiredsize.w > 0) and (spaceLeft < lc.desiredsize.w) then doWrap := true;
661 begin
662 // new line, fix this one
664 begin
671 end
672 else
673 begin
675 begin
683 begin
688 // fix last line
690 end
691 else
692 begin
693 // vertical boxes
699 // calc flex
701 begin
705 begin
711 // distribute space
713 //writeln('me: ', boxidx, '; margins: ', me.margins.toString);
715 begin
721 // fix flexbox size
723 begin
726 begin
727 // size changed
730 // compensate (crudely) rounding errors
734 // expand or align
735 if (lc.expand) then lc.desiredsize.w := nmin(lc.maxsize.w, me.desiredsize.w-me.margins.vert) // expand
736 else if (lc.aligndir > 0) then lc.desiredpos.x := me.desiredsize.w-me.margins.right-lc.desiredsize.w // right align
737 else if (lc.aligndir = 0) then lc.desiredpos.x := (me.desiredsize.w-lc.desiredsize.w) div 2; // center
739 begin
741 // relayout children
749 (*
750 second pass:
751 calcluate desired sizes (process flexes) using 'startsize', set 'desiredsize' and 'desiredpos'
752 if control has children, call 'second pass' recursively with this control as parent
753 flags set:
754 'group-element-changed', if any group element size was changed
755 'wrapping-changed', if not 'firsttime', and wrapping was changed (i.e. first pass will not set the flag)
756 *)
758 begin
759 // reset flags
764 begin
769 // fix 'wrapping-changed' flag
774 (*
775 third pass:
776 if 'group-element-changed':
777 for each group: adjust controls to max desired size (startsize), set 'temp-flex' flags to 0 for 'em, set 'second-again' flag
778 for other controls: if 'desiredsize' > 'maxsize', set 'startsize' to 'maxsize', set 'temp-flex' flag to 0, set 'second-again' flag
779 if 'second-again' or 'wrapping-changed':
780 reset 'second-again'
781 reset 'wrapping-changed'
782 reset 'firsttime'
783 goto second pass
784 *)
786 var
794 begin
796 begin
800 begin
802 // find max size for group, adjust 'startsize' controls to group max size
804 begin
806 begin
810 begin
817 begin
826 end
827 else
828 begin
830 begin
833 begin
838 begin
841 begin
842 //writeln('ctl #', f, '; dimension #', c, ': desired=', ctlist[f].desiredsize[c], '; max=', ctlist[f].maxsize[c]);
857 (*
858 fourth pass:
859 set 'actualsize' and 'actualpos' to 'desiredsize' and 'desiredpos'
860 return
861 *)
863 var
865 begin
867 begin
874 begin
883 var
887 begin
889 begin
893 writeln(lc.myidx, ': startsize:', lc.startsize.toString(), '; desiredsize=', lc.desiredsize.toString(), '; maxsize=', lc.maxsize.toString(), '; tempFlex=', lc.tempFlex, '; flags=', lc.flags,
894 '; parent=', lc.parent, '; next=', lc.nextSibling, '; child=', lc.firstChild, '; ctl.size=', ds.toString(), '; ctl.maxsize=', ms.toString());
900 var
903 begin
905 begin
908 writeln(lc.myidx, ': startsize:', lc.startsize.toString, '; desiredsize=', lc.desiredsize.toString, '; maxsize=', lc.maxsize.toString, '; tempFlex=', lc.tempFlex, '; despos=', lc.desiredpos.toString);
916 begin
921 // ////////////////////////////////////////////////////////////////////////// //
922 (*
923 void main () begin
924 auto win := new GuiControl();
925 (win ~= new GuiControl()).mSize := TLaySize(10, 5);
926 (win ~= new GuiControl()).mSize := TLaySize(16, 8);
928 //win.mSize := TLaySize(40, 20);
930 auto lay := TFlexLayouterBase!GuiControl();
931 lay.setup(win);
933 writeln('============================');
934 lay.dumpFlat();
936 writeln('=== initial ===');
937 lay.dump();
939 //lay.calcMaxSizeInternal(0);
940 /*
941 lay.firstPass();
942 writeln('=== after first pass ===');
943 lay.dump();
945 lay.secondPass();
946 writeln('=== after second pass ===');
947 lay.dump();
948 */
949 lay.layout();
950 writeln('=== final ===');
951 lay.dump();
952 *)