737 lines
24 KiB
ObjectPascal

// SPDX-License-Identifier: LGPL-3.0-linking-exception
{ General framework methods for rendering background, borders, text, etc.
originally written in 2012 by Krzysztof Dibowski dibowski at interia.pl
}
{******************************* CONTRIBUTOR(S) ******************************
- Edivando S. Santos Brasil | mailedivando@gmail.com
(Compatibility with delphi VCL 11/2018)
***************************** END CONTRIBUTOR(S) *****************************}
unit BCTools;
{$I bgracontrols.inc}
interface
uses
Classes, SysUtils, Types, Graphics,
{$IFDEF FPC}LCLType, LCLIntf,{$ENDIF} {$IFNDEF FPC}BGRAGraphics, GraphType, FPImage, {$ENDIF}
BGRABitmap, BGRABitmapTypes, bctypes, Controls, BGRAGradientScanner;
function ScaleRect(ARect: TRect; AScale: Single): TRect;
// This method prepare BGRABitmap for rendering BCFont type
procedure AssignBCFont(AFont: TBCFont; var ATargetBGRA: TBGRABitmap);
// Calculate text height and width (doesn't include wordwrap - just single line)
procedure CalculateTextSize(const AText: String; AFont: TBCFont; out ANewWidth, ANewHeight: integer;
AShadowMargin: boolean = true);
// Calculate text height and width (handles wordwrap and end ellipsis)
procedure CalculateTextSizeEx(const AText: String; AFont: TBCFont; out ANewWidth, ANewHeight: integer;
AAvailableWidth: integer; AShadowMargin: boolean = false);
// Determines the layout of the glyph
procedure GetGlyphActualLayout(ACaption: string; AFont: TBCFont;
AGlyphAlignment: TBCAlignment; AGlyphMargin: integer; out AHorizAlign: TAlignment;
out AVertAlign: TTextLayout; out AGlyphRelativeHorizAlign: TAlignment;
out AGlyphRelativeVertAlign: TTextLayout; out AGlyphHorizMargin: integer;
out AGlyphVertMargin: integer);
// Computes the position the glyph and update rAvail with the space dedicated to text.
// Specify the flag AOldPlacement to have the old (buggy) version
function ComputeGlyphPosition(var rAvail: TRect;
AGlyph: TBitmap; AGlyphAlignment: TBCAlignment; AGlyphMargin: integer;
ACaption: string; AFont: TBCFont; AOldPlacement: boolean = false;
AGlyphScale: Single = 1): TRect; overload;
function ComputeGlyphPosition(var rAvail: TRect;
gw, gh: integer; AGlyphAlignment: TBCAlignment; AGlyphMargin: integer;
ACaption: string; AFont: TBCFont; AOldPlacement: boolean = false): TRect; overload;
// This method correct TRect to border width. As far as border width is bigger,
// BGRA drawing rectangle with offset (half border width)
procedure CalculateBorderRect(ABorder: TBCBorder; var ARect: TRect);
// This returns a rectangle that is inside the border outline
procedure CalculateInnerRect(ABorder: TBCBorder; var ARect: TRect);
// Create BGRA Gradient Scanner based on BCGradient properties
function CreateGradient(AGradient: TBCGradient; ARect: TRect): TBGRAGradientScanner;
// Render arrow (used by BCButton with DropDownMenu style)
procedure RenderArrow(ATargetBGRA: TBGRABitmap; const ARect: TRect;
ASize: Integer; ADirection: TBCArrowDirection; AColor: TColor = clBlack;
AOpacity: Byte = 255);
// Render customizable backgroud (used e.g. by TBCButton, TBCPanel, TBCLabel)
procedure RenderBackground(const ARect: TRect; ABackground: TBCBackground;
ATargetBGRA: TBGRABitmap; ARounding: TBCRounding = nil; AHasNoBorder: boolean = false);
procedure RenderBackgroundF(x1,y1,x2,y2: single; ABackground: TBCBackground;
ATargetBGRA: TBGRABitmap; ARounding: TBCRounding = nil);
procedure RenderBackgroundAndBorder(const ARect: TRect; ABackground: TBCBackground;
ATargetBGRA: TBGRABitmap; ARounding: TBCRounding; ABorder: TBCBorder; AInnerMargin: single = 0);
// Render customizable border (used e.g. by TBCButton, TBCPanel, TBCLabel)
procedure RenderBorder(const ARect: TRect; ABorder: TBCBorder;
ATargetBGRA: TBGRABitmap; ARounding: TBCRounding = nil);
procedure RenderBorderF(x1,y1,x2,y2: single; ABorder: TBCBorder;
ATargetBGRA: TBGRABitmap; ARounding: TBCRounding = nil);
// Render BCFont (used e.g. by TBCButton, TBCPanel, TBCLabel)
procedure RenderText(const ARect: TRect; AFont: TBCFont;
const AText: String; ATargetBGRA: TBGRABitmap; AEnabled: boolean);
// Return LCL horizontal equivalent for BCAlignment
function BCAlign2HAlign(AAlign: TBCAlignment): TAlignment;
// Return LCL vertical equivalent for BCAlignment
function BCAlign2VAlign(AAlign: TBCAlignment): TTextLayout;
implementation
uses BGRAPolygon, BGRAFillInfo, BGRAText, math, BGRAUTF8, LazUTF8;
function ComputeGlyphPosition(var rAvail: TRect; AGlyph: TBitmap;
AGlyphAlignment: TBCAlignment; AGlyphMargin: integer; ACaption: string;
AFont: TBCFont; AOldPlacement: boolean; AGlyphScale: Single): TRect;
var gw, gh: integer;
begin
if Assigned(AGlyph) and not AGlyph.Empty then
begin
gw := round(AGlyph.Width * AGlyphScale);
gh := round(AGlyph.Height * AGlyphScale);
end else
begin
gw := 0;
gh := 0;
end;
result := ComputeGlyphPosition(rAvail, gw, gh, AGlyphAlignment, AGlyphMargin, ACaption,
AFont, AOldPlacement);
end;
procedure CalculateBorderRect(ABorder: TBCBorder; var ARect: TRect);
var w: integer;
begin
if ABorder = nil then Exit;
w := ABorder.Width div 2;
Inc(ARect.Left, w);
Inc(ARect.Top, w);
Dec(ARect.Right, w);
Dec(ARect.Bottom, w);
end;
procedure CalculateInnerRect(ABorder: TBCBorder; var ARect: TRect);
var w: integer;
begin
if (ABorder = nil) or (ABorder.Style = bboNone) then Exit;
w := ABorder.Width;
Inc(ARect.Left, w);
Inc(ARect.Top, w);
Dec(ARect.Right, w);
Dec(ARect.Bottom, w);
end;
function CreateGradient(AGradient: TBCGradient; ARect: TRect): TBGRAGradientScanner;
begin
Result := TBGRAGradientScanner.Create(
ColorToBGRA(ColorToRGB(AGradient.StartColor), AGradient.StartColorOpacity),
ColorToBGRA(ColorToRGB(AGradient.EndColor), AGradient.EndColorOpacity),
AGradient.GradientType, PointF(ARect.Left + Round(
((ARect.Right - ARect.Left) / 100) * AGradient.Point1XPercent),
ARect.Top + Round(((ARect.Bottom - ARect.Top) / 100) * AGradient.Point1YPercent)),
PointF(ARect.Left + Round(((ARect.Right - ARect.Left) / 100) *
AGradient.Point2XPercent), ARect.Top + Round(
((ARect.Bottom - ARect.Top) / 100) * AGradient.Point2YPercent)),
AGradient.ColorCorrection, AGradient.Sinus);
end;
procedure RenderBackgroundAndBorder(const ARect: TRect;
ABackground: TBCBackground; ATargetBGRA: TBGRABitmap;
ARounding: TBCRounding; ABorder: TBCBorder; AInnerMargin: single);
var w: single;
begin
if ABorder.Style = bboNone then
begin
w := AInnerMargin-0.5;
RenderBackgroundF(ARect.Left+w, ARect.Top+w, ARect.Right-1-w,
ARect.Bottom-1-w,ABackground,ATargetBGRA,ARounding);
end
else
begin
w := (ABorder.Width-1)/2+AInnerMargin;
RenderBackgroundF(ARect.Left+w,ARect.Top+w,ARect.Right-1-w,ARect.Bottom-1-w,ABackground,ATargetBGRA,ARounding);
RenderBorderF(ARect.Left+w,ARect.Top+w,ARect.Right-1-w,ARect.Bottom-1-w,ABorder,ATargetBGRA,ARounding);
end;
end;
procedure RenderBorder(const ARect: TRect; ABorder: TBCBorder;
ATargetBGRA: TBGRABitmap; ARounding: TBCRounding = nil);
begin
RenderBorderF(ARect.Left,ARect.Top,ARect.Right-1,ARect.Bottom-1,ABorder,
ATargetBGRA,ARounding);
end;
procedure RenderBorderF(x1,y1,x2,y2: single; ABorder: TBCBorder;
ATargetBGRA: TBGRABitmap; ARounding: TBCRounding = nil);
var
fiLight: TFillBorderRoundRectInfo;
rx,ry: Byte;
ropt: TRoundRectangleOptions;
begin
if (x1>x2) or (y1>y2) then exit;
if ABorder.Style=bboNone then Exit;
if ARounding = nil then
begin
rx := 0;
ry := 0;
ropt := [];
end else
begin
rx := ARounding.RoundX;
ry := ARounding.RoundY;
ropt := ARounding.RoundOptions;
end;
ATargetBGRA.RoundRectAntialias(x1,y1,x2,y2,
rx, ry, ColorToBGRA(ColorToRGB(ABorder.Color),ABorder.ColorOpacity),
ABorder.Width, ropt);
if ABorder.LightWidth > 0 then
begin
//compute light position
fiLight := TFillBorderRoundRectInfo.Create(
x1,y1,x2,y2, rx,
ry, ABorder.Width + ABorder.LightWidth, ropt);
//check if there is an inner position
if fiLight.InnerBorder <> nil then
with fiLight.InnerBorder do //fill with light
ATargetBGRA.RoundRectAntialias(topleft.x, topleft.y, bottomright.x,
bottomright.y, radiusx, radiusY,
ColorToBGRA(ColorToRGB(ABorder.LightColor), ABorder.LightOpacity),
ABorder.LightWidth, ropt);
fiLight.Free;
end;
end;
procedure RenderText(const ARect: TRect; AFont: TBCFont;
const AText: String; ATargetBGRA: TBGRABitmap; AEnabled: boolean);
var
shd: TBGRABitmap;
hal: TAlignment;
val: TTextLayout;
st: TTextStyle;
r: TRect;
c: TColor;
begin
if AText = '' then exit;
AssignBCFont(AFont,ATargetBGRA);
hal := BCAlign2HAlign(AFont.TextAlignment);
val := BCAlign2VAlign(AFont.TextAlignment);
FillChar({%H-}st, SizeOf({%H-}st),0);
st.Wordbreak := AFont.WordBreak;
st.Alignment := hal;
st.Layout := val;
st.SingleLine := AFont.SingleLine;
st.EndEllipsis := AFont.EndEllipsis;
r := ARect;
r.Left += AFont.PaddingLeft;
r.Right -= AFont.PaddingRight;
r.Top += AFont.PaddingTop;
r.Bottom -= AFont.PaddingBottom;
if AFont.Shadow then
begin
shd := TBGRABitmap.Create(ATargetBGRA.Width,ATargetBGRA.Height,BGRAPixelTransparent);
shd.FontName := ATargetBGRA.FontName;
shd.FontStyle := ATargetBGRA.FontStyle;
shd.FontQuality := ATargetBGRA.FontQuality;
shd.FontHeight := ATargetBGRA.FontHeight;
shd.TextRect(r, r.Left, r.Top, AText, st, ColorToBGRA(ColorToRGB(AFont.ShadowColor),
AFont.ShadowColorOpacity));
BGRAReplace(shd, shd.FilterBlurRadial(AFont.ShadowRadius, rbFast));
ATargetBGRA.BlendImage(AFont.ShadowOffsetX, AFont.ShadowOffsetY,
shd, boLinearBlend);
shd.Free;
end;
if AEnabled or (AFont.DisabledColor = clNone) then
c := AFont.Color else c := AFont.DisabledColor;
ATargetBGRA.TextRect(r,r.Left,r.Top,AText,st,c);
end;
function BCAlign2HAlign(AAlign: TBCAlignment): TAlignment;
begin
if AAlign in [bcaCenter, bcaCenterTop, bcaCenterBottom] then
Result := taCenter
else if AAlign in [bcaRightCenter, bcaRightTop, bcaRightBottom] then
Result := taRightJustify
else
Result := taLeftJustify;
end;
function BCAlign2VAlign(AAlign: TBCAlignment): TTextLayout;
begin
if AAlign in [bcaCenter, bcaLeftCenter, bcaRightCenter] then
Result := tlCenter
else if AAlign in [bcaCenterBottom, bcaLeftBottom, bcaRightBottom] then
Result := tlBottom
else
Result := tlTop;
end;
function ScaleRect(ARect: TRect; AScale: Single): TRect;
begin
with ARect do
result := rect(round(Left*AScale), round(Top*AScale),
round(Right*AScale), round(Bottom*AScale));
end;
procedure AssignBCFont(AFont: TBCFont; var ATargetBGRA: TBGRABitmap);
var c: TBitmap;
begin
// Canvas is need for calculate font height
c := TBitmap.Create;
c.Canvas.Font.Name := AFont.Name;
c.Canvas.Font.Style := AFont.Style;
case AFont.FontQuality of
fqSystem: c.Canvas.Font.Quality := fqNonAntialiased;
fqFineAntialiasing: c.Canvas.Font.Quality := fqAntialiased;
fqFineClearTypeRGB: c.Canvas.Font.Quality := fqProof;
fqSystemClearType: c.Canvas.Font.Quality := fqCleartype;
end;
// FontAntialias is only backward compability for FontQuality property.
// FontQuality is published in TBCFont so we don't need FontAntialias anymore.
//ATargetBGRA.FontAntialias := AFont.FontAntialias;
{%H-}ATargetBGRA.FontStyle := AFont.Style;
// If font quality is system, then we can leave default values. LCL will
// handle everything (when name is "default" or height 0)
if AFont.FontQuality in [fqSystem,fqSystemClearType] then
begin
ATargetBGRA.FontName := AFont.Name;
ATargetBGRA.FontHeight := AFont.Height;
end
else
begin
// Getting real font name
if SameText(AFont.Name,'default')
then ATargetBGRA.FontName := string(GetFontData(c.Canvas.Font.Handle).Name)
else ATargetBGRA.FontName := AFont.Name;
// Calculate default height, because when font quality is <> fqSystemXXX
// then if height is 0 then it is 0 for real
if (AFont.Height=0) then
ATargetBGRA.FontHeight := -c.Canvas.TextHeight('Bgra')
else
ATargetBGRA.FontHeight := AFont.Height;
end;
ATargetBGRA.FontQuality := AFont.FontQuality;
c.Free;
end;
procedure CalculateTextSize(const AText: String; AFont: TBCFont; out ANewWidth,
ANewHeight: integer; AShadowMargin: boolean);
var
s: TSize;
tmp: TBGRABitmap;
begin
if (AText = '') or (AFont = nil) then
begin
ANewWidth := 0;
ANewHeight := 0;
Exit;
end;
tmp := TBGRABitmap.Create(0,0);
AssignBCFont(AFont, tmp);
s := tmp.TextSize(AText);
tmp.Free;
{ shadow offset }
if AShadowMargin and AFont.Shadow then
begin
Inc(s.cx, 2 * Abs(AFont.ShadowOffsetX) + 2 * AFont.ShadowRadius);
Inc(s.cy, 2 * Abs(AFont.ShadowOffsetY) + 2 * AFont.ShadowRadius);
end;
inc(s.cx, AFont.PaddingLeft+Afont.PaddingRight);
inc(s.cy, AFont.PaddingTop+Afont.PaddingBottom);
ANewWidth := s.cx;
ANewHeight := s.cy;
end;
procedure CalculateTextSizeEx(const AText: String; AFont: TBCFont; out
ANewWidth, ANewHeight: integer; AAvailableWidth: integer; AShadowMargin: boolean);
var
s: TSize;
tmp: TBGRABitmap;
extraX,extraY, fitCount: integer;
dotSize: LongInt;
begin
if (AText = '') or (AFont = nil) then
begin
ANewWidth := 0;
ANewHeight := 0;
Exit;
end;
extraX := 0;
extraY := 0;
{ shadow offset }
if AShadowMargin and AFont.Shadow then
begin
Inc(extraX, 2 * Abs(AFont.ShadowOffsetX) + 2 * AFont.ShadowRadius);
Inc(extraY, 2 * Abs(AFont.ShadowOffsetY) + 2 * AFont.ShadowRadius);
end;
inc(extraX, AFont.PaddingLeft+Afont.PaddingRight);
inc(extraY, AFont.PaddingTop+Afont.PaddingBottom);
dec(AAvailableWidth, extraX);
tmp := TBGRABitmap.Create(0,0);
AssignBCFont(AFont, tmp);
if AFont.WordBreak then
s := tmp.TextSize(AText, AAvailableWidth)
else
begin
s := tmp.TextSize(AText);
if AFont.EndEllipsis and (s.cx > AAvailableWidth) then
begin
dotSize := tmp.TextSize('...').cx;
fitCount := tmp.TextFitInfo(AText, AAvailableWidth-dotSize);
s.cx := tmp.TextSize(UTF8Copy(AText, 1, fitCount)).cx + dotSize;
end;
end;
tmp.Free;
ANewWidth := s.cx+extraX;
ANewHeight := s.cy+extraY;
end;
procedure GetGlyphActualLayout(ACaption: string; AFont: TBCFont;
AGlyphAlignment: TBCAlignment; AGlyphMargin: integer; out AHorizAlign: TAlignment;
out AVertAlign: TTextLayout; out AGlyphRelativeHorizAlign: TAlignment;
out AGlyphRelativeVertAlign: TTextLayout; out AGlyphHorizMargin: integer;
out AGlyphVertMargin: integer);
begin
if AGlyphAlignment in [bcaLeftTop,bcaLeftCenter,bcaLeftBottom] then AHorizAlign := taLeftJustify
else if AGlyphAlignment in [bcaRightTop,bcaRightCenter,bcaRightBottom] then AHorizAlign:= taRightJustify
else AHorizAlign:= taCenter;
if AGlyphAlignment in [bcaCenter,bcaLeftCenter,bcaRightCenter] then AVertAlign := tlCenter
else if AGlyphAlignment in [bcaLeftBottom,bcaCenterBottom,bcaRightBottom] then AVertAlign := tlBottom
else AVertAlign := tlTop;
if ACaption<>'' then
begin
AGlyphRelativeVertAlign:= AVertAlign;
if AVertAlign <> tlCenter then
AGlyphRelativeHorizAlign:= AHorizAlign else
begin
if AHorizAlign = taCenter then
begin
if IsRightToLeftUTF8(ACaption) then AGlyphRelativeHorizAlign := taRightJustify
else AGlyphRelativeHorizAlign := taLeftJustify;
end else
AGlyphRelativeHorizAlign:= AHorizAlign;
end;
if AFont.TextAlignment in [bcaLeftTop,bcaLeftCenter,bcaLeftBottom] then AHorizAlign := taLeftJustify
else if AFont.TextAlignment in [bcaRightTop,bcaRightCenter,bcaRightBottom] then AHorizAlign:= taRightJustify
else AHorizAlign := taCenter;
if AFont.TextAlignment in [bcaLeftTop,bcaCenterTop,bcaRightTop] then AVertAlign := tlTop
else if AFont.TextAlignment in [bcaLeftBottom,bcaCenterBottom,bcaRightBottom] then AVertAlign:= tlBottom
else AVertAlign:= tlCenter;
if AGlyphRelativeVertAlign in[tlTop,tlBottom] then
begin
if AGlyphRelativeHorizAlign <> taCenter then AGlyphHorizMargin:= AGlyphMargin
else AGlyphHorizMargin:= 0;
if AGlyphRelativeVertAlign = AVertAlign then AGlyphVertMargin:= AGlyphMargin
else AGlyphVertMargin:= 0;
end else
begin
AGlyphHorizMargin:= AGlyphMargin;
AGlyphVertMargin:= 0;
end;
end else
begin
case AHorizAlign of
taCenter: AGlyphRelativeHorizAlign:= taCenter;
taRightJustify: AGlyphRelativeHorizAlign:= taLeftJustify;
else AGlyphRelativeHorizAlign:= taRightJustify;
end;
if AHorizAlign <> taCenter then AGlyphHorizMargin := AGlyphMargin
else AGlyphHorizMargin := 0;
case AVertAlign of
tlCenter: AGlyphRelativeVertAlign:= tlCenter;
tlBottom: AGlyphRelativeVertAlign:= tlTop;
else AGlyphRelativeVertAlign:= tlBottom;
end;
if AVertAlign <> tlCenter then AGlyphVertMargin := AGlyphMargin
else AGlyphVertMargin := 0;
end;
end;
function ComputeGlyphPosition(var rAvail: TRect;
gw, gh: integer; AGlyphAlignment: TBCAlignment; AGlyphMargin: integer;
ACaption: string; AFont: TBCFont; AOldPlacement: boolean): TRect;
var
w, h, w2,h2, glyphHorzMargin, glyphVertMargin: integer;
horizAlign, relHorizAlign: TAlignment;
vertAlign, relVertAlign: TTextLayout;
rText, rAll, rGlyph: TRect;
l,t: integer;
procedure AlignRect(var ARect: TRect; const ABounds: TRect; AHorizAlign: TAlignment;
AVertAlign: TTextLayout; AHorizMargin: integer = 0; AVertMargin: integer = 0);
begin
case AHorizAlign of
taCenter: ARect.Offset((ABounds.Left+ABounds.Right - (ARect.Right-ARect.Left)) div 2,0);
taRightJustify: ARect.Offset(ABounds.Right - AHorizMargin - (ARect.Right-ARect.Left),0);
else ARect.Offset(ABounds.Left + AHorizMargin,0);
end;
case AVertAlign of
tlCenter: ARect.Offset(0, (ABounds.Top+ABounds.Bottom - (ARect.Bottom-ARect.Top)) div 2);
tlBottom: ARect.Offset(0, ABounds.Bottom - AVertMargin - (ARect.Bottom-ARect.Top));
else ARect.Offset(0, ABounds.Top + AVertMargin);
end;
end;
begin
if (gw = 0) or (gh = 0) then exit(EmptyRect);
if AOldPlacement then
begin
if ACaption = '' then
begin
w := 0;
h := 0;
end else
CalculateTextSize(ACaption, AFont, w, h);
l := rAvail.Right - Round(((rAvail.Right - rAvail.Left) + w + gw) / 2);
t := rAvail.Bottom - Round(((rAvail.Bottom - rAvail.Top) + gh) / 2);
result := rect(l,t,l+gw,t+gh);
Inc(rAvail.Left, l + gw + AGlyphMargin);
exit;
end;
GetGlyphActualLayout(ACaption, AFont, AGlyphAlignment, AGlyphMargin,
horizAlign, vertAlign, relHorizAlign, relVertAlign, glyphHorzMargin, glyphVertMargin);
if ACaption = '' then
begin
rGlyph := rect(0,0,gw,gh);
AlignRect(rGlyph, rAvail, horizAlign, vertAlign, glyphHorzMargin, glyphVertMargin);
exit(rGlyph);
end else
CalculateTextSizeEx(ACaption, AFont, w, h, rAvail.Right-rAvail.Left);
if relVertAlign in[tlTop,tlBottom] then
begin
w2 := max(w,gw+glyphHorzMargin);
h2 := h+gh+glyphVertMargin;
end else
begin
w2 := w+gw+glyphHorzMargin;
if (ACaption <> '') and (w2 > rAvail.Right-rAvail.Left) then
begin
CalculateTextSizeEx(ACaption, AFont, w, h, rAvail.Right-rAvail.Left - (gw+glyphHorzMargin));
w2 := w+gw+glyphHorzMargin;
end;
h2 := max(h,gh+glyphVertMargin);
end;
rAll := rect(0,0,w2,h2);
AlignRect(rAll, rAvail, horizAlign, vertAlign);
rText := rect(0,0,w,h);
rGlyph := rect(0,0,gw,gh);
case relVertAlign of
tlTop: begin
AlignRect(rGlyph, rAll, relHorizAlign, tlTop,
glyphHorzMargin, glyphVertMargin);
AlignRect(rText, rAll, horizAlign, tlBottom);
end;
tlBottom: begin
AlignRect(rGlyph, rAll, relHorizAlign, tlBottom,
glyphHorzMargin, glyphVertMargin);
AlignRect(rText, rAll, horizAlign, tlTop);
end;
else begin
if relHorizAlign = taRightJustify then
begin
AlignRect(rGlyph, rAll, taRightJustify, tlCenter,
glyphHorzMargin, glyphHorzMargin);
AlignRect(rText, rAll, taLeftJustify, tlCenter);
end else
begin
AlignRect(rGlyph, rAll, taLeftJustify, tlCenter,
glyphHorzMargin, glyphHorzMargin);
AlignRect(rText, rAll, taRightJustify, tlCenter);
end;
end;
end;
result := rGlyph;
if AFont.WordBreak and (rText.Right < rAvail.Right) then inc(rText.Right); //word-break computation may be one pixel off
rAvail := rText;
end;
procedure RenderArrow(ATargetBGRA: TBGRABitmap; const ARect: TRect;
ASize: Integer; ADirection: TBCArrowDirection; AColor: TColor; AOpacity: Byte);
var
p: ArrayOfTPointF;
n: byte;
temp: TBGRABitmap;
w: Integer;
begin
// We can't draw outside rect
w := Min(ASize, ARect.Right - ARect.Left);
{ Poly }
SetLength(p, 3);
temp := TBGRABitmap.Create(w+1, w+1,BGRAPixelTransparent);
case ADirection of
badDown:
begin;
p[0].x := 0;
p[0].y := 0;
p[1].x := w;
p[1].y := 0;
p[2].x := Round(w/2);
p[2].y := w;
end;
badUp:
begin
p[0].x := Round(w/2);
p[0].y := 0;
p[1].x := 0;
p[1].y := w;
p[2].x := w;
p[2].y := w;
end;
badLeft:
begin
p[0].x := 0;
p[0].y := Round(w/2);
p[1].x := w;
p[1].y := 0;
p[2].x := w;
p[2].y := w;
end;
badRight:
begin
p[0].x := w;
p[0].y := Round(w/2);
p[1].x := 0;
p[1].y := 0;
p[2].x := 0;
p[2].y := w;
end;
end;
// Fill n times to get best quality
for n := 1 to 6 do
temp.FillPolyAntialias(p, ColorToBGRA(ColorToRGB(AColor),AOpacity));
ATargetBGRA.BlendImage(
ARect.Right-Round( ((ARect.Right-ARect.Left)/2) + (w/2) ),
ARect.Bottom-Round( ((ARect.Bottom-ARect.Top)/2) + (w/2) ),
temp,
boLinearBlend
);
temp.Free;
end;
procedure RenderBackgroundF(x1,y1,x2,y2: single; ABackground: TBCBackground;
ATargetBGRA: TBGRABitmap; ARounding: TBCRounding = nil);
var
backcolor: TBGRAPixel;
multi: TBGRAMultishapeFiller;
back: TBGRABitmap;
grect1, grect2: TRect;
gra: TBGRAGradientScanner;
rx,ry: Byte;
ropt: TRoundRectangleOptions;
begin
if (x1>=x2) or (y1>=y2) then exit;
if ARounding = nil then
begin
rx := 0;
ry := 0;
ropt := [];
end else
begin
rx := ARounding.RoundX;
ry := ARounding.RoundY;
ropt := ARounding.RoundOptions;
end;
{ Background color }
case ABackground.Style of
bbsClear: backcolor := BGRAPixelTransparent;
// TODO: Why if I use some system colors like clBtnFace, clActiveCaption etc.
// without ColorToRGB, I always get Black? Interface: QT
bbsColor: backcolor := ColorToBGRA(ColorToRGB(ABackground.Color), ABackground.ColorOpacity);
end;
case ABackground.Style of
bbsClear, bbsColor:
{ Solid background color }
ATargetBGRA.FillRoundRectAntialias(x1,y1,x2,y2, rx, ry, {%H-}backcolor, ropt);
bbsGradient:
begin
{ Using multishape filler to merge background gradient and border }
multi := TBGRAMultishapeFiller.Create;
multi.PolygonOrder := poFirstOnTop; { Border will replace background }
{ Gradients }
back := TBGRABitmap.Create(ATargetBGRA.Width, ATargetBGRA.Height, BGRAPixelTransparent);
grect1 := rect(floor(x1),floor(y1),ceil(x2)+1,ceil(y2)+1);
grect2 := grect1;
{ Gradient 1 }
if ABackground.Gradient1EndPercent > 0 then
begin
grect1.Bottom := grect1.top + Round(((grect1.Bottom-grect1.Top) / 100) * ABackground.Gradient1EndPercent);
gra := CreateGradient(ABackground.Gradient1, grect1);
back.FillRect(grect1.Left, grect1.Top, grect1.Right, grect1.Bottom,
gra, dmSet
);
gra.Free;
end;
{ Gradient 2 }
if ABackground.Gradient1EndPercent < 100 then
begin
grect2.Top := grect1.Bottom;
gra := CreateGradient(ABackground.Gradient2, grect2);
back.FillRect(grect2.Left, grect2.Top, grect2.Right, grect2.Bottom,
gra, dmSet
);
gra.Free;
end;
multi.AddRoundRectangle(x1,y1,x2,y2, rx, ry, back, ropt);
multi.Draw(ATargetBGRA);
multi.Free;
back.Free;
end;
end;
end;
procedure RenderBackground(const ARect: TRect; ABackground: TBCBackground;
ATargetBGRA: TBGRABitmap; ARounding: TBCRounding = nil; AHasNoBorder: boolean = false);
var
extraSize: single;
begin
if AHasNoBorder then extraSize := 0.5
else extraSize := 0;
RenderBackgroundF(ARect.Left-extraSize, ARect.Top-extraSize, ARect.Right-1+extraSize,
ARect.Bottom-1+extraSize,ABackground,ATargetBGRA,ARounding);
end;
end.