diff --git a/src/gx/gh_flexlay.pas b/src/gx/gh_flexlay.pas
index 79c6a5c578b8fc3a858d34eb5cc966f8a4a691c7..c6da6b316ce79d9f6b24c891a667cc4beff1f2eb 100644 (file)
--- a/src/gx/gh_flexlay.pas
+++ b/src/gx/gh_flexlay.pas
+(* coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
+ * Understanding is not required. Only obedience.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *)
{$INCLUDE ../shared/a_modes.inc}
unit gh_flexlay;
-
-(*
-first pass:
- set all 'temp-flex' flags for controls to 'flex'
- reset all 'laywrap' flags for controls
- build group arrays; for each group: find max size for group, adjust 'startsize' controls to group max size
- call 'calc max size' for top-level control
- flags set:
- 'firsttime'
-
-second pass:
- calcluate desired sizes (process flexes) using 'startsize', set 'desiredsize' and 'desiredpos'
- if control has children, call 'second pass' recursively with this control as parent
- flags set:
- 'group-element-changed', if any group element size was changed
- 'wrapping-changed', if not 'firsttime', and wrapping was changed (i.e. first pass will not set the flag)
-
-third pass:
- if 'group-element-changed':
- for each group: adjust controls to max desired size (startsize), set 'temp-flex' flags to 0 for 'em, set 'second-again' flag
- for other controls: if 'desiredsize' > 'maxsize', set 'startsize' to 'maxsize', set 'temp-flex' flag to 0, set 'second-again' flag
- if 'second-again' or 'wrapping-changed':
- reset 'second-again'
- reset 'wrapping-changed'
- reset 'firsttime'
- goto second pass
-
-fourth pass:
- set 'actualsize' and 'actualpos' to 'desiredsize' and 'desiredpos'
- return
-
-calc max size:
- set 'startsize' to max(size, maxsize, 0)
- if 'size' is negative:
- set 'temp-flex' flag to 0
- if has children:
- call 'calc max size' for each child
- set 'desiredmax' to 'startsize'
- do lines, don't distribute space (i.e. calc only wrapping),
- for each complete line, set 'desiredmax' to max(desiredmax, linesize)
- if 'maxsize' >= 0:
- set 'desiredmax' to min(desiredmax, maxsize)
- set 'startsize' to 'desiredmax'
- return
-
-
-wrapping lines:
- try to stuff controls in line until line width is less or equal to maxsize
- distribute flex for filled line
- continue until we still has something to stuff
-
-
-for wrapping:
- we'll hold 'laywrap' flag for each control; it will be set if this control
- starts a new line (even if this is the first control in line, as it is obviously
- starts a new line)
-
- on redoing second pass, if 'laywrap' flag changed, set 'wrapping-changed' flag
-*)
-
-
(*
control default size will be increased by margins
negative margins are ignored
function getExpand (): Boolean; inline;
procedure setExpand (v: Boolean); inline;
+ function alignLeft (): Boolean; inline;
+ function alignTop (): Boolean; inline;
+ function alignRight (): Boolean; inline;
+ function alignBottom (): Boolean; inline;
+ function alignCenter (): Boolean; inline;
+
public
property didWrap: Boolean read getDidWrap write setDidWrap;
property expand: Boolean read getExpand write setExpand;
// this also sets `tempFlex`
procedure calcMaxSizeInternal (cidx: LayControlIdx);
- procedure fixLine (me: PLayControl; i0, i1: LayControlIdx; cury: Integer; var spaceLeft: Single; var flexTotal: Integer; var flexBoxCount: Integer);
+ procedure fixLine (me: PLayControl; i0, i1: LayControlIdx; var cury: Integer; var spaceLeft: Single);
// do box layouting; call `layBox()` recursively if necessary
procedure layBox (boxidx: LayControlIdx);
@@ -247,6 +208,12 @@ procedure TFlexLayouterBase.TLayControl.setDidWrap (v: Boolean); inline; begin i
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;
+function TFlexLayouterBase.TLayControl.alignLeft (): Boolean; inline; begin result := (aligndir < 0); end;
+function TFlexLayouterBase.TLayControl.alignTop (): Boolean; inline; begin result := (aligndir < 0); end;
+function TFlexLayouterBase.TLayControl.alignRight (): Boolean; inline; begin result := (aligndir > 0); end;
+function TFlexLayouterBase.TLayControl.alignBottom (): Boolean; inline; begin result := (aligndir > 0); end;
+function TFlexLayouterBase.TLayControl.alignCenter (): Boolean; inline; begin result := (aligndir = 0); end;
+
// ////////////////////////////////////////////////////////////////////////// //
constructor TFlexLayouterBase.TChildrenEnumerator.Create (constref actls: TLayCtlArray; acur: Integer);
lc, c: PLayControl;
msz: TLaySize;
negw, negh: Boolean;
+ zerow: Boolean;
curwdt, curhgt, totalhgt: Integer;
doWrap: Boolean;
begin
lc := @ctlist[cidx];
msz := lc.ctl.getMaxSize;
- negw := (lc.startsize.w <= 0);
- negh := (lc.startsize.h <= 0);
+ negw := (lc.startsize.w < 0);
+ negh := (lc.startsize.h < 0);
+ zerow := (lc.startsize.w = 0);
lc.tempFlex := lc.ctl.getFlex;
// horizontal boxes
if (negw) then lc.tempFlex := 0; // size is negative: don't expand
curwdt := lc.margins.horiz;
- curhgt := lc.margins.vert;
- totalhgt := 0;
+ 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 (lc.canWrap) and (c.canWrap) and (msz.w > 0) and (curwdt+c.startsize.w > lc.startsize.w) then doWrap := true;
+ 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;
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
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;
- if (lc.maxsize.w < lc.startsize.w) then lc.maxsize.w := lc.startsize.w;
- if (lc.maxsize.h < lc.startsize.h) then lc.maxsize.h := lc.startsize.h;
end;
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;
+ //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;
for gtype := 0 to 1 do
for f := 0 to High(ctlist) do ctlist[f].desiredsize := ctlist[f].startsize;
// set flags
firstTime := true;
- //writeln('=== calculated max size ===');
- //dump();
+ //writeln('=== calculated max size (final) ==='); dump();
end;
-procedure TFlexLayouterBase.fixLine (me: PLayControl; i0, i1: LayControlIdx; cury: Integer; var spaceLeft: Single; var flexTotal: Integer; var flexBoxCount: Integer);
+procedure TFlexLayouterBase.fixLine (me: PLayControl; i0, i1: LayControlIdx; var cury: Integer; var spaceLeft: Single);
var
+ flexTotal: Integer = 0; // total sum of flex fields
+ flexBoxCount: Integer = 0; // number of boxes
curx: Integer;
lc: PLayControl;
osz: TLaySize;
begin
curx := me.margins.left;
sti0 := i0;
- // calc minimal line height
+ // calc minimal line height, count flexboxes
lineh := 0;
while (i0 <> i1) do
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;
end;
// distribute space, expand/align
end;
end;
// expand or align
- if (lc.expand) then lc.desiredsize.h := nmin(lc.maxsize.h, lineh) // expand
- else if (lc.aligndir > 0) then lc.desiredpos.y := cury+(lineh-lc.desiredsize.h) // bottom align
- else if (lc.aligndir = 0) then lc.desiredpos.y := cury+(lineh-lc.desiredsize.h) div 2; // center
+ 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
if (not osz.equals(lc.desiredsize)) then
begin
if (lc.inGroup) then groupElementChanged := true;
flexTotal := 0;
flexBoxCount := 0;
spaceLeft := me.desiredsize.w-me.margins.horiz;
+ cury += lineh;
end;
flexBoxCount: Integer; // number of boxes
spaceLeft: Single;
cury: Integer;
- maxwdt, maxhgt: Integer;
+ maxwdt: Integer;
lineStartIdx: LayControlIdx;
lc: PLayControl;
doWrap: Boolean;
me := @ctlist[boxidx];
// if we have no children, there's nothing to do
- if (me.firstChild = -1) then exit;
-
- // first, layout all children
- for lc in forChildren(boxidx) do layBox(lc.myidx);
-
- // second, layout lines, distribute flex data
- if (me.horizBox) then
+ if (me.firstChild <> -1) then
begin
- // horizontal boxes
- cury := me.margins.top;
- maxhgt := 0;
+ // first, layout all children
+ for lc in forChildren(boxidx) do layBox(lc.myidx);
- fixLine(me, -1, -1, cury, spaceLeft, flexTotal, flexBoxCount); //HACK!
+ // second, layout lines, distribute flex data
+ if (me.horizBox) then
+ begin
+ // horizontal boxes
+ cury := me.margins.top;
- lineStartIdx := me.firstChild;
+ fixLine(me, -1, -1, cury, spaceLeft); //HACK!
- 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
+ lineStartIdx := me.firstChild;
+ for lc in forChildren(boxidx) do
begin
- // new line, fix this one
- if (not lc.didWrap) then
+ // 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
- wrappingChanged := true;
- lc.didWrap := true;
- end;
- fixLine(me, lineStartIdx, lc.myidx, cury, spaceLeft, flexTotal, flexBoxCount);
- cury += maxhgt;
- lineStartIdx := lc.myidx;
- end
- else
- begin
- if (lc.didWrap) then
+ // 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
- wrappingChanged := true;
- lc.didWrap := false;
+ 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;
- 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, flexTotal, flexBoxCount);
- end
- else
- begin
- // vertical boxes
- maxwdt := 0;
- flexTotal := 0;
- flexBoxCount := 0;
- spaceLeft := me.desiredsize.h-me.margins.vert;
-
- // calc flex
- for lc in forChildren(boxidx) do
+ // fix last line
+ fixLine(me, lineStartIdx, -1, cury, spaceLeft);
+ end
+ else
begin
- spaceLeft -= lc.desiredsize.h;
- if (maxwdt < lc.desiredsize.w) then maxwdt := lc.desiredsize.w;
- if (lc.tempFlex > 0) then
+ // vertical boxes
+ maxwdt := 0;
+ flexTotal := 0;
+ flexBoxCount := 0;
+ spaceLeft := me.desiredsize.h-me.margins.vert;
+
+ // calc flex
+ for lc in forChildren(boxidx) do
begin
- flexTotal += lc.tempFlex;
- flexBoxCount += 1;
+ 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;
- 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
+ // distribute space
+ cury := me.margins.top;
+ //writeln('me: ', boxidx, '; margins: ', me.margins.toString);
+ for lc in forChildren(boxidx) do
begin
- toadd := trunc(spaceLeft*lc.tempFlex/flexTotal+0.5);
- if (toadd > 0) then
+ 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
- // 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;
+ 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;
- // expand or align
- if (lc.expand) then lc.desiredsize.w := nmin(lc.maxsize.w, me.desiredsize.w-me.margins.vert) // expand
- else if (lc.aligndir > 0) then lc.desiredpos.x := me.desiredsize.w-me.margins.right-lc.desiredsize.w // right align
- else if (lc.aligndir = 0) 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;
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;
-(*
-second pass:
- calcluate desired sizes (process flexes) using 'startsize', set 'desiredsize' and 'desiredpos'
- if control has children, call 'second pass' recursively with this control as parent
- flags set:
- 'group-element-changed', if any group element size was changed
- 'wrapping-changed', if not 'firsttime', and wrapping was changed (i.e. first pass will not set the flag)
-*)
procedure TFlexLayouterBase.secondPass ();
begin
// reset flags
end;
-(*
-third pass:
- if 'group-element-changed':
- for each group: adjust controls to max desired size (startsize), set 'temp-flex' flags to 0 for 'em, set 'second-again' flag
- for other controls: if 'desiredsize' > 'maxsize', set 'startsize' to 'maxsize', set 'temp-flex' flag to 0, set 'second-again' flag
- if 'second-again' or 'wrapping-changed':
- reset 'second-again'
- reset 'wrapping-changed'
- reset 'firsttime'
- goto second pass
-*)
procedure TFlexLayouterBase.thirdPass ();
var
secondAgain: Boolean;
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;
secondAgain := true;
end;
end;
+ *)
end;
end;
if (not secondAgain) and (not wrappingChanged) then break;