737 lines
24 KiB
ObjectPascal
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.
|
|
|