DEADSOFTWARE

FlexUI: alot of fixes; Holmes help window now using new FlexUI controls and layouter
[d2df-sdl.git] / src / gx / gh_flexlay.pas
index 79c6a5c578b8fc3a858d34eb5cc966f8a4a691c7..c6da6b316ce79d9f6b24c891a667cc4beff1f2eb 100644 (file)
@@ -1,66 +1,21 @@
+(* 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
@@ -144,6 +99,12 @@ type
         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;
@@ -175,7 +136,7 @@ type
     // 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);
@@ -433,6 +400,7 @@ var
   lc, c: PLayControl;
   msz: TLaySize;
   negw, negh: Boolean;
+  zerow: Boolean;
   curwdt, curhgt, totalhgt: Integer;
   doWrap: Boolean;
 begin
@@ -440,8 +408,9 @@ 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;
 
@@ -452,14 +421,14 @@ begin
     // 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;
@@ -470,7 +439,9 @@ begin
       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
@@ -488,9 +459,14 @@ begin
   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;
 
 
@@ -512,11 +488,12 @@ begin
     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
@@ -549,13 +526,14 @@ begin
   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;
@@ -565,12 +543,13 @@ var
 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
@@ -599,9 +578,9 @@ begin
       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;
@@ -613,6 +592,7 @@ begin
   flexTotal := 0;
   flexBoxCount := 0;
   spaceLeft := me.desiredsize.w-me.margins.horiz;
+  cury += lineh;
 end;
 
 
@@ -624,7 +604,7 @@ var
   flexBoxCount: Integer; // number of boxes
   spaceLeft: Single;
   cury: Integer;
-  maxwdt, maxhgt: Integer;
+  maxwdt: Integer;
   lineStartIdx: LayControlIdx;
   lc: PLayControl;
   doWrap: Boolean;
@@ -635,125 +615,102 @@ begin
   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
@@ -771,17 +728,6 @@ begin
 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;
@@ -834,6 +780,7 @@ 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;
@@ -846,6 +793,7 @@ begin
             secondAgain := true;
           end;
         end;
+        *)
       end;
     end;
     if (not secondAgain) and (not wrappingChanged) then break;