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 getPadding (): TLaySize; // children padding (each non-first child will get this on left/top)
27 function getMaxSize (): TLaySize; // max size; <0: set to some huge value
28 function getFlex (): Integer; // <=0: not flexible
29 function isHorizBox (): Boolean; // horizontal layout for children?
30 function canWrap (): Boolean; // for horizontal boxes: can wrap children? for child: `false` means 'nonbreakable at *next* ctl'
31 function noPad (): Boolean; // ignore padding in box direction for this control
32 function isLineStart (): Boolean; // `true` if this ctl should start a new line; ignored for vertical boxes
33 function getAlign (): Integer; // aligning in non-main direction: <0: left/up; 0: center; >0: right/down
34 function getExpand (): Boolean; // expanding in non-main direction: `true` will ignore align and eat all available space
35 procedure setActualSizePos (constref apos: TLayPos; constref asize: TLaySize);
36 function getHGroup (): AnsiString; // empty: not grouped
37 function getVGroup (): AnsiString; // empty: not grouped
38 function nextSibling (): ControlT;
39 function firstChild (): ControlT;
40 *)
42 interface
44 uses
45 fui_common;
48 // ////////////////////////////////////////////////////////////////////////// //
49 type
51 public
54 private
57 private
58 // flags
59 const
64 // internal
70 private
71 type
74 public
90 private
94 public
113 public
127 private
135 private
141 // this also sets `tempFlex`
144 procedure fixLine (me: PLayControl; i0, i1: LayControlIdx; ypad: Integer; var cury: Integer; var spaceLeft: Single);
145 // do box layouting; call `layBox()` recursively if necessary
155 public
156 type
158 private
162 public
170 public
174 // clear build lists
177 // build control and group lists
189 implementation
191 uses
192 utils;
195 // ////////////////////////////////////////////////////////////////////////// //
197 begin
204 function TFlexLayouterBase.TLayControl.horizBox (): Boolean; inline; begin result := ((flags and FlagHorizBox) <> 0); end;
205 function TFlexLayouterBase.TLayControl.lineStart (): Boolean; inline; begin result := ((flags and FlagLineStart) <> 0); end;
206 function TFlexLayouterBase.TLayControl.canWrap (): Boolean; inline; begin result := ((flags and FlagLineCanWrap) <> 0); end;
207 function TFlexLayouterBase.TLayControl.inGroup (): Boolean; inline; begin result := ((flags and FlagInGroup) <> 0); end;
208 function TFlexLayouterBase.TLayControl.firstInLine (): Boolean; inline; begin result := ((flags and FlagLineFirst) <> 0); end;
209 function TFlexLayouterBase.TLayControl.noPad (): Boolean; inline; begin result := ((flags and FlagNoPad) <> 0); end;
211 function TFlexLayouterBase.TLayControl.getDidWrap (): Boolean; inline; begin result := ((flags and FlagLineDidWrap) <> 0); end;
212 procedure TFlexLayouterBase.TLayControl.setDidWrap (v: Boolean); inline; begin if (v) then flags := flags or FlagLineDidWrap else flags := flags and (not FlagLineDidWrap); end;
214 function TFlexLayouterBase.TLayControl.getExpand (): Boolean; inline; begin result := ((flags and FlagExpand) <> 0); end;
215 procedure TFlexLayouterBase.TLayControl.setExpand (v: Boolean); inline; begin if (v) then flags := flags or FlagExpand else flags := flags and (not FlagExpand); end;
217 function TFlexLayouterBase.TLayControl.alignLeft (): Boolean; inline; begin result := (aligndir < 0); end;
218 function TFlexLayouterBase.TLayControl.alignTop (): Boolean; inline; begin result := (aligndir < 0); end;
219 function TFlexLayouterBase.TLayControl.alignRight (): Boolean; inline; begin result := (aligndir > 0); end;
220 function TFlexLayouterBase.TLayControl.alignBottom (): Boolean; inline; begin result := (aligndir > 0); end;
221 function TFlexLayouterBase.TLayControl.alignCenter (): Boolean; inline; begin result := (aligndir = 0); end;
224 // ////////////////////////////////////////////////////////////////////////// //
225 constructor TFlexLayouterBase.TChildrenEnumerator.Create (constref actls: TLayCtlArray; acur: Integer);
226 begin
233 begin
235 begin
238 end
239 else
240 begin
247 begin
252 begin
257 // ////////////////////////////////////////////////////////////////////////// //
259 begin
271 begin
278 begin
284 begin
292 var
294 begin
297 //lc.flags := 0;
308 var
311 begin
315 begin
321 begin
324 // first child is always linestart
326 end
327 else
328 begin
342 procedure TFlexLayouterBase.appendToGroup (const gname: AnsiString; cidx: LayControlIdx; gidx: Integer);
343 var
345 begin
351 begin
353 begin
356 exit;
359 // new group
369 var
372 begin
374 begin
382 // build control and group lists
384 begin
388 try
397 except
404 // this also sets `tempFlex`
406 var
416 begin
430 begin
431 // horizontal boxes
439 begin
441 // new line?
443 // need to wrap?
444 if (not doWrap) and (not zerow) and (not negw) and (lc.canWrap) and (c.canWrap) and (msz.w > 0) and (curwdt+c.startsize.w+realpad > lc.startsize.w) then doWrap := true;
446 begin
458 //writeln('00: ', cidx, ': totalhgt=', totalhgt);
460 //writeln('01: ', cidx, ': totalhgt=', totalhgt);
463 end
464 else
465 begin
466 // vertical boxes
471 begin
472 if (lc.startsize.w < c.startsize.w+lc.margins.horiz) then lc.startsize.w := c.startsize.w+lc.margins.horiz;
481 {
482 lc.maxsize := msz;
483 if (lc.maxsize.w < lc.startsize.w) then begin if (lc.maxsize.w >= 0) then lc.maxsize.w := lc.startsize.w; end;
484 if (lc.maxsize.h < lc.startsize.h) then begin if (lc.maxsize.h >= 0) then lc.maxsize.h := lc.startsize.h; end;
485 }
493 var
501 begin
502 // reset all 'laywrap' flags for controls, set initial 'startsize'
504 begin
510 // setup sizes
512 //writeln('=== calculated max size (0) ==='); dump();
513 // find max size for group, adjust 'startsize' controls to group max size
516 begin
518 begin
522 begin
528 begin
532 begin
539 // recalc maxsize if necessary
541 // set "desired size" to "start size"
543 // set flags
545 //writeln('=== calculated max size (final) ==='); dump();
549 procedure TFlexLayouterBase.fixLine (me: PLayControl; i0, i1: LayControlIdx; ypad: Integer; var cury: Integer; var spaceLeft: Single);
550 var
560 begin
564 // calc minimal line height, count flexboxes
567 begin
573 // distribute space, expand/align
577 begin
585 // fix flexbox size
587 begin
590 begin
591 // size changed
594 // compensate (crudely) rounding errors
596 // relayout children
600 // expand or align
605 begin
607 // relayout children
619 // do box layouting; call `layBox()` recursively if necessary
621 var
635 begin
639 // if we have no children, there's nothing to do
641 begin
642 // first, layout all children
645 // second, layout lines, distribute flex data
647 begin
648 // horizontal boxes
657 begin
659 // new line?
661 // need to wrap?
662 if (not doWrap) and (lc.canWrap) and (lc.canWrap) and (lc.desiredsize.w > 0) and (spaceLeft-realpad < lc.desiredsize.w) then doWrap := true;
664 begin
665 // new line, fix this one
671 end
672 else
673 begin
678 //if (maxhgt < lc.desiredsize.h) then maxhgt := lc.desiredsize.h;
679 //if (lc.tempFlex > 0) then begin flexTotal += lc.tempFlex; flexBoxCount += 1; end;
681 // fix last line
683 end
684 else
685 begin
686 // vertical boxes
693 // calc flex
695 begin
701 // distribute space
703 //writeln('me: ', boxidx, '; margins: ', me.margins.toString);
705 begin
712 // fix flexbox size
714 begin
717 begin
718 // size changed
721 // compensate (crudely) rounding errors
725 // expand or align
727 else if (lc.alignRight) then lc.desiredpos.x := me.desiredsize.w-me.margins.right-lc.desiredsize.w // right align
728 else if (lc.alignCenter) then lc.desiredpos.x := (me.desiredsize.w-lc.desiredsize.w) div 2; // center
730 begin
732 // relayout children
739 if (me.maxsize.w >= 0) and (me.desiredsize.w > me.maxsize.w) then me.desiredsize.w := me.maxsize.w;
740 if (me.maxsize.h >= 0) and (me.desiredsize.h > me.maxsize.h) then me.desiredsize.h := me.maxsize.h;
745 begin
746 // reset flags
751 begin
756 // fix 'wrapping-changed' flag
762 var
770 begin
772 begin
776 begin
778 // find max size for group, adjust 'startsize' controls to group max size
780 begin
782 begin
786 begin
793 begin
802 end
803 else
804 begin
806 begin
809 begin
813 (*
814 for c := 0 to 1 do
815 begin
816 if (ct.maxsize[c] < 0) then continue;
817 if (ct.desiredsize[c] > ct.maxsize[c]) then
818 begin
819 //writeln('ctl #', f, '; dimension #', c, ': desired=', ctlist[f].desiredsize[c], '; max=', ctlist[f].maxsize[c]);
820 ct.startsize[c] := ct.maxsize[c];
821 ct.desiredsize[c] := ct.maxsize[c];
822 ct.tempFlex := 0; // don't change control size anymore
823 secondAgain := true;
824 end;
825 end;
826 *)
835 (*
836 fourth pass:
837 set 'actualsize' and 'actualpos' to 'desiredsize' and 'desiredpos'
838 return
839 *)
841 var
843 begin
845 begin
852 begin
861 var
865 begin
867 begin
871 writeln(lc.myidx, ': startsize:', lc.startsize.toString(), '; desiredsize=', lc.desiredsize.toString(), '; maxsize=', lc.maxsize.toString(), '; tempFlex=', lc.tempFlex, '; flags=', lc.flags,
872 '; parent=', lc.parent, '; next=', lc.nextSibling, '; child=', lc.firstChild, '; ctl.size=', ds.toString(), '; ctl.maxsize=', ms.toString());
878 var
881 begin
883 begin
886 writeln(lc.myidx, ': startsize:', lc.startsize.toString, '; desiredsize=', lc.desiredsize.toString, '; maxsize=', lc.maxsize.toString, '; tempFlex=', lc.tempFlex, '; despos=', lc.desiredpos.toString);
894 begin
899 // ////////////////////////////////////////////////////////////////////////// //
900 (*
901 void main () begin
902 auto win := new GuiControl();
903 (win ~= new GuiControl()).mSize := TLaySize(10, 5);
904 (win ~= new GuiControl()).mSize := TLaySize(16, 8);
906 //win.mSize := TLaySize(40, 20);
908 auto lay := TFlexLayouterBase!GuiControl();
909 lay.setup(win);
911 writeln('============================');
912 lay.dumpFlat();
914 writeln('=== initial ===');
915 lay.dump();
917 //lay.calcMaxSizeInternal(0);
918 /*
919 lay.firstPass();
920 writeln('=== after first pass ===');
921 lay.dump();
923 lay.secondPass();
924 writeln('=== after second pass ===');
925 lay.dump();
926 */
927 lay.layout();
928 writeln('=== final ===');
929 lay.dump();
930 *)