1 (* coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *)
17 {$INCLUDE ../shared/a_modes.inc}
20 (* WARNING! OUT OF DATE! will be fixed later.
22 first pass:
23 set all 'temp-flex' flags for controls to 'flex'
24 reset all 'laywrap' flags for controls
25 build group arrays; for each group: find max size for group, adjust 'startsize' controls to group max size
26 call 'calc max size' for top-level control
27 flags set:
28 'firsttime'
30 second pass:
31 calcluate desired sizes (process flexes) using 'startsize', set 'desiredsize' and 'desiredpos'
32 if control has children, call 'second pass' recursively with this control as parent
33 flags set:
34 'group-element-changed', if any group element size was changed
35 'wrapping-changed', if not 'firsttime', and wrapping was changed (i.e. first pass will not set the flag)
37 third pass:
38 if 'group-element-changed':
39 for each group: adjust controls to max desired size (startsize), set 'temp-flex' flags to 0 for 'em, set 'second-again' flag
40 for other controls: if 'desiredsize' > 'maxsize', set 'startsize' to 'maxsize', set 'temp-flex' flag to 0, set 'second-again' flag
41 if 'second-again' or 'wrapping-changed':
42 reset 'second-again'
43 reset 'wrapping-changed'
44 reset 'firsttime'
45 goto second pass
47 fourth pass:
48 set 'actualsize' and 'actualpos' to 'desiredsize' and 'desiredpos'
49 return
51 calc max size:
52 set 'startsize' to max(size, maxsize, 0)
53 if 'size' is negative:
54 set 'temp-flex' flag to 0
55 if has children:
56 call 'calc max size' for each child
57 set 'desiredmax' to 'startsize'
58 do lines, don't distribute space (i.e. calc only wrapping),
59 for each complete line, set 'desiredmax' to max(desiredmax, linesize)
60 if 'maxsize' >= 0:
61 set 'desiredmax' to min(desiredmax, maxsize)
62 set 'startsize' to 'desiredmax'
63 return
66 wrapping lines:
67 try to stuff controls in line until line width is less or equal to maxsize
68 distribute flex for filled line
69 continue until we still has something to stuff
72 for wrapping:
73 we'll hold 'laywrap' flag for each control; it will be set if this control
74 starts a new line (even if this is the first control in line, as it is obviously
75 starts a new line)
77 on redoing second pass, if 'laywrap' flag changed, set 'wrapping-changed' flag
78 *)
81 (*
82 control default size will be increased by margins
83 negative margins are ignored
84 ControlT:
85 procedure layPrepare (); // called before registering control in layouter
86 function getDefSize (): TLaySize; // default size; <0: use max size
87 function getMargins (): TLayMargins;
88 function getMaxSize (): TLaySize; // max size; <0: set to some huge value
89 function getFlex (): Integer; // <=0: not flexible
90 function isHorizBox (): Boolean; // horizontal layout for children?
91 function canWrap (): Boolean; // for horizontal boxes: can wrap children? for child: `false` means 'nonbreakable at *next* ctl'
92 function isLineStart (): Boolean; // `true` if this ctl should start a new line; ignored for vertical boxes
93 function getAlign (): Integer; // aligning in non-main direction: <0: left/up; 0: center; >0: right/down
94 function getExpand (): Boolean; // expanding in non-main direction: `true` will ignore align and eat all available space
95 procedure setActualSizePos (constref apos: TLayPos; constref asize: TLaySize);
96 function getHGroup (): AnsiString; // empty: not grouped
97 function getVGroup (): AnsiString; // empty: not grouped
98 function nextSibling (): ControlT;
99 function firstChild (): ControlT;
100 *)
102 interface
104 uses
105 gh_ui_common;
108 // ////////////////////////////////////////////////////////////////////////// //
109 type
111 public
114 private
117 private
120 private
121 // flags
122 const
126 // internal
132 private
133 type
136 public
151 private
155 public
173 public
187 private
195 private
201 // this also sets `tempFlex`
204 procedure fixLine (me: PLayControl; i0, i1: LayControlIdx; var cury: Integer; var spaceLeft: Single);
205 // do box layouting; call `layBox()` recursively if necessary
215 public
216 type
218 private
222 public
230 public
234 // clear build lists
237 // build control and group lists
249 implementation
251 uses
252 utils;
255 // ////////////////////////////////////////////////////////////////////////// //
257 begin
264 // ////////////////////////////////////////////////////////////////////////// //
266 begin
273 function TFlexLayouterBase.TLayControl.horizBox (): Boolean; inline; begin result := ((flags and FlagHorizBox) <> 0); end;
274 function TFlexLayouterBase.TLayControl.lineStart (): Boolean; inline; begin result := ((flags and FlagLineStart) <> 0); end;
275 function TFlexLayouterBase.TLayControl.canWrap (): Boolean; inline; begin result := ((flags and FlagLineCanWrap) <> 0); end;
276 function TFlexLayouterBase.TLayControl.inGroup (): Boolean; inline; begin result := ((flags and FlagInGroup) <> 0); end;
277 function TFlexLayouterBase.TLayControl.firstInLine (): Boolean; inline; begin result := ((flags and FlagLineFirst) <> 0); end;
279 function TFlexLayouterBase.TLayControl.getDidWrap (): Boolean; inline; begin result := ((flags and FlagLineDidWrap) <> 0); end;
280 procedure TFlexLayouterBase.TLayControl.setDidWrap (v: Boolean); inline; begin if (v) then flags := flags or FlagLineDidWrap else flags := flags and (not FlagLineDidWrap); end;
282 function TFlexLayouterBase.TLayControl.getExpand (): Boolean; inline; begin result := ((flags and FlagExpand) <> 0); end;
283 procedure TFlexLayouterBase.TLayControl.setExpand (v: Boolean); inline; begin if (v) then flags := flags or FlagExpand else flags := flags and (not FlagExpand); end;
285 function TFlexLayouterBase.TLayControl.alignLeft (): Boolean; inline; begin result := (aligndir < 0); end;
286 function TFlexLayouterBase.TLayControl.alignTop (): Boolean; inline; begin result := (aligndir < 0); end;
287 function TFlexLayouterBase.TLayControl.alignRight (): Boolean; inline; begin result := (aligndir > 0); end;
288 function TFlexLayouterBase.TLayControl.alignBottom (): Boolean; inline; begin result := (aligndir > 0); end;
289 function TFlexLayouterBase.TLayControl.alignCenter (): Boolean; inline; begin result := (aligndir = 0); end;
292 // ////////////////////////////////////////////////////////////////////////// //
293 constructor TFlexLayouterBase.TChildrenEnumerator.Create (constref actls: TLayCtlArray; acur: Integer);
294 begin
301 begin
303 begin
306 end
307 else
308 begin
315 begin
320 begin
325 // ////////////////////////////////////////////////////////////////////////// //
327 begin
339 begin
346 begin
352 begin
360 var
362 begin
365 //lc.flags := 0;
375 var
378 begin
382 begin
388 begin
391 // first child is always linestart
393 end
394 else
395 begin
409 procedure TFlexLayouterBase.appendToGroup (const gname: AnsiString; cidx: LayControlIdx; gidx: Integer);
410 var
412 begin
418 begin
420 begin
423 exit;
426 // new group
436 var
439 begin
441 begin
449 // build control and group lists
451 begin
455 try
464 except
471 // this also sets `tempFlex`
473 var
480 begin
494 begin
495 // horizontal boxes
501 begin
502 // new line?
504 // need to wrap?
505 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;
507 begin
516 //writeln('00: ', cidx, ': totalhgt=', totalhgt);
518 //writeln('01: ', cidx, ': totalhgt=', totalhgt);
521 end
522 else
523 begin
524 // vertical boxes
528 begin
529 if (lc.startsize.w < c.startsize.w+lc.margins.horiz) then lc.startsize.w := c.startsize.w+lc.margins.horiz;
537 if (lc.maxsize.w < lc.startsize.w) then begin if (lc.maxsize.w >= 0) then lc.maxsize.w := lc.startsize.w; end;
538 if (lc.maxsize.h < lc.startsize.h) then begin if (lc.maxsize.h >= 0) then lc.maxsize.h := lc.startsize.h; end;
543 var
552 begin
553 // reset all 'laywrap' flags for controls, set initial 'startsize'
555 begin
560 //ctlist[f].startsize.w += mr.horiz;
561 //ctlist[f].startsize.h += mr.vert;
563 // setup sizes
565 //writeln('=== calculated max size (0) ==='); dump();
566 // find max size for group, adjust 'startsize' controls to group max size
569 begin
571 begin
575 begin
581 begin
585 begin
592 // recalc maxsize if necessary
594 // set "desired size" to "start size"
596 // set flags
598 //writeln('=== calculated max size (final) ==='); dump();
602 procedure TFlexLayouterBase.fixLine (me: PLayControl; i0, i1: LayControlIdx; var cury: Integer; var spaceLeft: Single);
603 var
612 begin
615 // calc minimal line height, count flexboxes
618 begin
624 // distribute space, expand/align
627 begin
634 // fix flexbox size
636 begin
639 begin
640 // size changed
643 // compensate (crudely) rounding errors
645 // relayout children
649 // expand or align
654 begin
656 // relayout children
668 // do box layouting; call `layBox()` recursively if necessary
670 var
682 begin
686 // if we have no children, there's nothing to do
689 // first, layout all children
692 // second, layout lines, distribute flex data
694 begin
695 // horizontal boxes
702 begin
703 // new line?
705 // need to wrap?
706 if (not doWrap) and (lc.canWrap) and (lc.canWrap) and (lc.desiredsize.w > 0) and (spaceLeft < lc.desiredsize.w) then doWrap := true;
708 begin
709 // new line, fix this one
713 end
714 else
715 begin
719 //if (maxhgt < lc.desiredsize.h) then maxhgt := lc.desiredsize.h;
720 //if (lc.tempFlex > 0) then begin flexTotal += lc.tempFlex; flexBoxCount += 1; end;
722 // fix last line
724 end
725 else
726 begin
727 // vertical boxes
733 // calc flex
735 begin
741 // distribute space
743 //writeln('me: ', boxidx, '; margins: ', me.margins.toString);
745 begin
751 // fix flexbox size
753 begin
756 begin
757 // size changed
760 // compensate (crudely) rounding errors
764 // expand or align
765 if (lc.expand) then lc.desiredsize.w := nminX(lc.maxsize.w, me.desiredsize.w-me.margins.vert) // expand
766 else if (lc.alignRight) then lc.desiredpos.x := me.desiredsize.w-me.margins.right-lc.desiredsize.w // right align
767 else if (lc.alignCenter) then lc.desiredpos.x := (me.desiredsize.w-lc.desiredsize.w) div 2; // center
769 begin
771 // relayout children
779 (*
780 second pass:
781 calcluate desired sizes (process flexes) using 'startsize', set 'desiredsize' and 'desiredpos'
782 if control has children, call 'second pass' recursively with this control as parent
783 flags set:
784 'group-element-changed', if any group element size was changed
785 'wrapping-changed', if not 'firsttime', and wrapping was changed (i.e. first pass will not set the flag)
786 *)
788 begin
789 // reset flags
794 begin
799 // fix 'wrapping-changed' flag
804 (*
805 third pass:
806 if 'group-element-changed':
807 for each group: adjust controls to max desired size (startsize), set 'temp-flex' flags to 0 for 'em, set 'second-again' flag
808 for other controls: if 'desiredsize' > 'maxsize', set 'startsize' to 'maxsize', set 'temp-flex' flag to 0, set 'second-again' flag
809 if 'second-again' or 'wrapping-changed':
810 reset 'second-again'
811 reset 'wrapping-changed'
812 reset 'firsttime'
813 goto second pass
814 *)
816 var
824 begin
826 begin
830 begin
832 // find max size for group, adjust 'startsize' controls to group max size
834 begin
836 begin
840 begin
847 begin
856 end
857 else
858 begin
860 begin
863 begin
868 begin
871 begin
872 //writeln('ctl #', f, '; dimension #', c, ': desired=', ctlist[f].desiredsize[c], '; max=', ctlist[f].maxsize[c]);
887 (*
888 fourth pass:
889 set 'actualsize' and 'actualpos' to 'desiredsize' and 'desiredpos'
890 return
891 *)
893 var
895 begin
897 begin
904 begin
913 var
917 begin
919 begin
923 writeln(lc.myidx, ': startsize:', lc.startsize.toString(), '; desiredsize=', lc.desiredsize.toString(), '; maxsize=', lc.maxsize.toString(), '; tempFlex=', lc.tempFlex, '; flags=', lc.flags,
924 '; parent=', lc.parent, '; next=', lc.nextSibling, '; child=', lc.firstChild, '; ctl.size=', ds.toString(), '; ctl.maxsize=', ms.toString());
930 var
933 begin
935 begin
938 writeln(lc.myidx, ': startsize:', lc.startsize.toString, '; desiredsize=', lc.desiredsize.toString, '; maxsize=', lc.maxsize.toString, '; tempFlex=', lc.tempFlex, '; despos=', lc.desiredpos.toString);
946 begin
951 // ////////////////////////////////////////////////////////////////////////// //
952 (*
953 void main () begin
954 auto win := new GuiControl();
955 (win ~= new GuiControl()).mSize := TLaySize(10, 5);
956 (win ~= new GuiControl()).mSize := TLaySize(16, 8);
958 //win.mSize := TLaySize(40, 20);
960 auto lay := TFlexLayouterBase!GuiControl();
961 lay.setup(win);
963 writeln('============================');
964 lay.dumpFlat();
966 writeln('=== initial ===');
967 lay.dump();
969 //lay.calcMaxSizeInternal(0);
970 /*
971 lay.firstPass();
972 writeln('=== after first pass ===');
973 lay.dump();
975 lay.secondPass();
976 writeln('=== after second pass ===');
977 lay.dump();
978 */
979 lay.layout();
980 writeln('=== final ===');
981 lay.dump();
982 *)