From f9becfd79bc5b789311f7167304d426daed71577 Mon Sep 17 00:00:00 2001 From: Ketmar Dark Date: Mon, 2 Oct 2017 21:47:02 +0300 Subject: [PATCH] FlexUI: "padding" property for boxes, so i don't have to insert dummy spans everywhere padding is inserted *after* control, if there is another control coming, and current control doesn't have "nopad" property set note that span control has no padding by default, and box with frame has no padding too --- src/flexui/fui_ctls.pas | 89 +++++++++++++++++++++++++++----------- src/flexui/fui_flexlay.pas | 72 +++++++++++++++++++++--------- 2 files changed, 114 insertions(+), 47 deletions(-) diff --git a/src/flexui/fui_ctls.pas b/src/flexui/fui_ctls.pas index 8c3834e..b7d5bfa 100644 --- a/src/flexui/fui_ctls.pas +++ b/src/flexui/fui_ctls.pas @@ -138,29 +138,26 @@ type 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; @@ -170,18 +167,23 @@ type 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; @@ -897,6 +899,8 @@ begin //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; @@ -1000,26 +1004,17 @@ end; // ////////////////////////////////////////////////////////////////////////// // 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 @@ -1067,6 +1062,37 @@ 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 := @@ -1244,6 +1270,9 @@ begin 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; @@ -2347,12 +2376,19 @@ procedure TUIBox.setHasFrame (v: Boolean); 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)); @@ -2478,6 +2514,7 @@ begin inherited; mExpand := true; mCanFocus := false; + mNoPad := true; mCtl4Style := 'span'; end; diff --git a/src/flexui/fui_flexlay.pas b/src/flexui/fui_flexlay.pas index 99db4fe..fb470c4 100644 --- a/src/flexui/fui_flexlay.pas +++ b/src/flexui/fui_flexlay.pas @@ -23,10 +23,12 @@ 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 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 @@ -58,11 +60,12 @@ type 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 @@ -71,12 +74,13 @@ 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; @@ -95,6 +99,7 @@ type 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; @@ -136,7 +141,7 @@ type // 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); @@ -201,6 +206,7 @@ function TFlexLayouterBase.TLayControl.lineStart (): Boolean; inline; begin resu 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; @@ -293,6 +299,7 @@ begin 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; @@ -403,6 +410,9 @@ var 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; @@ -423,21 +433,27 @@ begin 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; @@ -450,10 +466,13 @@ begin // 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; @@ -479,17 +498,14 @@ var 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` @@ -530,7 +546,7 @@ begin 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 @@ -540,7 +556,9 @@ var 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 @@ -548,11 +566,12 @@ begin 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 @@ -562,6 +581,7 @@ 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 @@ -610,6 +630,8 @@ var 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]; @@ -625,33 +647,39 @@ begin 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 @@ -660,6 +688,7 @@ 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 @@ -679,6 +708,7 @@ begin 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 -- 2.29.2