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
506 begin
507 // reset all 'laywrap' flags for controls, set initial 'startsize'
509 begin
517 // setup sizes
519 // find max size for group, adjust 'startsize' controls to group max size
522 begin
524 begin
528 begin
533 begin
536 begin
543 // recalc maxsize if necessary
545 // set "desired size" to "start size"
547 // set flags
549 //writeln('=== calculated max size ===');
550 //dump();
554 procedure TFlexLayouterBase.fixLine (me: PLayControl; i0, i1: LayControlIdx; cury: Integer; var spaceLeft: Single; var flexTotal: Integer; var flexBoxCount: Integer);
555 var
562 begin
565 // calc minimal line height
568 begin
573 // distribute space, expand/align
576 begin
583 // fix flexbox size
585 begin
588 begin
589 // size changed
592 // compensate (crudely) rounding errors
594 // relayout children
598 // expand or align
601 else if (lc.aligndir = 0) then lc.desiredpos.y := cury+(lineh-lc.desiredsize.h) div 2; // center
603 begin
605 // relayout children
616 // do box layouting; call `layBox()` recursively if necessary
618 var
630 begin
634 // if we have no children, there's nothing to do
637 // first, layout all children
640 // second, layout lines, distribute flex data
642 begin
643 // horizontal boxes
652 begin
653 // new line?
655 // need to wrap?
656 if (not doWrap) and (lc.canWrap) and (lc.canWrap) and (lc.desiredsize.w > 0) and (spaceLeft < lc.desiredsize.w) then doWrap := true;
658 begin
659 // new line, fix this one
661 begin
668 end
669 else
670 begin
672 begin
680 begin
685 // fix last line
687 end
688 else
689 begin
690 // vertical boxes
696 // calc flex
698 begin
702 begin
708 // distribute space
710 //writeln('me: ', boxidx, '; margins: ', me.margins.toString);
712 begin
718 // fix flexbox size
720 begin
723 begin
724 // size changed
727 // compensate (crudely) rounding errors
731 // expand or align
732 if (lc.expand) then lc.desiredsize.w := nmin(lc.maxsize.w, me.desiredsize.w-me.margins.vert) // expand
733 else if (lc.aligndir > 0) then lc.desiredpos.x := me.desiredsize.w-me.margins.right-lc.desiredsize.w // right align
734 else if (lc.aligndir = 0) then lc.desiredpos.x := (me.desiredsize.w-me.margins.horiz-lc.desiredsize.w) div 2; // center
736 begin
738 // relayout children
746 (*
747 second pass:
748 calcluate desired sizes (process flexes) using 'startsize', set 'desiredsize' and 'desiredpos'
749 if control has children, call 'second pass' recursively with this control as parent
750 flags set:
751 'group-element-changed', if any group element size was changed
752 'wrapping-changed', if not 'firsttime', and wrapping was changed (i.e. first pass will not set the flag)
753 *)
755 begin
756 // reset flags
761 begin
766 // fix 'wrapping-changed' flag
771 (*
772 third pass:
773 if 'group-element-changed':
774 for each group: adjust controls to max desired size (startsize), set 'temp-flex' flags to 0 for 'em, set 'second-again' flag
775 for other controls: if 'desiredsize' > 'maxsize', set 'startsize' to 'maxsize', set 'temp-flex' flag to 0, set 'second-again' flag
776 if 'second-again' or 'wrapping-changed':
777 reset 'second-again'
778 reset 'wrapping-changed'
779 reset 'firsttime'
780 goto second pass
781 *)
783 var
791 begin
793 begin
797 begin
799 // find max size for group, adjust 'startsize' controls to group max size
801 begin
803 begin
807 begin
814 begin
823 end
824 else
825 begin
827 begin
830 begin
835 begin
838 begin
839 //writeln('ctl #', f, '; dimension #', c, ': desired=', ctlist[f].desiredsize[c], '; max=', ctlist[f].maxsize[c]);
854 (*
855 fourth pass:
856 set 'actualsize' and 'actualpos' to 'desiredsize' and 'desiredpos'
857 return
858 *)
860 var
862 begin
864 begin
871 begin
880 var
884 begin
886 begin
890 writeln(lc.myidx, ': startsize:', lc.startsize.toString(), '; desiredsize=', lc.desiredsize.toString(), '; maxsize=', lc.maxsize.toString(), '; tempFlex=', lc.tempFlex, '; flags=', lc.flags,
891 '; parent=', lc.parent, '; next=', lc.nextSibling, '; child=', lc.firstChild, '; ctl.size=', ds.toString(), '; ctl.maxsize=', ms.toString());
897 var
900 begin
902 begin
905 writeln(lc.myidx, ': startsize:', lc.startsize.toString, '; desiredsize=', lc.desiredsize.toString, '; maxsize=', lc.maxsize.toString, '; tempFlex=', lc.tempFlex, '; despos=', lc.desiredpos.toString);
913 begin
918 // ////////////////////////////////////////////////////////////////////////// //
919 (*
920 void main () begin
921 auto win := new GuiControl();
922 (win ~= new GuiControl()).mSize := TLaySize(10, 5);
923 (win ~= new GuiControl()).mSize := TLaySize(16, 8);
925 //win.mSize := TLaySize(40, 20);
927 auto lay := TFlexLayouterBase!GuiControl();
928 lay.setup(win);
930 writeln('============================');
931 lay.dumpFlat();
933 writeln('=== initial ===');
934 lay.dump();
936 //lay.calcMaxSizeInternal(0);
937 /*
938 lay.firstPass();
939 writeln('=== after first pass ===');
940 lay.dump();
942 lay.secondPass();
943 writeln('=== after second pass ===');
944 lay.dump();
945 */
946 lay.layout();
947 writeln('=== final ===');
948 lay.dump();
949 *)