mLayDefSize: TLaySize;
mLayMaxSize: TLaySize;
mFullSize: TLaySize;
+ mNoPad: Boolean;
+ mPadding: TLaySize;
public
// layouter interface
function getDefSize (): TLaySize; inline; // default size; <0: use max size
//procedure setDefSize (const sz: TLaySize); inline; // default size; <0: use max size
function getMargins (): TLayMargins; inline;
+ function getPadding (): TLaySize; inline; // children padding (each non-first child will get this on left/top)
function getMaxSize (): TLaySize; inline; // max size; <0: set to some huge value
//procedure setMaxSize (const sz: TLaySize); inline; // max size; <0: set to some huge value
function getFlex (): Integer; inline; // <=0: not flexible
function isHorizBox (): Boolean; inline; // horizontal layout for children?
- procedure setHorizBox (v: Boolean); inline; // horizontal layout for children?
function canWrap (): Boolean; inline; // for horizontal boxes: can wrap children? for child: `false` means 'nonbreakable at *next* ctl'
- procedure setCanWrap (v: Boolean); inline; // for horizontal boxes: can wrap children? for child: `false` means 'nonbreakable at *next* ctl'
+ function noPad (): Boolean; inline; // ignore padding in box direction for this control
function isLineStart (): Boolean; inline; // `true` if this ctl should start a new line; ignored for vertical boxes
- procedure setLineStart (v: Boolean); inline; // `true` if this ctl should start a new line; ignored for vertical boxes
function getAlign (): Integer; inline; // aligning in non-main direction: <0: left/up; 0: center; >0: right/down
- procedure setAlign (v: Integer); inline; // aligning in non-main direction: <0: left/up; 0: center; >0: right/down
function getExpand (): Boolean; inline; // expanding in non-main direction: `true` will ignore align and eat all available space
- procedure setExpand (v: Boolean); inline; // expanding in non-main direction: `true` will ignore align and eat all available space
function getHGroup (): AnsiString; inline; // empty: not grouped
- procedure setHGroup (const v: AnsiString); inline; // empty: not grouped
function getVGroup (): AnsiString; inline; // empty: not grouped
- procedure setVGroup (const v: AnsiString); inline; // empty: not grouped
procedure setActualSizePos (constref apos: TLayPos; constref asize: TLaySize); inline;
property flex: Integer read mFlex write mFlex;
property flDefaultSize: TLaySize read mDefSize write mDefSize;
property flMaxSize: TLaySize read mMaxSize write mMaxSize;
- property flHoriz: Boolean read isHorizBox write setHorizBox;
- property flCanWrap: Boolean read canWrap write setCanWrap;
- property flLineStart: Boolean read isLineStart write setLineStart;
- property flAlign: Integer read getAlign write setAlign;
- property flExpand: Boolean read getExpand write setExpand;
- property flHGroup: AnsiString read getHGroup write setHGroup;
- property flVGroup: AnsiString read getVGroup write setVGroup;
+ property flPadding: TLaySize read mPadding write mPadding;
+ property flHoriz: Boolean read mHoriz write mHoriz;
+ property flCanWrap: Boolean read mCanWrap write mCanWrap;
+ property flLineStart: Boolean read mLineStart write mLineStart;
+ property flAlign: Integer read mAlign write mAlign;
+ property flExpand: Boolean read mExpand write mExpand;
+ property flHGroup: AnsiString read mHGroup write mHGroup;
+ property flVGroup: AnsiString read mVGroup write mVGroup;
+ property flNoPad: Boolean read mNoPad write mNoPad;
property fullSize: TLaySize read mFullSize;
protected
function parsePos (par: TTextParser): TLayPos;
function parseSize (par: TTextParser): TLaySize;
+ function parsePadding (par: TTextParser): TLaySize;
+ function parseHPadding (par: TTextParser; def: Integer): TLaySize;
+ function parseVPadding (par: TTextParser; def: Integer): TLaySize;
function parseBool (par: TTextParser): Boolean;
function parseAnyAlign (par: TTextParser): Integer;
function parseHAlign (par: TTextParser): Integer;
//mDefSize := TLaySize.Create(64, 8); // default size
mDefSize := TLaySize.Create(0, 0); // default size
mMaxSize := TLaySize.Create(-1, -1); // maximum size
+ mPadding := TLaySize.Create(0, 0);
+ mNoPad := false;
mFlex := 0;
mHoriz := true;
mCanWrap := false;
// ////////////////////////////////////////////////////////////////////////// //
function TUIControl.getDefSize (): TLaySize; inline; begin result := mLayDefSize; end;
function TUIControl.getMaxSize (): TLaySize; inline; begin result := mLayMaxSize; end;
+function TUIControl.getPadding (): TLaySize; inline; begin result := mPadding; end;
function TUIControl.getFlex (): Integer; inline; begin result := mFlex; end;
function TUIControl.isHorizBox (): Boolean; inline; begin result := mHoriz; end;
-procedure TUIControl.setHorizBox (v: Boolean); inline; begin mHoriz := v; end;
function TUIControl.canWrap (): Boolean; inline; begin result := mCanWrap; end;
-procedure TUIControl.setCanWrap (v: Boolean); inline; begin mCanWrap := v; end;
+function TUIControl.noPad (): Boolean; inline; begin result := mNoPad; end;
function TUIControl.isLineStart (): Boolean; inline; begin result := mLineStart; end;
-procedure TUIControl.setLineStart (v: Boolean); inline; begin mLineStart := v; end;
function TUIControl.getAlign (): Integer; inline; begin result := mAlign; end;
-procedure TUIControl.setAlign (v: Integer); inline; begin mAlign := v; end;
function TUIControl.getExpand (): Boolean; inline; begin result := mExpand; end;
-procedure TUIControl.setExpand (v: Boolean); inline; begin mExpand := v; end;
function TUIControl.getHGroup (): AnsiString; inline; begin result := mHGroup; end;
-procedure TUIControl.setHGroup (const v: AnsiString); inline; begin mHGroup := v; end;
function TUIControl.getVGroup (): AnsiString; inline; begin result := mVGroup; end;
-procedure TUIControl.setVGroup (const v: AnsiString); inline; begin mVGroup := v; end;
-
-function TUIControl.getMargins (): TLayMargins; inline;
-begin
- result := TLayMargins.Create(mFrameHeight, mFrameWidth, mFrameHeight, mFrameWidth);
-end;
+function TUIControl.getMargins (): TLayMargins; inline; begin result := TLayMargins.Create(mFrameHeight, mFrameWidth, mFrameHeight, mFrameWidth); end;
procedure TUIControl.setActualSizePos (constref apos: TLayPos; constref asize: TLaySize); inline;
begin
par.expectDelim(ech);
end;
+function TUIControl.parsePadding (par: TTextParser): TLaySize;
+begin
+ result := parseSize(par);
+end;
+
+function TUIControl.parseHPadding (par: TTextParser; def: Integer): TLaySize;
+begin
+ if (par.isInt) then
+ begin
+ result.h := def;
+ result.w := par.expectInt();
+ end
+ else
+ begin
+ result := parsePadding(par);
+ end;
+end;
+
+function TUIControl.parseVPadding (par: TTextParser; def: Integer): TLaySize;
+begin
+ if (par.isInt) then
+ begin
+ result.w := def;
+ result.h := par.expectInt();
+ end
+ else
+ begin
+ result := parsePadding(par);
+ end;
+end;
+
function TUIControl.parseBool (par: TTextParser): Boolean;
begin
result :=
if (strEquCI1251(prname, 'defheight')) or (strEquCI1251(prname, 'height')) then begin mDefSize.h := par.expectInt(); exit; end;
if (strEquCI1251(prname, 'maxwidth')) then begin mMaxSize.w := par.expectInt(); exit; end;
if (strEquCI1251(prname, 'maxheight')) then begin mMaxSize.h := par.expectInt(); exit; end;
+ // padding
+ if (strEquCI1251(prname, 'padding')) then begin mPadding := parsePadding(par); exit; end;
+ if (strEquCI1251(prname, 'nopad')) then begin mNoPad := true; exit; end;
// flags
if (strEquCI1251(prname, 'wrap')) then begin mCanWrap := parseBool(par); exit; end;
if (strEquCI1251(prname, 'linestart')) then begin mLineStart := parseBool(par); exit; end;
begin
mHasFrame := v;
if (mHasFrame) then begin mFrameWidth := 8; mFrameHeight := 8; end else begin mFrameWidth := 0; mFrameHeight := 0; end;
+ if (mHasFrame) then mNoPad := true;
end;
function TUIBox.parseProperty (const prname: AnsiString; par: TTextParser): Boolean;
begin
if (parseOrientation(prname, par)) then begin result := true; exit; end;
+ if (strEquCI1251(prname, 'padding')) then
+ begin
+ if (mHoriz) then mPadding := parseHPadding(par, 0) else mPadding := parseVPadding(par, 0);
+ result := true;
+ exit;
+ end;
if (strEquCI1251(prname, 'frame')) then
begin
setHasFrame(parseBool(par));
inherited;
mExpand := true;
mCanFocus := false;
+ mNoPad := true;
mCtl4Style := 'span';
end;
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 noPad (): Boolean; // ignore padding in box direction for this control
function isLineStart (): Boolean; // `true` if this ctl should start a new line; ignored for vertical boxes
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
FlagHorizBox = LongWord(1) shl 0; // horizontal layout for children
FlagLineStart = LongWord(1) shl 1;
FlagLineCanWrap = LongWord(1) shl 2;
+ FlagNoPad = LongWord(1) shl 3;
// 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;
+ FlagLineDidWrap = LongWord(1) shl 8; // will be set when line was wrapped
+ FlagInGroup = LongWord(1) shl 9; // set if this control is a member of any group
+ FlagExpand = LongWord(1) shl 10;
+ FlagLineFirst = LongWord(1) shl 11;
private
type
public
myidx: LayControlIdx;
tempFlex: Integer;
- flags: LongWord; // see below
+ flags: LongWord; // see above
aligndir: Integer;
startsize: TLaySize; // current
desiredsize: TLaySize;
maxsize: TLaySize;
margins: TLayMargins; // can never be negative
+ padding: TLaySize;
desiredpos: TLayPos;
ctl: ControlT;
parent: LayControlIdx; // = -1;
function canWrap (): Boolean; inline;
function inGroup (): Boolean; inline;
function firstInLine (): Boolean; inline;
+ function noPad (): Boolean; inline;
function getExpand (): Boolean; inline;
procedure setExpand (v: Boolean); inline;
// this also sets `tempFlex`
procedure calcMaxSizeInternal (cidx: LayControlIdx);
- procedure fixLine (me: PLayControl; i0, i1: LayControlIdx; var cury: Integer; var spaceLeft: Single);
+ procedure fixLine (me: PLayControl; i0, i1: LayControlIdx; ypad: Integer; var cury: Integer; var spaceLeft: Single);
// do box layouting; call `layBox()` recursively if necessary
procedure layBox (boxidx: LayControlIdx);
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.noPad (): Boolean; inline; begin result := ((flags and FlagNoPad) <> 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;
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;
+ if (lc.ctl.noPad) then lc.flags := lc.flags or FlagNoPad;
lc.aligndir := lc.ctl.getAlign;
end;
zerow: Boolean;
curwdt, curhgt, totalhgt: Integer;
doWrap: Boolean;
+ xpad, ypad: Integer;
+ realpad: Integer;
+ dopad: Boolean = false;
begin
if (cidx < 0) or (cidx >= Length(ctlist)) then exit;
curwdt := lc.margins.horiz;
curhgt := 0;
totalhgt := lc.margins.vert;
+ xpad := nmax(0, lc.padding.w);
+ ypad := 0;
for c in forChildren(cidx) do
begin
+ if (dopad) then realpad := xpad else realpad := 0;
// 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 (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;
if (doWrap) then
begin
totalhgt += curhgt;
if (lc.startsize.w < curwdt) then lc.startsize.w := curwdt;
curwdt := 0;
curhgt := 0;
+ ypad := nmax(0, lc.padding.h);
+ realpad := 0;
end;
- curwdt += c.startsize.w;
- if (curhgt < c.startsize.h) then curhgt := c.startsize.h;
+ curwdt += c.startsize.w+realpad;
+ if (curhgt < c.startsize.h+ypad) then curhgt := c.startsize.h+ypad;
+ dopad := (xpad > 0) and (not lc.noPad);
end;
//writeln('00: ', cidx, ': totalhgt=', totalhgt);
totalhgt += curhgt;
// vertical boxes
if (negh) then lc.tempFlex := 0; // size is negative: don't expand
curhgt := lc.margins.vert;
+ ypad := nmax(0, lc.padding.h);
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;
+ if (dopad) then curhgt += ypad;
+ dopad := (not c.noPad);
end;
if (lc.startsize.h < curhgt) then lc.startsize.h := curhgt;
end;
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;
+ ctlist[f].margins := ctlist[f].ctl.getMargins;
+ ctlist[f].padding := ctlist[f].ctl.getPadding;
end;
// setup sizes
calcMaxSizeInternal(0); // this also sets `tempFlex`
end;
-procedure TFlexLayouterBase.fixLine (me: PLayControl; i0, i1: LayControlIdx; var cury: Integer; var spaceLeft: Single);
+procedure TFlexLayouterBase.fixLine (me: PLayControl; i0, i1: LayControlIdx; ypad: Integer; var cury: Integer; var spaceLeft: Single);
var
flexTotal: Integer = 0; // total sum of flex fields
flexBoxCount: Integer = 0; // number of boxes
toadd: Integer;
sti0: Integer;
lineh: Integer;
+ xpad: Integer;
begin
+ if (ypad < 0) then ypad := 0;
curx := me.margins.left;
sti0 := i0;
// calc minimal line height, count flexboxes
while (i0 <> i1) do
begin
lc := @ctlist[i0];
- lineh := nmax(lineh, lc.startsize.h);
+ lineh := nmax(lineh, lc.startsize.h+ypad);
if (lc.tempFlex > 0) then begin flexTotal += lc.tempFlex; flexBoxCount += 1; end;
i0 := lc.nextSibling;
end;
// distribute space, expand/align
+ xpad := nmax(0, me.padding.w);
i0 := sti0;
while (i0 <> i1) do
begin
lc.desiredpos.x := curx;
lc.desiredpos.y := cury;
curx += lc.desiredsize.w;
+ if (xpad > 0) and (not lc.noPad) then curx += xpad;
// fix flexbox size
if (lc.tempFlex > 0) and (spaceLeft > 0) then
begin
doWrap: Boolean;
toadd: Integer;
osz: TLaySize;
+ xpad, ypad, realpad: Integer;
+ dopad: Boolean = false;
begin
if (boxidx < 0) or (boxidx >= Length(ctlist)) then exit;
me := @ctlist[boxidx];
begin
// horizontal boxes
cury := me.margins.top;
+ xpad := nmax(0, me.padding.w);
+ ypad := 0;
- fixLine(me, -1, -1, cury, spaceLeft); //HACK!
+ fixLine(me, -1, -1, 0, cury, spaceLeft); //HACK!
lineStartIdx := me.firstChild;
for lc in forChildren(boxidx) do
begin
+ if (dopad) then realpad := xpad else realpad := 0;
// 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 (not doWrap) and (lc.canWrap) and (lc.canWrap) and (lc.desiredsize.w > 0) and (spaceLeft-realpad < 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);
+ fixLine(me, lineStartIdx, lc.myidx, ypad, cury, spaceLeft);
lineStartIdx := lc.myidx;
+ ypad := nmax(0, me.padding.h);
+ realpad := 0;
end
else
begin
if (lc.didWrap) then begin wrappingChanged := true; lc.didWrap := false; end;
end;
- spaceLeft -= lc.desiredsize.w;
+ spaceLeft -= lc.desiredsize.w+realpad;
+ dopad := (xpad > 0) and (not lc.noPad);
//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);
+ fixLine(me, lineStartIdx, -1, ypad, cury, spaceLeft);
end
else
begin
flexTotal := 0;
flexBoxCount := 0;
spaceLeft := me.desiredsize.h-me.margins.vert;
+ ypad := nmax(0, me.padding.h);
// calc flex
for lc in forChildren(boxidx) do
lc.desiredpos.x := me.margins.left;
lc.desiredpos.y := cury;
cury += lc.desiredsize.h;
+ if (ypad > 0) and (not lc.noPad) then cury += ypad;
// fix flexbox size
if (lc.tempFlex > 0) and (spaceLeft > 0) then
begin