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 'wantsize' 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 'wantsize', 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 (wantsize), set 'temp-flex' flags to 0 for 'em, set 'second-again' flag
23 for other controls: if 'desiredsize' > 'maxsize', set 'wantsize' 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 'wantsize' 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 'wantsize'
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 'wantsize' 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 ControlT:
66 function getDefSize (): TLaySize; // default size; <0: use max size
67 function getMaxSize (): TLaySize; // max size; <0: set to some huge value
68 function getFlex (): Integer; // <=0: not flexible
69 function isHorizBox (): Boolean; // horizontal layout for children?
70 function canWrap (): Boolean; // for horizontal boxes: can wrap children? for child: `false` means 'nonbreakable at *next* ctl'
71 function isLineStart (): Boolean; // `true` if this ctl should start a new line; ignored for vertical boxes
72 function getAlign (): Integer; // aligning in non-main direction: <0: left/up; 0: center; >0: right/down
73 function getExpand (): Boolean; // expanding in non-main direction: `true` will ignore align and eat all available space
74 procedure setActualSizePos (constref apos: TLayPos; constref asize: TLaySize);
75 function getHGroup (): AnsiString; // empty: not grouped
76 function getVGroup (): AnsiString; // empty: not grouped
77 function nextSibling (): ControlT;
78 function firstChild (): ControlT;
79 *)
81 interface
83 uses
84 gh_ui_common;
87 // ////////////////////////////////////////////////////////////////////////// //
88 type
90 public
93 private
96 private
97 // flags
98 const
102 // internal
108 private
109 type
112 public
124 private
128 public
138 public
151 private
159 private
165 // this also sets `tempFlex`
168 procedure fixLine (me: PLayControl; i0, i1: LayControlIdx; cury: Integer; var spaceLeft: Single; var flexTotal: Integer; var flexBoxCount: Integer);
169 // do box layouting; call `layBox()` recursively if necessary
179 public
180 type
182 private
186 public
194 public
198 // clear build lists
201 // build control and group lists
213 implementation
215 uses
216 utils;
219 // ////////////////////////////////////////////////////////////////////////// //
221 begin
228 function TFlexLayouterBase.TLayControl.horizBox (): Boolean; inline; begin result := ((flags and FlagHorizBox) <> 0); end;
229 function TFlexLayouterBase.TLayControl.lineStart (): Boolean; inline; begin result := ((flags and FlagLineStart) <> 0); end;
230 function TFlexLayouterBase.TLayControl.canWrap (): Boolean; inline; begin result := ((flags and FlagLineCanWrap) <> 0); end;
231 function TFlexLayouterBase.TLayControl.inGroup (): Boolean; inline; begin result := ((flags and FlagInGroup) <> 0); end;
232 function TFlexLayouterBase.TLayControl.expand (): Boolean; inline; begin result := ((flags and FlagExpand) <> 0); end;
233 function TFlexLayouterBase.TLayControl.firstInLine (): Boolean; inline; begin result := ((flags and FlagLineFirst) <> 0); end;
235 function TFlexLayouterBase.TLayControl.getDidWrap (): Boolean; inline; begin result := ((flags and FlagLineDidWrap) <> 0); end;
236 procedure TFlexLayouterBase.TLayControl.setDidWrap (v: Boolean); inline; begin if (v) then flags := flags or FlagLineDidWrap else flags := flags and (not FlagLineDidWrap); end;
239 // ////////////////////////////////////////////////////////////////////////// //
240 constructor TFlexLayouterBase.TChildrenEnumerator.Create (constref actls: TLayCtlArray; acur: Integer);
241 begin
248 begin
250 begin
253 end
254 else
255 begin
262 begin
267 begin
272 // ////////////////////////////////////////////////////////////////////////// //
274 begin
286 begin
293 begin
299 begin
307 var
309 begin
312 //lc.flags := 0;
322 var
325 begin
329 begin
333 begin
336 // first child is always linestart
338 end
339 else
340 begin
354 procedure TFlexLayouterBase.appendToGroup (const gname: AnsiString; cidx: LayControlIdx; gidx: Integer);
355 var
357 begin
363 begin
365 begin
368 exit;
371 // new group
381 var
384 begin
386 begin
394 // build control and group lists
396 begin
399 try
407 except
414 // this also sets `tempFlex`
416 var
422 begin
427 //lc.wantsize := lc.ctl.getDefSize;
429 //negh := (lc.wantsize.h <= 0);
431 //if (lc.wantsize.w < msz.w) lc.wantsize.w := msz.w;
432 //if (lc.wantsize.h < msz.h) lc.wantsize.h := msz.h;
434 //writeln('calcsize #', cidx, '; wantsize=', lc.wantsize, '; ctl.maxsize=', msz);
441 begin
442 // horizontal boxes
448 begin
449 // new line?
451 // need to wrap?
452 if (not doWrap) and (lc.canWrap) and (c.canWrap) and (msz.w > 0) and (curwdt+c.wantsize.w > lc.wantsize.w) then doWrap := true;
454 begin
466 end
467 else
468 begin
469 // vertical boxes
472 begin
487 var
494 begin
495 // reset all 'laywrap' flags for controls, set initial 'wantsize'
497 begin
501 // setup sizes
503 // find max size for group, adjust 'wantsize' controls to group max size
506 begin
508 begin
512 begin
517 begin
520 begin
527 // recalc maxsize if necessary
529 // set flags
534 procedure TFlexLayouterBase.fixLine (me: PLayControl; i0, i1: LayControlIdx; cury: Integer; var spaceLeft: Single; var flexTotal: Integer; var flexBoxCount: Integer);
535 var
540 begin
543 begin
550 // fix flexbox size
552 begin
555 begin
556 // size changed
559 // compensate (crudely) rounding errors
561 // relayout children
574 // do box layouting; call `layBox()` recursively if necessary
576 var
587 begin
591 // if we have no children, just set desired size and exit
595 // first, layout all children; also, gather some flex data
598 // second, layout lines, distribute flex data
600 begin
601 // horizontal boxes
610 begin
611 // new line?
613 // need to wrap?
614 if (not doWrap) and (lc.canWrap) and (lc.canWrap) and (lc.desiredsize.w > 0) and (spaceLeft < lc.desiredsize.w) then doWrap := true;
616 begin
617 // new line, fix this one
619 begin
626 end
627 else
628 begin
630 begin
638 begin
643 // fix last line
645 end
646 else
647 begin
648 // vertical boxes
654 // calc flex
656 begin
660 begin
666 // distribute space
669 begin
674 // fix flexbox size
676 begin
679 begin
680 // size changed
683 // compensate (crudely) rounding errors
685 // relayout children
694 (*
695 second pass:
696 calcluate desired sizes (process flexes) using 'wantsize', set 'desiredsize' and 'desiredpos'
697 if control has children, call 'second pass' recursively with this control as parent
698 flags set:
699 'group-element-changed', if any group element size was changed
700 'wrapping-changed', if not 'firsttime', and wrapping was changed (i.e. first pass will not set the flag)
701 *)
703 begin
704 // reset flags
709 begin
714 // fix 'wrapping-changed' flag
719 (*
720 third pass:
721 if 'group-element-changed':
722 for each group: adjust controls to max desired size (wantsize), set 'temp-flex' flags to 0 for 'em, set 'second-again' flag
723 for other controls: if 'desiredsize' > 'maxsize', set 'wantsize' to 'maxsize', set 'temp-flex' flag to 0, set 'second-again' flag
724 if 'second-again' or 'wrapping-changed':
725 reset 'second-again'
726 reset 'wrapping-changed'
727 reset 'firsttime'
728 goto second pass
729 *)
731 var
733 begin
735 begin
738 begin
739 // do it
748 (*
749 fourth pass:
750 set 'actualsize' and 'actualpos' to 'desiredsize' and 'desiredpos'
751 return
752 *)
754 var
756 begin
758 begin
765 begin
774 var
778 begin
780 begin
784 writeln(lc.myidx, ': wantsize:', lc.wantsize.toString(), '; desiredsize=', lc.desiredsize.toString(), '; maxsize=', lc.maxsize.toString(), '; tempFlex=', lc.tempFlex, '; flags=', lc.flags,
785 '; parent=', lc.parent, '; next=', lc.nextSibling, '; child=', lc.firstChild, '; ctl.size=', ds.toString(), '; ctl.maxsize=', ms.toString());
791 var
794 begin
796 begin
799 writeln(lc.myidx, ': wantsize:', lc.wantsize.toString, '; desiredsize=', lc.desiredsize.toString, '; maxsize=', lc.maxsize.toString, '; tempFlex=', lc.tempFlex, '; despos=', lc.desiredpos.toString);
807 begin
812 // ////////////////////////////////////////////////////////////////////////// //
813 (*
814 void main () begin
815 auto win := new GuiControl();
816 (win ~= new GuiControl()).mSize := TLaySize(10, 5);
817 (win ~= new GuiControl()).mSize := TLaySize(16, 8);
819 //win.mSize := TLaySize(40, 20);
821 auto lay := TFlexLayouterBase!GuiControl();
822 lay.setup(win);
824 writeln('============================');
825 lay.dumpFlat();
827 writeln('=== initial ===');
828 lay.dump();
830 //lay.calcMaxSizeInternal(0);
831 /*
832 lay.firstPass();
833 writeln('=== after first pass ===');
834 lay.dump();
836 lay.secondPass();
837 writeln('=== after second pass ===');
838 lay.dump();
839 */
840 lay.layout();
841 writeln('=== final ===');
842 lay.dump();
843 *)