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}
19 (*
20 control default size will be increased by margins
21 negative margins are ignored
22 ControlT:
23 procedure layPrepare (); // called before registering control in layouter
24 function getDefSize (): TLaySize; // default size; <0: use max size
25 function getMargins (): TLayMargins;
26 function getMaxSize (): TLaySize; // max size; <0: set to some huge value
27 function getFlex (): Integer; // <=0: not flexible
28 function isHorizBox (): Boolean; // horizontal layout for children?
29 function canWrap (): Boolean; // for horizontal boxes: can wrap children? for child: `false` means 'nonbreakable at *next* ctl'
30 function isLineStart (): Boolean; // `true` if this ctl should start a new line; ignored for vertical boxes
31 function getAlign (): Integer; // aligning in non-main direction: <0: left/up; 0: center; >0: right/down
32 function getExpand (): Boolean; // expanding in non-main direction: `true` will ignore align and eat all available space
33 procedure setActualSizePos (constref apos: TLayPos; constref asize: TLaySize);
34 function getHGroup (): AnsiString; // empty: not grouped
35 function getVGroup (): AnsiString; // empty: not grouped
36 function nextSibling (): ControlT;
37 function firstChild (): ControlT;
38 *)
40 interface
42 uses
43 fui_common;
46 // ////////////////////////////////////////////////////////////////////////// //
47 type
49 public
52 private
55 private
56 // flags
57 const
61 // internal
67 private
68 type
71 public
86 private
90 public
108 public
122 private
130 private
136 // this also sets `tempFlex`
139 procedure fixLine (me: PLayControl; i0, i1: LayControlIdx; var cury: Integer; var spaceLeft: Single);
140 // do box layouting; call `layBox()` recursively if necessary
150 public
151 type
153 private
157 public
165 public
169 // clear build lists
172 // build control and group lists
184 implementation
186 uses
187 utils;
190 // ////////////////////////////////////////////////////////////////////////// //
192 begin
199 function TFlexLayouterBase.TLayControl.horizBox (): Boolean; inline; begin result := ((flags and FlagHorizBox) <> 0); end;
200 function TFlexLayouterBase.TLayControl.lineStart (): Boolean; inline; begin result := ((flags and FlagLineStart) <> 0); end;
201 function TFlexLayouterBase.TLayControl.canWrap (): Boolean; inline; begin result := ((flags and FlagLineCanWrap) <> 0); end;
202 function TFlexLayouterBase.TLayControl.inGroup (): Boolean; inline; begin result := ((flags and FlagInGroup) <> 0); end;
203 function TFlexLayouterBase.TLayControl.firstInLine (): Boolean; inline; begin result := ((flags and FlagLineFirst) <> 0); end;
205 function TFlexLayouterBase.TLayControl.getDidWrap (): Boolean; inline; begin result := ((flags and FlagLineDidWrap) <> 0); end;
206 procedure TFlexLayouterBase.TLayControl.setDidWrap (v: Boolean); inline; begin if (v) then flags := flags or FlagLineDidWrap else flags := flags and (not FlagLineDidWrap); end;
208 function TFlexLayouterBase.TLayControl.getExpand (): Boolean; inline; begin result := ((flags and FlagExpand) <> 0); end;
209 procedure TFlexLayouterBase.TLayControl.setExpand (v: Boolean); inline; begin if (v) then flags := flags or FlagExpand else flags := flags and (not FlagExpand); end;
211 function TFlexLayouterBase.TLayControl.alignLeft (): Boolean; inline; begin result := (aligndir < 0); end;
212 function TFlexLayouterBase.TLayControl.alignTop (): Boolean; inline; begin result := (aligndir < 0); end;
213 function TFlexLayouterBase.TLayControl.alignRight (): Boolean; inline; begin result := (aligndir > 0); end;
214 function TFlexLayouterBase.TLayControl.alignBottom (): Boolean; inline; begin result := (aligndir > 0); end;
215 function TFlexLayouterBase.TLayControl.alignCenter (): Boolean; inline; begin result := (aligndir = 0); end;
218 // ////////////////////////////////////////////////////////////////////////// //
219 constructor TFlexLayouterBase.TChildrenEnumerator.Create (constref actls: TLayCtlArray; acur: Integer);
220 begin
227 begin
229 begin
232 end
233 else
234 begin
241 begin
246 begin
251 // ////////////////////////////////////////////////////////////////////////// //
253 begin
265 begin
272 begin
278 begin
286 var
288 begin
291 //lc.flags := 0;
301 var
304 begin
308 begin
314 begin
317 // first child is always linestart
319 end
320 else
321 begin
335 procedure TFlexLayouterBase.appendToGroup (const gname: AnsiString; cidx: LayControlIdx; gidx: Integer);
336 var
338 begin
344 begin
346 begin
349 exit;
352 // new group
362 var
365 begin
367 begin
375 // build control and group lists
377 begin
381 try
390 except
397 // this also sets `tempFlex`
399 var
406 begin
420 begin
421 // horizontal boxes
427 begin
428 // new line?
430 // need to wrap?
431 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;
433 begin
442 //writeln('00: ', cidx, ': totalhgt=', totalhgt);
444 //writeln('01: ', cidx, ': totalhgt=', totalhgt);
447 end
448 else
449 begin
450 // vertical boxes
454 begin
455 if (lc.startsize.w < c.startsize.w+lc.margins.horiz) then lc.startsize.w := c.startsize.w+lc.margins.horiz;
462 {
463 lc.maxsize := msz;
464 if (lc.maxsize.w < lc.startsize.w) then begin if (lc.maxsize.w >= 0) then lc.maxsize.w := lc.startsize.w; end;
465 if (lc.maxsize.h < lc.startsize.h) then begin if (lc.maxsize.h >= 0) then lc.maxsize.h := lc.startsize.h; end;
466 }
474 var
483 begin
484 // reset all 'laywrap' flags for controls, set initial 'startsize'
486 begin
491 //ctlist[f].startsize.w += mr.horiz;
492 //ctlist[f].startsize.h += mr.vert;
494 // setup sizes
496 //writeln('=== calculated max size (0) ==='); dump();
497 // find max size for group, adjust 'startsize' controls to group max size
500 begin
502 begin
506 begin
512 begin
516 begin
523 // recalc maxsize if necessary
525 // set "desired size" to "start size"
527 // set flags
529 //writeln('=== calculated max size (final) ==='); dump();
533 procedure TFlexLayouterBase.fixLine (me: PLayControl; i0, i1: LayControlIdx; var cury: Integer; var spaceLeft: Single);
534 var
543 begin
546 // calc minimal line height, count flexboxes
549 begin
555 // distribute space, expand/align
558 begin
565 // fix flexbox size
567 begin
570 begin
571 // size changed
574 // compensate (crudely) rounding errors
576 // relayout children
580 // expand or align
585 begin
587 // relayout children
599 // do box layouting; call `layBox()` recursively if necessary
601 var
613 begin
617 // if we have no children, there's nothing to do
619 begin
620 // first, layout all children
623 // second, layout lines, distribute flex data
625 begin
626 // horizontal boxes
633 begin
634 // new line?
636 // need to wrap?
637 if (not doWrap) and (lc.canWrap) and (lc.canWrap) and (lc.desiredsize.w > 0) and (spaceLeft < lc.desiredsize.w) then doWrap := true;
639 begin
640 // new line, fix this one
644 end
645 else
646 begin
650 //if (maxhgt < lc.desiredsize.h) then maxhgt := lc.desiredsize.h;
651 //if (lc.tempFlex > 0) then begin flexTotal += lc.tempFlex; flexBoxCount += 1; end;
653 // fix last line
655 end
656 else
657 begin
658 // vertical boxes
664 // calc flex
666 begin
672 // distribute space
674 //writeln('me: ', boxidx, '; margins: ', me.margins.toString);
676 begin
682 // fix flexbox size
684 begin
687 begin
688 // size changed
691 // compensate (crudely) rounding errors
695 // expand or align
697 else if (lc.alignRight) then lc.desiredpos.x := me.desiredsize.w-me.margins.right-lc.desiredsize.w // right align
698 else if (lc.alignCenter) then lc.desiredpos.x := (me.desiredsize.w-lc.desiredsize.w) div 2; // center
700 begin
702 // relayout children
709 if (me.maxsize.w >= 0) and (me.desiredsize.w > me.maxsize.w) then me.desiredsize.w := me.maxsize.w;
710 if (me.maxsize.h >= 0) and (me.desiredsize.h > me.maxsize.h) then me.desiredsize.h := me.maxsize.h;
715 begin
716 // reset flags
721 begin
726 // fix 'wrapping-changed' flag
732 var
740 begin
742 begin
746 begin
748 // find max size for group, adjust 'startsize' controls to group max size
750 begin
752 begin
756 begin
763 begin
772 end
773 else
774 begin
776 begin
779 begin
783 (*
784 for c := 0 to 1 do
785 begin
786 if (ct.maxsize[c] < 0) then continue;
787 if (ct.desiredsize[c] > ct.maxsize[c]) then
788 begin
789 //writeln('ctl #', f, '; dimension #', c, ': desired=', ctlist[f].desiredsize[c], '; max=', ctlist[f].maxsize[c]);
790 ct.startsize[c] := ct.maxsize[c];
791 ct.desiredsize[c] := ct.maxsize[c];
792 ct.tempFlex := 0; // don't change control size anymore
793 secondAgain := true;
794 end;
795 end;
796 *)
805 (*
806 fourth pass:
807 set 'actualsize' and 'actualpos' to 'desiredsize' and 'desiredpos'
808 return
809 *)
811 var
813 begin
815 begin
822 begin
831 var
835 begin
837 begin
841 writeln(lc.myidx, ': startsize:', lc.startsize.toString(), '; desiredsize=', lc.desiredsize.toString(), '; maxsize=', lc.maxsize.toString(), '; tempFlex=', lc.tempFlex, '; flags=', lc.flags,
842 '; parent=', lc.parent, '; next=', lc.nextSibling, '; child=', lc.firstChild, '; ctl.size=', ds.toString(), '; ctl.maxsize=', ms.toString());
848 var
851 begin
853 begin
856 writeln(lc.myidx, ': startsize:', lc.startsize.toString, '; desiredsize=', lc.desiredsize.toString, '; maxsize=', lc.maxsize.toString, '; tempFlex=', lc.tempFlex, '; despos=', lc.desiredpos.toString);
864 begin
869 // ////////////////////////////////////////////////////////////////////////// //
870 (*
871 void main () begin
872 auto win := new GuiControl();
873 (win ~= new GuiControl()).mSize := TLaySize(10, 5);
874 (win ~= new GuiControl()).mSize := TLaySize(16, 8);
876 //win.mSize := TLaySize(40, 20);
878 auto lay := TFlexLayouterBase!GuiControl();
879 lay.setup(win);
881 writeln('============================');
882 lay.dumpFlat();
884 writeln('=== initial ===');
885 lay.dump();
887 //lay.calcMaxSizeInternal(0);
888 /*
889 lay.firstPass();
890 writeln('=== after first pass ===');
891 lay.dump();
893 lay.secondPass();
894 writeln('=== after second pass ===');
895 lay.dump();
896 */
897 lay.layout();
898 writeln('=== final ===');
899 lay.dump();
900 *)