X-Git-Url: http://deadsoftware.ru/gitweb?a=blobdiff_plain;f=src%2Fflexui%2Ffui_flexlay.pas;h=4bf7ce791a7ba5ebfe8eeec21dc212f92dc32686;hb=987c4a835a103345b59937e8e1be8524a6228712;hp=99db4fe02e8adf31de7f2770e0a49ed8110957bf;hpb=26290d9816334b8377531cf8b3e58643444f4d04;p=d2df-sdl.git diff --git a/src/flexui/fui_flexlay.pas b/src/flexui/fui_flexlay.pas index 99db4fe..4bf7ce7 100644 --- a/src/flexui/fui_flexlay.pas +++ b/src/flexui/fui_flexlay.pas @@ -3,8 +3,7 @@ * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * the Free Software Foundation, version 3 of the License ONLY. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -23,11 +22,11 @@ ControlT: procedure layPrepare (); // called before registering control in layouter function getDefSize (): TLaySize; // default size; <0: use max size function getMargins (): TLayMargins; + function getPadding (): TLaySize; // children padding (each non-first child will get this on left/top) function getMaxSize (): TLaySize; // max size; <0: set to some huge value function getFlex (): Integer; // <=0: not flexible function isHorizBox (): Boolean; // horizontal layout for children? - function canWrap (): Boolean; // for horizontal boxes: can wrap children? for child: `false` means 'nonbreakable at *next* ctl' - function isLineStart (): Boolean; // `true` if this ctl should start a new line; ignored for vertical boxes + function noPad (): Boolean; // ignore padding in box direction for this control function getAlign (): Integer; // aligning in non-main direction: <0: left/up; 0: center; >0: right/down function getExpand (): Boolean; // expanding in non-main direction: `true` will ignore align and eat all available space procedure setActualSizePos (constref apos: TLayPos; constref asize: TLaySize); @@ -56,13 +55,11 @@ type // flags const FlagHorizBox = LongWord(1) shl 0; // horizontal layout for children - FlagLineStart = LongWord(1) shl 1; - FlagLineCanWrap = LongWord(1) shl 2; + FlagNoPad = LongWord(1) shl 1; + FlagExpand = LongWord(1) shl 2; // internal - FlagLineDidWrap = LongWord(1) shl 3; // will be set when line was wrapped - FlagInGroup = LongWord(1) shl 4; // set if this control is a member of any group - FlagExpand = LongWord(1) shl 5; - FlagLineFirst = LongWord(1) shl 6; + FlagInGroupH = LongWord(1) shl 8; // set if this control is a member of any group + FlagInGroupV = LongWord(1) shl 9; // set if this control is a member of any group private type @@ -71,30 +68,25 @@ type public myidx: LayControlIdx; tempFlex: Integer; - flags: LongWord; // see below + flags: LongWord; // see above aligndir: Integer; - startsize: TLaySize; // current - desiredsize: TLaySize; - maxsize: TLaySize; + startsize: TLaySize; // original size + desiredsize: TLaySize; // current size + maxsize: TLaySize; // current maximum size margins: TLayMargins; // can never be negative + padding: TLaySize; desiredpos: TLayPos; ctl: ControlT; parent: LayControlIdx; // = -1; firstChild: LayControlIdx; // = -1; nextSibling: LayControlIdx; // = -1; - private - function getDidWrap (): Boolean; inline; - procedure setDidWrap (v: Boolean); inline; - public procedure initialize (); inline; function horizBox (): Boolean; inline; - function lineStart (): Boolean; inline; - function canWrap (): Boolean; inline; - function inGroup (): Boolean; inline; - function firstInLine (): Boolean; inline; + function inGroup (idx: Integer): Boolean; inline; + function noPad (): Boolean; inline; function getExpand (): Boolean; inline; procedure setExpand (v: Boolean); inline; @@ -105,8 +97,9 @@ type function alignBottom (): Boolean; inline; function alignCenter (): Boolean; inline; + function visible (): Boolean; inline; + public - property didWrap: Boolean read getDidWrap write setDidWrap; property expand: Boolean read getExpand write setExpand; end; @@ -122,28 +115,22 @@ type private ctlist: TLayCtlArray; groups: array[0..1] of TLayGrpArray; // horiz, vert - - firstTime: Boolean; groupElementChanged: Boolean; - wrappingChanged: Boolean; private - procedure fixFlags (cidx: LayControlIdx); + procedure firstTimeSetup (cidx: LayControlIdx); procedure doChildren (parent: LayControlIdx; child: ControlT); procedure appendToGroup (const gname: AnsiString;cidx: LayControlIdx;gidx: Integer); + procedure clearGroups (); procedure setupGroups (); - // this also sets `tempFlex` - procedure calcMaxSizeInternal (cidx: LayControlIdx); - - procedure fixLine (me: PLayControl; i0, i1: LayControlIdx; var cury: Integer; var spaceLeft: Single); + procedure distributeChildren (boxidx: LayControlIdx; maindir: Integer); // do box layouting; call `layBox()` recursively if necessary procedure layBox (boxidx: LayControlIdx); procedure firstPass (); procedure secondPass (); procedure thirdPass (); - procedure fourthPass (); procedure dumpList (cidx: LayControlIdx; indent: Integer); @@ -154,8 +141,9 @@ type ctls: TLayCtlArray; cur: Integer; first: Boolean; + onlyVisible: Boolean; public - constructor Create (constref actls: TLayCtlArray; acur: Integer); + constructor Create (constref actls: TLayCtlArray; acur: Integer; aonlyvis: Boolean); function moveNext (): Boolean; inline; function getCurrent (): PLayControl; inline; function getEnumerator (): TChildrenEnumerator; inline; @@ -173,6 +161,7 @@ type procedure setup (root: ControlT); function forChildren (cidx: LayControlIdx): TChildrenEnumerator; inline; + function forVisibleChildren (cidx: LayControlIdx): TChildrenEnumerator; inline; procedure layout (); @@ -197,13 +186,8 @@ begin end; function TFlexLayouterBase.TLayControl.horizBox (): Boolean; inline; begin result := ((flags and FlagHorizBox) <> 0); end; -function TFlexLayouterBase.TLayControl.lineStart (): Boolean; inline; begin result := ((flags and FlagLineStart) <> 0); end; -function TFlexLayouterBase.TLayControl.canWrap (): Boolean; inline; begin result := ((flags and FlagLineCanWrap) <> 0); end; -function TFlexLayouterBase.TLayControl.inGroup (): Boolean; inline; begin result := ((flags and FlagInGroup) <> 0); end; -function TFlexLayouterBase.TLayControl.firstInLine (): Boolean; inline; begin result := ((flags and FlagLineFirst) <> 0); end; - -function TFlexLayouterBase.TLayControl.getDidWrap (): Boolean; inline; begin result := ((flags and FlagLineDidWrap) <> 0); end; -procedure TFlexLayouterBase.TLayControl.setDidWrap (v: Boolean); inline; begin if (v) then flags := flags or FlagLineDidWrap else flags := flags and (not FlagLineDidWrap); end; +function TFlexLayouterBase.TLayControl.inGroup (idx: Integer): Boolean; inline; begin if (idx = 0) then result := ((flags and FlagInGroupH) <> 0) else if (idx = 1) then result := ((flags and FlagInGroupV) <> 0) else result := false; end; +function TFlexLayouterBase.TLayControl.noPad (): Boolean; inline; begin result := ((flags and FlagNoPad) <> 0); end; function TFlexLayouterBase.TLayControl.getExpand (): Boolean; inline; begin result := ((flags and FlagExpand) <> 0); end; procedure TFlexLayouterBase.TLayControl.setExpand (v: Boolean); inline; begin if (v) then flags := flags or FlagExpand else flags := flags and (not FlagExpand); end; @@ -214,27 +198,38 @@ function TFlexLayouterBase.TLayControl.alignRight (): Boolean; inline; begin res function TFlexLayouterBase.TLayControl.alignBottom (): Boolean; inline; begin result := (aligndir > 0); end; function TFlexLayouterBase.TLayControl.alignCenter (): Boolean; inline; begin result := (aligndir = 0); end; +function TFlexLayouterBase.TLayControl.visible (): Boolean; inline; +begin + result := (startsize.w <> 0) or (startsize.h <> 0); +end; + // ////////////////////////////////////////////////////////////////////////// // -constructor TFlexLayouterBase.TChildrenEnumerator.Create (constref actls: TLayCtlArray; acur: Integer); +constructor TFlexLayouterBase.TChildrenEnumerator.Create (constref actls: TLayCtlArray; acur: Integer; aonlyvis: Boolean); begin ctls := actls; cur := acur; first := true; + onlyVisible := aonlyvis; end; function TFlexLayouterBase.TChildrenEnumerator.moveNext (): Boolean; inline; begin - if first then - begin - if (cur >= 0) and (cur < Length(ctls)) then cur := ctls[cur].firstChild else cur := -1; - first := false; - end - else + while true do begin - cur := ctls[cur].nextSibling; + if first then + begin + if (cur >= 0) and (cur < Length(ctls)) then cur := ctls[cur].firstChild else cur := -1; + first := false; + end + else + begin + cur := ctls[cur].nextSibling; + end; + result := (cur >= 0); + if (not result) or (not onlyVisible) then break; + if (ctls[cur].visible) then break; end; - result := (cur >= 0); end; function TFlexLayouterBase.TChildrenEnumerator.getCurrent (): PLayControl; inline; @@ -254,10 +249,7 @@ begin ctlist := nil; groups[0] := nil; groups[1] := nil; - - firstTime := false; groupElementChanged := false; - wrappingChanged := false; end; @@ -270,30 +262,19 @@ end; function TFlexLayouterBase.forChildren (cidx: LayControlIdx): TChildrenEnumerator; inline; begin - result := TChildrenEnumerator.Create(ctlist, cidx); + result := TChildrenEnumerator.Create(ctlist, cidx, false); end; - -procedure TFlexLayouterBase.clear (); +function TFlexLayouterBase.forVisibleChildren (cidx: LayControlIdx): TChildrenEnumerator; inline; begin - ctlist := nil; - groups[0] := nil; - groups[1] := nil; + result := TChildrenEnumerator.Create(ctlist, cidx, true); end; -procedure TFlexLayouterBase.fixFlags (cidx: LayControlIdx); -var - lc: PLayControl; +procedure TFlexLayouterBase.clear (); begin - assert((cidx >= 0) and (cidx < Length(ctlist))); - lc := @ctlist[cidx]; - //lc.flags := 0; - if (lc.ctl.isHorizBox) then lc.flags := lc.flags or FlagHorizBox; - if (lc.ctl.isLineStart) then lc.flags := lc.flags or FlagLineStart; - if (lc.ctl.canWrap) then lc.flags := lc.flags or FlagLineCanWrap; - if (lc.ctl.getExpand) then lc.flags := lc.flags or FlagExpand; - lc.aligndir := lc.ctl.getAlign; + clearGroups(); + ctlist := nil; end; @@ -307,6 +288,7 @@ begin while (child <> nil) do begin child.layPrepare; + //if (msz.w = 0) or (msz.h = 0) then continue; // hidden controls will have zero maxsize, so skip 'em SetLength(ctlist, Length(ctlist)+1); lc := @ctlist[High(ctlist)]; lc.initialize(); @@ -314,8 +296,6 @@ begin begin cidx := LayControlIdx(High(ctlist)); ctlist[parent].firstChild := cidx; - // first child is always linestart - lc.flags := lc.flags or FlagLineStart or FlagLineFirst; end else begin @@ -325,7 +305,6 @@ begin lc.myidx := cidx; lc.ctl := child; lc.parent := parent; - fixFlags(cidx); doChildren(cidx, child.firstChild); child := child.nextSibling; end; @@ -335,11 +314,13 @@ end; procedure TFlexLayouterBase.appendToGroup (const gname: AnsiString; cidx: LayControlIdx; gidx: Integer); var f: Integer; + gflg: LongWord; begin if (Length(gname) = 0) then exit; assert((cidx >= 0) and (cidx < Length(ctlist))); assert((gidx = 0) or (gidx = 1)); - ctlist[cidx].flags := ctlist[cidx].flags or FlagInGroup; + if (gidx = 0) then gflg := FlagInGroupH else gflg := FlagInGroupV; + ctlist[cidx].flags := ctlist[cidx].flags or gflg; for f := 0 to High(groups[gidx]) do begin if (groups[gidx][f].name = gname) then @@ -358,17 +339,59 @@ begin end; +procedure TFlexLayouterBase.clearGroups (); +var + gidx, f: Integer; +begin + for gidx := 0 to 1 do + begin + for f := 0 to High(groups[gidx]) do groups[gidx][f].ctls := nil; + groups[gidx] := nil; + end; +end; + + procedure TFlexLayouterBase.setupGroups (); var - idx: Integer; + gflg: LongWord; + idx, gidx, f, c: Integer; lc: PLayControl; begin + clearGroups(); for idx := 0 to High(ctlist) do begin lc := @ctlist[idx]; appendToGroup(lc.ctl.getHGroup, LayControlIdx(idx), 0); appendToGroup(lc.ctl.getVGroup, LayControlIdx(idx), 1); end; + // if control is only one in a group, mark is as "not grouped" + for gidx := 0 to 1 do + begin + if (gidx = 0) then gflg := LongWord(not FlagInGroupH) else gflg := LongWord(not FlagInGroupV); + f := 0; + while (f < Length(groups[gidx])) do + begin + if (Length(groups[gidx][f].ctls) < 2) then + begin + // unmark controls + for c := 0 to High(groups[gidx][f].ctls) do + begin + lc := @ctlist[groups[gidx][f].ctls[c]]; + lc.flags := lc.flags and gflg; + end; + // remove this group + groups[gidx][f].ctls := nil; + for c := f+1 to High(groups[gidx]) do groups[gidx][c-1] := groups[gidx][c]; + c := High(groups[gidx]); + groups[gidx][c].ctls := nil; + SetLength(groups[gidx], c); + end + else + begin + Inc(f); + end; + end; + end; end; @@ -383,10 +406,7 @@ begin ctlist[0].initialize(); ctlist[0].myidx := 0; ctlist[0].ctl := root; - fixFlags(0); - ctlist[0].flags := ctlist[0].flags or FlagLineStart or FlagLineFirst; doChildren(0, root.firstChild); - setupGroups(); except clear(); raise; @@ -394,205 +414,159 @@ begin end; -// this also sets `tempFlex` -procedure TFlexLayouterBase.calcMaxSizeInternal (cidx: LayControlIdx); +procedure TFlexLayouterBase.firstTimeSetup (cidx: LayControlIdx); var - lc, c: PLayControl; - msz: TLaySize; - negw, negh: Boolean; - zerow: Boolean; - curwdt, curhgt, totalhgt: Integer; - doWrap: Boolean; + lc: PLayControl; begin - if (cidx < 0) or (cidx >= Length(ctlist)) then exit; - + assert((cidx >= 0) and (cidx < Length(ctlist))); lc := @ctlist[cidx]; - msz := lc.ctl.getMaxSize; - negw := (lc.startsize.w < 0); - negh := (lc.startsize.h < 0); - zerow := (lc.startsize.w = 0); - + lc.flags := 0; + if (lc.ctl.isHorizBox) then lc.flags := lc.flags or FlagHorizBox; + if (lc.ctl.getExpand) then lc.flags := lc.flags or FlagExpand; + if (lc.ctl.noPad) then lc.flags := lc.flags or FlagNoPad; + lc.aligndir := lc.ctl.getAlign; + lc.startsize := lc.ctl.getDefSize; + //lc.startsize.w := nmax(0, lc.startsize.w); + //lc.startsize.h := nmax(0, lc.startsize.h); + lc.margins := lc.ctl.getMargins; + lc.margins.left := nmax(0, lc.margins.left); + lc.margins.top := nmax(0, lc.margins.top); + lc.margins.right := nmax(0, lc.margins.right); + lc.margins.bottom := nmax(0, lc.margins.bottom); + lc.padding := lc.ctl.getPadding; + lc.padding.w := nmax(0, lc.padding.w); + lc.padding.h := nmax(0, lc.padding.h); + lc.maxsize := TLaySize.Create(-1, -1); + if (lc.maxsize.w >= 0) then lc.startsize.w := nmin(lc.maxsize.w, lc.startsize.w); + if (lc.maxsize.h >= 0) then lc.startsize.h := nmin(lc.maxsize.h, lc.startsize.h); + lc.desiredsize := lc.startsize; lc.tempFlex := lc.ctl.getFlex; - - for c in forChildren(cidx) do calcMaxSizeInternal(c.myidx); - - if (lc.horizBox) then - begin - // horizontal boxes - if (negw) then lc.tempFlex := 0; // size is negative: don't expand - curwdt := lc.margins.horiz; - curhgt := 0; - totalhgt := lc.margins.vert; - for c in forChildren(cidx) do - begin - // new line? - doWrap := (not c.firstInLine) and (c.lineStart); - // need to wrap? - 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; - if (doWrap) then - begin - totalhgt += curhgt; - if (lc.startsize.w < curwdt) then lc.startsize.w := curwdt; - curwdt := 0; - curhgt := 0; - end; - curwdt += c.startsize.w; - if (curhgt < c.startsize.h) then curhgt := c.startsize.h; - end; - //writeln('00: ', cidx, ': totalhgt=', totalhgt); - totalhgt += curhgt; - //writeln('01: ', cidx, ': totalhgt=', totalhgt); - if (lc.startsize.w < curwdt) then lc.startsize.w := curwdt; - if (lc.startsize.h < totalhgt) then lc.startsize.h := totalhgt; - end - else - begin - // vertical boxes - if (negh) then lc.tempFlex := 0; // size is negative: don't expand - curhgt := lc.margins.vert; - for c in forChildren(cidx) do - begin - if (lc.startsize.w < c.startsize.w+lc.margins.horiz) then lc.startsize.w := c.startsize.w+lc.margins.horiz; - curhgt += c.startsize.h; - end; - if (lc.startsize.h < curhgt) then lc.startsize.h := curhgt; - end; - if (lc.startsize.w < 0) then lc.startsize.w := 0; - if (lc.startsize.h < 0) then lc.startsize.h := 0; - { - lc.maxsize := msz; - if (lc.maxsize.w < lc.startsize.w) then begin if (lc.maxsize.w >= 0) then lc.maxsize.w := lc.startsize.w; end; - if (lc.maxsize.h < lc.startsize.h) then begin if (lc.maxsize.h >= 0) then lc.maxsize.h := lc.startsize.h; end; - } - if (msz.w < 0) then msz.w := lc.startsize.w; - if (msz.h < 0) then msz.h := lc.startsize.h; - lc.maxsize := msz; end; procedure TFlexLayouterBase.firstPass (); var - f, c: Integer; - needRecalcMaxSize: Boolean; + f: Integer; gtype: Integer; - grp: PLayGroup; - maxsz: Integer; - cidx: LayControlIdx; - ct: PLayControl; - mr: TLayMargins; begin - // reset all 'laywrap' flags for controls, set initial 'startsize' - for f := 0 to High(ctlist) do - begin - ctlist[f].didWrap := false; - ctlist[f].startsize := ctlist[f].ctl.getDefSize; - mr := ctlist[f].ctl.getMargins; - ctlist[f].margins := mr; - //ctlist[f].startsize.w += mr.horiz; - //ctlist[f].startsize.h += mr.vert; - end; - // setup sizes - calcMaxSizeInternal(0); // this also sets `tempFlex` - //writeln('=== calculated max size (0) ==='); dump(); - // find max size for group, adjust 'startsize' controls to group max size - needRecalcMaxSize := false; + groupElementChanged := false; + setupGroups(); + for f := 0 to High(ctlist) do firstTimeSetup(f); + // if we have any groups, set "group element changed" flag, so third pass will fix 'em for gtype := 0 to 1 do begin - for f := 0 to High(groups[gtype]) do + if (Length(groups[gtype]) > 0) then begin - grp := @groups[gtype][f]; - maxsz := 0; - for c := 0 to High(grp.ctls) do - begin - cidx := grp.ctls[c]; - ct := @ctlist[cidx]; - if (maxsz < ct.startsize[gtype]) then maxsz := ct.startsize[gtype]; - end; - for c := 0 to High(grp.ctls) do - begin - cidx := grp.ctls[c]; - ct := @ctlist[cidx]; - if (maxsz <> ct.startsize[gtype]) then - begin - needRecalcMaxSize := true; - ct.startsize[gtype] := maxsz; - end; - end; + groupElementChanged := true; + break; end; end; - // recalc maxsize if necessary - if (needRecalcMaxSize) then calcMaxSizeInternal(0); - // set "desired size" to "start size" - for f := 0 to High(ctlist) do ctlist[f].desiredsize := ctlist[f].startsize; - // set flags - firstTime := true; - //writeln('=== calculated max size (final) ==='); dump(); end; -procedure TFlexLayouterBase.fixLine (me: PLayControl; i0, i1: LayControlIdx; var cury: Integer; var spaceLeft: Single); +procedure TFlexLayouterBase.distributeChildren (boxidx: LayControlIdx; maindir: Integer); var + me, lc: PLayControl; + suppdir: Integer; + marg0, marg1, margtotal: Integer; + marg0Op, marg1Op, margtotalOp: Integer; flexTotal: Integer = 0; // total sum of flex fields - flexBoxCount: Integer = 0; // number of boxes - curx: Integer; - lc: PLayControl; - osz: TLaySize; + spaceLeft: Integer = 0; + dopad: Boolean = false; + prevpad: Boolean = false; + maxdim: Integer = 0; + curpos: Integer; toadd: Integer; - sti0: Integer; - lineh: Integer; + pad: Integer; + osz: TLaySize; begin - curx := me.margins.left; - sti0 := i0; - // calc minimal line height, count flexboxes - lineh := 0; - while (i0 <> i1) do + assert((boxidx >= 0) and (boxidx < Length(ctlist))); + assert((maindir = 0) or (maindir = 1)); + // cache some parameters + me := @ctlist[boxidx]; + suppdir := 1-maindir; + if (maindir = 0) then begin - lc := @ctlist[i0]; - lineh := nmax(lineh, lc.startsize.h); - if (lc.tempFlex > 0) then begin flexTotal += lc.tempFlex; flexBoxCount += 1; end; - i0 := lc.nextSibling; + marg0 := me.margins.left; + marg1 := me.margins.right; + marg0Op := me.margins.top; + marg1Op := me.margins.bottom; + end + else + begin + marg0 := me.margins.top; + marg1 := me.margins.bottom; + marg0Op := me.margins.left; + marg1Op := me.margins.right; end; - // distribute space, expand/align - i0 := sti0; - while (i0 <> i1) do + margtotal := marg0+marg1; + margtotalOp := marg0Op+marg1Op; + // horizontal boxes + pad := nmax(0, me.padding[maindir]); + // calc required space, count flexes + for lc in forVisibleChildren(boxidx) do + begin + if (lc.tempFlex > 0) then flexTotal += lc.tempFlex; + spaceLeft += nmax(0, lc.desiredsize[maindir]); + // insert padding if both current and previous children allow padding + dopad := (not lc.noPad); + if (prevpad) and (dopad) then spaceLeft += pad; + prevpad := dopad; + maxdim := nmax(maxdim, lc.desiredsize[suppdir]); + end; + // add margins + spaceLeft += margtotal; + maxdim += margtotalOp; + // fix box size + me.desiredsize[maindir] := nmax(spaceLeft, me.desiredsize[maindir]); + me.desiredsize[suppdir] := nmax(maxdim, me.desiredsize[suppdir]); + // calculate free space + spaceLeft := me.desiredsize[maindir]-spaceLeft; + // distribute children + dopad := false; + prevpad := false; + curpos := marg0; + for lc in forVisibleChildren(boxidx) do begin - lc := @ctlist[i0]; osz := lc.desiredsize; - lc.desiredsize := lc.startsize; - lc.desiredpos.x := curx; - lc.desiredpos.y := cury; - curx += lc.desiredsize.w; + // main direction + // insert padding if both current and previous children allow padding + dopad := (not lc.noPad); + if (prevpad) and (dopad) then curpos += pad; + prevpad := dopad; + lc.desiredpos[maindir] := curpos; + if (lc.desiredsize[maindir] < 0) then lc.desiredsize[maindir] := 0; + curpos += lc.desiredsize[maindir]; // fix flexbox size - if (lc.tempFlex > 0) and (spaceLeft > 0) then + //writeln(':lcidx=', lc.myidx, '; tempFlex=', lc.tempFlex, '; spaceLeft=', spaceLeft); + if (spaceLeft > 0) and (lc.tempFlex > 0) then begin toadd := trunc(spaceLeft*lc.tempFlex/flexTotal+0.5); if (toadd > 0) then begin // size changed - lc.desiredsize.w += toadd; - curx += toadd; // compensate (crudely) rounding errors - if (curx > me.desiredsize.w-me.margins.horiz) then begin lc.desiredsize.w -= 1; curx -= 1; end; - // relayout children - layBox(lc.firstChild); + if (curpos+toadd > me.desiredsize[maindir]-margtotal) then toadd -= 1; + //writeln('***curpos=', curpos, '; toadd=', toadd, '; spaceLeft=', spaceLeft); + // fix size + lc.desiredsize[maindir] := lc.desiredsize[maindir]+toadd; + curpos += toadd; end; end; - // expand or align - if (lc.expand) then lc.desiredsize.h := nmax(1, lineh) // expand - else if (lc.alignBottom) then lc.desiredpos.y := cury+(lineh-lc.desiredsize.h) // bottom align - else if (lc.alignCenter) then lc.desiredpos.y := cury+(lineh-lc.desiredsize.h) div 2; // center + // secondary direction: expand or align + if (lc.desiredsize[suppdir] < 0) then lc.desiredsize[suppdir] := 0; + lc.desiredpos[suppdir] := marg0Op; // left/top align + if (lc.expand) then lc.desiredsize[suppdir] := me.desiredsize[suppdir]-margtotalOp // expand + else if (lc.aligndir > 0) then lc.desiredpos[suppdir] := me.desiredsize[suppdir]-marg1Op-lc.desiredsize[suppdir] // right/bottom align + else if (lc.aligndir = 0) then lc.desiredpos[suppdir] := (me.desiredsize[suppdir]-lc.desiredsize[suppdir]) div 2; // center + lc.desiredsize[suppdir] := nmax(lc.desiredsize[suppdir], osz[suppdir]); + // relayout children if size was changed if (not osz.equals(lc.desiredsize)) then begin - if (lc.inGroup) then groupElementChanged := true; - // relayout children - layBox(lc.firstChild); + if (lc.inGroup(0)) or (lc.inGroup(1)) then groupElementChanged := true; + layBox(lc.myidx); end; - i0 := lc.nextSibling; end; - flexTotal := 0; - flexBoxCount := 0; - spaceLeft := me.desiredsize.w-me.margins.horiz; - cury += lineh; end; @@ -600,152 +574,51 @@ end; procedure TFlexLayouterBase.layBox (boxidx: LayControlIdx); var me: PLayControl; - flexTotal: Integer; // total sum of flex fields - flexBoxCount: Integer; // number of boxes - spaceLeft: Single; - cury: Integer; - maxwdt: Integer; - lineStartIdx: LayControlIdx; lc: PLayControl; - doWrap: Boolean; - toadd: Integer; osz: TLaySize; begin if (boxidx < 0) or (boxidx >= Length(ctlist)) then exit; me := @ctlist[boxidx]; - // if we have no children, there's nothing to do if (me.firstChild <> -1) then begin - // first, layout all children - for lc in forChildren(boxidx) do layBox(lc.myidx); - - // second, layout lines, distribute flex data - if (me.horizBox) then - begin - // horizontal boxes - cury := me.margins.top; - - fixLine(me, -1, -1, cury, spaceLeft); //HACK! - - lineStartIdx := me.firstChild; - for lc in forChildren(boxidx) do - begin - // new line? - doWrap := (not lc.firstInLine) and (lc.lineStart); - // need to wrap? - if (not doWrap) and (lc.canWrap) and (lc.canWrap) and (lc.desiredsize.w > 0) and (spaceLeft < lc.desiredsize.w) then doWrap := true; - if (doWrap) then - begin - // new line, fix this one - if (not lc.didWrap) then begin wrappingChanged := true; lc.didWrap := true; end; - fixLine(me, lineStartIdx, lc.myidx, cury, spaceLeft); - lineStartIdx := lc.myidx; - end - else - begin - if (lc.didWrap) then begin wrappingChanged := true; lc.didWrap := false; end; - end; - spaceLeft -= lc.desiredsize.w; - //if (maxhgt < lc.desiredsize.h) then maxhgt := lc.desiredsize.h; - //if (lc.tempFlex > 0) then begin flexTotal += lc.tempFlex; flexBoxCount += 1; end; - end; - // fix last line - fixLine(me, lineStartIdx, -1, cury, spaceLeft); - end - else + while true do begin - // vertical boxes - maxwdt := 0; - flexTotal := 0; - flexBoxCount := 0; - spaceLeft := me.desiredsize.h-me.margins.vert; - - // calc flex - for lc in forChildren(boxidx) do - begin - spaceLeft -= lc.desiredsize.h; - if (maxwdt < lc.desiredsize.w) then maxwdt := lc.desiredsize.w; - if (lc.tempFlex > 0) then begin flexTotal += lc.tempFlex; flexBoxCount += 1; end; - end; - - // distribute space - cury := me.margins.top; - //writeln('me: ', boxidx, '; margins: ', me.margins.toString); - for lc in forChildren(boxidx) do - begin - osz := lc.desiredsize; - lc.desiredsize := lc.startsize; - lc.desiredpos.x := me.margins.left; - lc.desiredpos.y := cury; - cury += lc.desiredsize.h; - // fix flexbox size - if (lc.tempFlex > 0) and (spaceLeft > 0) then - begin - toadd := trunc(spaceLeft*lc.tempFlex/flexTotal+0.5); - if (toadd > 0) then - begin - // size changed - lc.desiredsize.h += toadd; - cury += toadd; - // compensate (crudely) rounding errors - if (cury > me.desiredsize.h-me.margins.vert) then begin lc.desiredsize.h -= 1; cury -= 1; end; - end; - end; - // expand or align - if (lc.expand) then lc.desiredsize.w := nmax(1, me.desiredsize.w-me.margins.vert) // expand - else if (lc.alignRight) then lc.desiredpos.x := me.desiredsize.w-me.margins.right-lc.desiredsize.w // right align - else if (lc.alignCenter) then lc.desiredpos.x := (me.desiredsize.w-lc.desiredsize.w) div 2; // center - if (not osz.equals(lc.desiredsize)) then - begin - if (lc.inGroup) then groupElementChanged := true; - // relayout children - layBox(lc.firstChild); - end; - end; + osz := me.desiredsize; + // layout all children + for lc in forVisibleChildren(boxidx) do layBox(lc.myidx); + // distribute children + if (me.horizBox) then distributeChildren(me.myidx, 0) else distributeChildren(me.myidx, 1); + // relayout children if size was changed + if (osz.equals(me.desiredsize)) then break; + if (me.inGroup(0)) or (me.inGroup(1)) then groupElementChanged := true; end; end; - - if (me.maxsize.w >= 0) and (me.desiredsize.w > me.maxsize.w) then me.desiredsize.w := me.maxsize.w; - if (me.maxsize.h >= 0) and (me.desiredsize.h > me.maxsize.h) then me.desiredsize.h := me.maxsize.h; end; procedure TFlexLayouterBase.secondPass (); -begin - // reset flags - groupElementChanged := false; - wrappingChanged := false; - - if (Length(ctlist) > 0) then - begin - ctlist[0].desiredpos := TLayPos.Create(0, 0); - layBox(0); - end; - - // fix 'wrapping-changed' flag - if (firstTime) then begin wrappingChanged := false; firstTime := false; end; -end; - - -procedure TFlexLayouterBase.thirdPass (); var secondAgain: Boolean; gtype: Integer; maxsz: Integer; grp: PLayGroup; f, c: Integer; + maindir: Integer; cidx: LayControlIdx; ct: PLayControl; + loopsLeft: Integer = 64; begin - while true do + while (loopsLeft > 0) do begin - secondPass(); + Dec(loopsLeft); + layBox(0); secondAgain := false; if (groupElementChanged) then begin secondAgain := true; - // find max size for group, adjust 'startsize' controls to group max size + groupElementChanged := false; + // fix group sizes for gtype := 0 to 1 do begin for f := 0 to High(groups[gtype]) do @@ -756,58 +629,38 @@ begin begin cidx := grp.ctls[c]; ct := @ctlist[cidx]; - ct.expand := false; // don't expand grouped controls anymore - if (maxsz < ct.startsize[gtype]) then maxsz := ct.startsize[gtype]; + maxsz := nmax(maxsz, ct.desiredsize[gtype]); end; for c := 0 to High(grp.ctls) do begin cidx := grp.ctls[c]; ct := @ctlist[cidx]; - ct.startsize[gtype] := maxsz; ct.desiredsize[gtype] := maxsz; - ct.tempFlex := 0; // don't change control size anymore end; end; end; - end - else + end; + // don't change group control sizes anymore + for f := 0 to High(ctlist) do begin - for f := 0 to High(ctlist) do + ct := @ctlist[f]; + if (ct.parent <> -1) then begin - ct := @ctlist[f]; - if (ct.inGroup) then - begin - ct.expand := false; // don't expand grouped controls anymore - ct.tempFlex := 0; // don't change control size anymore - end; - (* - for c := 0 to 1 do - begin - if (ct.maxsize[c] < 0) then continue; - if (ct.desiredsize[c] > ct.maxsize[c]) then - begin - //writeln('ctl #', f, '; dimension #', c, ': desired=', ctlist[f].desiredsize[c], '; max=', ctlist[f].maxsize[c]); - ct.startsize[c] := ct.maxsize[c]; - ct.desiredsize[c] := ct.maxsize[c]; - ct.tempFlex := 0; // don't change control size anymore - secondAgain := true; - end; - end; - *) + if (ctlist[ct.parent].horizBox) then maindir := 0 else maindir := 1; + end + else + begin + maindir := 0; // arbitrary end; + if (ct.inGroup(maindir)) then ct.tempFlex := 0; // don't change control size anymore + if (ct.inGroup(1-maindir)) then ct.expand := false; // don't expand grouped controls anymore end; - if (not secondAgain) and (not wrappingChanged) then break; - firstTime := false; + if (not secondAgain) then break; end; end; -(* -fourth pass: - set 'actualsize' and 'actualpos' to 'desiredsize' and 'desiredpos' - return -*) -procedure TFlexLayouterBase.fourthPass (); +procedure TFlexLayouterBase.thirdPass (); var f: Integer; begin @@ -820,10 +673,13 @@ end; procedure TFlexLayouterBase.layout (); begin + if (Length(ctlist) = 0) then exit; + ctlist[0].desiredpos := TLayPos.Create(0, 0); firstPass(); + //writeln('============== AFTER FIRST PASS =============='); dump(); secondPass(); + //writeln('============== AFTER SECOND PASS =============='); dump(); thirdPass(); - fourthPass(); end; @@ -853,7 +709,10 @@ begin begin lc := @ctlist[cidx]; for f := 0 to indent do write(' '); - writeln(lc.myidx, ': startsize:', lc.startsize.toString, '; desiredsize=', lc.desiredsize.toString, '; maxsize=', lc.maxsize.toString, '; tempFlex=', lc.tempFlex, '; despos=', lc.desiredpos.toString); + if (not lc.visible) then write('!'); + write(lc.myidx, ': '); + if (lc.ctl.id <> '') then write('<', lc.ctl.className, '> {', lc.ctl.id, '} ') else write('<', lc.ctl.className, '> '); + writeln('startsize:', lc.startsize.toString, '; desiredsize=', lc.desiredsize.toString, '; maxsize=', lc.maxsize.toString, '; tempFlex=', lc.tempFlex, '; despos=', lc.desiredpos.toString); dumpList(lc.firstChild, indent+2); cidx := lc.nextSibling; end; @@ -866,36 +725,4 @@ begin end; -// ////////////////////////////////////////////////////////////////////////// // -(* -void main () begin - auto win := new GuiControl(); - (win ~= new GuiControl()).mSize := TLaySize(10, 5); - (win ~= new GuiControl()).mSize := TLaySize(16, 8); - - //win.mSize := TLaySize(40, 20); - - auto lay := TFlexLayouterBase!GuiControl(); - lay.setup(win); - - writeln('============================'); - lay.dumpFlat(); - - writeln('=== initial ==='); - lay.dump(); - - //lay.calcMaxSizeInternal(0); - /* - lay.firstPass(); - writeln('=== after first pass ==='); - lay.dump(); - - lay.secondPass(); - writeln('=== after second pass ==='); - lay.dump(); - */ - lay.layout(); - writeln('=== final ==='); - lay.dump(); -*) end.