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
145 public
158 private
166 private
172 // this also sets `tempFlex`
175 procedure fixLine (me: PLayControl; i0, i1: LayControlIdx; cury: Integer; var spaceLeft: Single; var flexTotal: Integer; var flexBoxCount: Integer);
176 // do box layouting; call `layBox()` recursively if necessary
186 public
187 type
189 private
193 public
201 public
205 // clear build lists
208 // build control and group lists
220 implementation
222 uses
223 utils;
226 // ////////////////////////////////////////////////////////////////////////// //
228 begin
235 function TFlexLayouterBase.TLayControl.horizBox (): Boolean; inline; begin result := ((flags and FlagHorizBox) <> 0); end;
236 function TFlexLayouterBase.TLayControl.lineStart (): Boolean; inline; begin result := ((flags and FlagLineStart) <> 0); end;
237 function TFlexLayouterBase.TLayControl.canWrap (): Boolean; inline; begin result := ((flags and FlagLineCanWrap) <> 0); end;
238 function TFlexLayouterBase.TLayControl.inGroup (): Boolean; inline; begin result := ((flags and FlagInGroup) <> 0); end;
239 function TFlexLayouterBase.TLayControl.expand (): Boolean; inline; begin result := ((flags and FlagExpand) <> 0); end;
240 function TFlexLayouterBase.TLayControl.firstInLine (): Boolean; inline; begin result := ((flags and FlagLineFirst) <> 0); end;
242 function TFlexLayouterBase.TLayControl.getDidWrap (): Boolean; inline; begin result := ((flags and FlagLineDidWrap) <> 0); end;
243 procedure TFlexLayouterBase.TLayControl.setDidWrap (v: Boolean); inline; begin if (v) then flags := flags or FlagLineDidWrap else flags := flags and (not FlagLineDidWrap); end;
246 // ////////////////////////////////////////////////////////////////////////// //
247 constructor TFlexLayouterBase.TChildrenEnumerator.Create (constref actls: TLayCtlArray; acur: Integer);
248 begin
255 begin
257 begin
260 end
261 else
262 begin
269 begin
274 begin
279 // ////////////////////////////////////////////////////////////////////////// //
281 begin
293 begin
300 begin
306 begin
314 var
316 begin
319 //lc.flags := 0;
329 var
332 begin
336 begin
342 begin
345 // first child is always linestart
347 end
348 else
349 begin
363 procedure TFlexLayouterBase.appendToGroup (const gname: AnsiString; cidx: LayControlIdx; gidx: Integer);
364 var
366 begin
372 begin
374 begin
377 exit;
380 // new group
390 var
393 begin
395 begin
403 // build control and group lists
405 begin
409 try
418 except
425 // this also sets `tempFlex`
427 var
433 begin
446 begin
447 // horizontal boxes
453 begin
454 // new line?
456 // need to wrap?
457 if (not doWrap) and (lc.canWrap) and (c.canWrap) and (msz.w > 0) and (curwdt+c.startsize.w > lc.startsize.w) then doWrap := true;
459 begin
471 end
472 else
473 begin
474 // vertical boxes
478 begin
479 if (lc.startsize.w < c.startsize.w+lc.margins.horiz) then lc.startsize.w := c.startsize.w+lc.margins.horiz;
493 var
501 begin
502 // reset all 'laywrap' flags for controls, set initial 'startsize'
504 begin
512 // setup sizes
514 // find max size for group, adjust 'startsize' controls to group max size
517 begin
519 begin
523 begin
528 begin
531 begin
538 // recalc maxsize if necessary
540 // set "desired size" to "start size"
542 // set flags
544 //writeln('=== calculated max size ===');
545 //dump();
549 procedure TFlexLayouterBase.fixLine (me: PLayControl; i0, i1: LayControlIdx; cury: Integer; var spaceLeft: Single; var flexTotal: Integer; var flexBoxCount: Integer);
550 var
557 begin
560 // calc minimal line height
563 begin
568 // distribute space, expand/align
571 begin
578 // fix flexbox size
580 begin
583 begin
584 // size changed
587 // compensate (crudely) rounding errors
589 // relayout children
593 // expand or align
596 else if (lc.aligndir = 0) then lc.desiredpos.y := cury+(lineh-lc.desiredsize.h) div 2; // center
598 begin
600 // relayout children
611 // do box layouting; call `layBox()` recursively if necessary
613 var
625 begin
629 // if we have no children, there's nothing to do
632 // first, layout all children
635 // second, layout lines, distribute flex data
637 begin
638 // horizontal boxes
647 begin
648 // new line?
650 // need to wrap?
651 if (not doWrap) and (lc.canWrap) and (lc.canWrap) and (lc.desiredsize.w > 0) and (spaceLeft < lc.desiredsize.w) then doWrap := true;
653 begin
654 // new line, fix this one
656 begin
663 end
664 else
665 begin
667 begin
675 begin
680 // fix last line
682 end
683 else
684 begin
685 // vertical boxes
691 // calc flex
693 begin
697 begin
703 // distribute space
705 //writeln('me: ', boxidx, '; margins: ', me.margins.toString);
707 begin
713 // fix flexbox size
715 begin
718 begin
719 // size changed
722 // compensate (crudely) rounding errors
726 // expand or align
727 if (lc.expand) then lc.desiredsize.w := nmin(lc.maxsize.w, me.desiredsize.w-me.margins.vert) // expand
728 else if (lc.aligndir > 0) then lc.desiredpos.x := me.desiredsize.w-me.margins.right-lc.desiredsize.w // right align
729 else if (lc.aligndir = 0) then lc.desiredpos.x := (me.desiredsize.w-me.margins.horiz-lc.desiredsize.w) div 2; // center
731 begin
733 // relayout children
741 (*
742 second pass:
743 calcluate desired sizes (process flexes) using 'startsize', set 'desiredsize' and 'desiredpos'
744 if control has children, call 'second pass' recursively with this control as parent
745 flags set:
746 'group-element-changed', if any group element size was changed
747 'wrapping-changed', if not 'firsttime', and wrapping was changed (i.e. first pass will not set the flag)
748 *)
750 begin
751 // reset flags
756 begin
761 // fix 'wrapping-changed' flag
766 (*
767 third pass:
768 if 'group-element-changed':
769 for each group: adjust controls to max desired size (startsize), set 'temp-flex' flags to 0 for 'em, set 'second-again' flag
770 for other controls: if 'desiredsize' > 'maxsize', set 'startsize' to 'maxsize', set 'temp-flex' flag to 0, set 'second-again' flag
771 if 'second-again' or 'wrapping-changed':
772 reset 'second-again'
773 reset 'wrapping-changed'
774 reset 'firsttime'
775 goto second pass
776 *)
778 var
785 begin
787 begin
791 begin
793 // find max size for group, adjust 'startsize' controls to group max size
795 begin
797 begin
801 begin
806 begin
814 end
815 else
816 begin
818 begin
820 begin
823 begin
824 //writeln('ctl #', f, '; dimension #', c, ': desired=', ctlist[f].desiredsize[c], '; max=', ctlist[f].maxsize[c]);
839 (*
840 fourth pass:
841 set 'actualsize' and 'actualpos' to 'desiredsize' and 'desiredpos'
842 return
843 *)
845 var
847 begin
849 begin
856 begin
865 var
869 begin
871 begin
875 writeln(lc.myidx, ': startsize:', lc.startsize.toString(), '; desiredsize=', lc.desiredsize.toString(), '; maxsize=', lc.maxsize.toString(), '; tempFlex=', lc.tempFlex, '; flags=', lc.flags,
876 '; parent=', lc.parent, '; next=', lc.nextSibling, '; child=', lc.firstChild, '; ctl.size=', ds.toString(), '; ctl.maxsize=', ms.toString());
882 var
885 begin
887 begin
890 writeln(lc.myidx, ': startsize:', lc.startsize.toString, '; desiredsize=', lc.desiredsize.toString, '; maxsize=', lc.maxsize.toString, '; tempFlex=', lc.tempFlex, '; despos=', lc.desiredpos.toString);
898 begin
903 // ////////////////////////////////////////////////////////////////////////// //
904 (*
905 void main () begin
906 auto win := new GuiControl();
907 (win ~= new GuiControl()).mSize := TLaySize(10, 5);
908 (win ~= new GuiControl()).mSize := TLaySize(16, 8);
910 //win.mSize := TLaySize(40, 20);
912 auto lay := TFlexLayouterBase!GuiControl();
913 lay.setup(win);
915 writeln('============================');
916 lay.dumpFlat();
918 writeln('=== initial ===');
919 lay.dump();
921 //lay.calcMaxSizeInternal(0);
922 /*
923 lay.firstPass();
924 writeln('=== after first pass ===');
925 lay.dump();
927 lay.secondPass();
928 writeln('=== after second pass ===');
929 lay.dump();
930 */
931 lay.layout();
932 writeln('=== final ===');
933 lay.dump();
934 *)