404 lines
11 KiB
ObjectPascal
404 lines
11 KiB
ObjectPascal
// SPDX-License-Identifier: LGPL-3.0-linking-exception
|
|
unit BGRATheme;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs,
|
|
BGRABitmap, BGRABitmapTypes, BGRASVGImageList;
|
|
|
|
type
|
|
TBGRAThemeButtonState = (btbsNormal, btbsHover, btbsActive, btbsDisabled);
|
|
|
|
{ TBGRAThemeSurface }
|
|
|
|
TBGRAThemeSurface = class
|
|
private
|
|
FBitmap: TBGRABitmap;
|
|
FBitmapRect: TRect;
|
|
FCanvasScale: single;
|
|
FDestCanvas: TCanvas;
|
|
FLclDPI: integer;
|
|
function GetBitmap: TBGRABitmap;
|
|
function GetBitmapDPI: integer;
|
|
procedure SetBitmapRect(AValue: TRect);
|
|
public
|
|
constructor Create(AControl: TCustomControl);
|
|
constructor Create(ADestRect: TRect; ADestCanvas: TCanvas; ACanvasScale: single; ALclDPI: integer);
|
|
destructor Destroy; override;
|
|
procedure DrawBitmap;
|
|
procedure DiscardBitmap;
|
|
procedure BitmapColorOverlay(AColor: TBGRAPixel; AOperation: TBlendOperation = boTransparent); overload;
|
|
function ScaleForCanvas(AValue: integer; AFromDPI: integer = 96): integer;
|
|
function ScaleForBitmap(AValue: integer; AFromDPI: integer = 96): integer;
|
|
function ScaleForBitmap(const ARect: TRect; AFromDPI: integer = 96): TRect;
|
|
property DestCanvas: TCanvas read FDestCanvas;
|
|
property DestCanvasDPI: integer read FLclDPI;
|
|
property Bitmap: TBGRABitmap read GetBitmap;
|
|
property BitmapRect: TRect read FBitmapRect write SetBitmapRect;
|
|
property BitmapDPI: integer read GetBitmapDPI;
|
|
end;
|
|
|
|
TBGRATheme = class;
|
|
|
|
{ TBGRAThemeControl }
|
|
|
|
TBGRAThemeControl = class(TCustomControl)
|
|
private
|
|
FTheme: TBGRATheme;
|
|
procedure SetTheme(AValue: TBGRATheme);
|
|
public
|
|
destructor Destroy; override;
|
|
published
|
|
property Theme: TBGRATheme read FTheme write SetTheme;
|
|
end;
|
|
|
|
{ TBGRATheme }
|
|
|
|
TBGRATheme = class(TComponent)
|
|
private
|
|
FThemedControls: TList;
|
|
function GetThemedControl(AIndex: integer): TBGRAThemeControl;
|
|
function GetThemedControlCount: integer;
|
|
procedure AddThemedControl(AControl: TBGRAThemeControl);
|
|
procedure RemoveThemedControl(AControl: TBGRAThemeControl);
|
|
|
|
protected
|
|
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
procedure InvalidateThemedControls;
|
|
|
|
function PreferredButtonWidth(const hasGlyph: boolean): Integer; virtual;
|
|
function PreferredButtonHeight(const hasGlyph: boolean): Integer; virtual;
|
|
|
|
procedure DrawButton(Caption: string; State: TBGRAThemeButtonState;
|
|
Focused: boolean; ARect: TRect; ASurface: TBGRAThemeSurface; AImageIndex: Integer = -1; AImageList: TBGRASVGImageList = nil); virtual;
|
|
procedure DrawRadioButton(Caption: string; State: TBGRAThemeButtonState;
|
|
{%H-}Focused: boolean; Checked: boolean; ARect: TRect; ASurface: TBGRAThemeSurface); virtual;
|
|
procedure DrawCheckBox(Caption: string; State: TBGRAThemeButtonState;
|
|
{%H-}Focused: boolean; Checked: boolean; ARect: TRect; ASurface: TBGRAThemeSurface); virtual;
|
|
|
|
property ThemedControlCount: integer read GetThemedControlCount;
|
|
property ThemedControl[AIndex: integer]: TBGRAThemeControl read GetThemedControl;
|
|
published
|
|
|
|
end;
|
|
|
|
var
|
|
BGRADefaultTheme: TBGRATheme;
|
|
|
|
procedure Register;
|
|
|
|
implementation
|
|
|
|
uses LCLType;
|
|
|
|
procedure Register;
|
|
begin
|
|
RegisterComponents('BGRA Themes', [TBGRATheme]);
|
|
end;
|
|
|
|
{ TBGRAThemeControl }
|
|
|
|
procedure TBGRAThemeControl.SetTheme(AValue: TBGRATheme);
|
|
begin
|
|
if FTheme=AValue then Exit;
|
|
if Assigned(AValue) then AValue.RemoveThemedControl(self);
|
|
FTheme:=AValue;
|
|
if Assigned(AValue) then AValue.AddThemedControl(self);
|
|
Invalidate;
|
|
end;
|
|
|
|
destructor TBGRAThemeControl.Destroy;
|
|
begin
|
|
if Assigned(FTheme) then FTheme.RemoveThemedControl(self);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
{ TBGRAThemeSurface }
|
|
|
|
function TBGRAThemeSurface.GetBitmap: TBGRABitmap;
|
|
begin
|
|
if FBitmap = nil then
|
|
FBitmap := TBGRABitmap.Create(round(FBitmapRect.Width * FCanvasScale),
|
|
round(FBitmapRect.Height * FCanvasScale));
|
|
result := FBitmap;
|
|
end;
|
|
|
|
function TBGRAThemeSurface.GetBitmapDPI: integer;
|
|
begin
|
|
result := round(FLclDPI*FCanvasScale);
|
|
end;
|
|
|
|
procedure TBGRAThemeSurface.SetBitmapRect(AValue: TRect);
|
|
begin
|
|
if FBitmapRect=AValue then Exit;
|
|
DiscardBitmap;
|
|
FBitmapRect:=AValue;
|
|
end;
|
|
|
|
constructor TBGRAThemeSurface.Create(AControl: TCustomControl);
|
|
var
|
|
parentForm: TCustomForm;
|
|
lclDPI: Integer;
|
|
begin
|
|
parentForm := GetParentForm(AControl, False);
|
|
if Assigned(parentForm) then
|
|
lclDPI := parentForm.PixelsPerInch
|
|
else lclDPI := Screen.PixelsPerInch;
|
|
Create(AControl.ClientRect, AControl.Canvas, AControl.GetCanvasScaleFactor, lclDPI);
|
|
end;
|
|
|
|
constructor TBGRAThemeSurface.Create(ADestRect: TRect; ADestCanvas: TCanvas;
|
|
ACanvasScale: single; ALclDPI: integer);
|
|
begin
|
|
FBitmap := nil;
|
|
FBitmapRect := ADestRect;
|
|
FDestCanvas := ADestCanvas;
|
|
FCanvasScale:= ACanvasScale;
|
|
FLclDPI:= ALclDPI;
|
|
end;
|
|
|
|
destructor TBGRAThemeSurface.Destroy;
|
|
begin
|
|
FBitmap.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TBGRAThemeSurface.DrawBitmap;
|
|
begin
|
|
if FBitmap = nil then exit;
|
|
FBitmap.Draw(FDestCanvas, FBitmapRect, false);
|
|
end;
|
|
|
|
procedure TBGRAThemeSurface.DiscardBitmap;
|
|
begin
|
|
FreeAndNil(FBitmap);
|
|
end;
|
|
|
|
procedure TBGRAThemeSurface.BitmapColorOverlay(AColor: TBGRAPixel;
|
|
AOperation: TBlendOperation);
|
|
begin
|
|
if AColor.alpha <> 0 then
|
|
Bitmap.BlendOver(AColor, AOperation, AColor.alpha, false, true);
|
|
end;
|
|
|
|
function TBGRAThemeSurface.ScaleForCanvas(AValue: integer; AFromDPI: integer): integer;
|
|
begin
|
|
result := MulDiv(AValue, DestCanvasDPI, AFromDPI);
|
|
end;
|
|
|
|
function TBGRAThemeSurface.ScaleForBitmap(AValue: integer; AFromDPI: integer): integer;
|
|
begin
|
|
result := MulDiv(AValue, BitmapDPI, AFromDPI);
|
|
end;
|
|
|
|
function TBGRAThemeSurface.ScaleForBitmap(const ARect: TRect; AFromDPI: integer): TRect;
|
|
begin
|
|
result.Left := ScaleForBitmap(ARect.Left, AFromDPI);
|
|
result.Top := ScaleForBitmap(ARect.Top, AFromDPI);
|
|
result.Right := ScaleForBitmap(ARect.Right, AFromDPI);
|
|
result.Bottom := ScaleForBitmap(ARect.Bottom, AFromDPI);
|
|
end;
|
|
|
|
{ TBGRATheme }
|
|
|
|
function TBGRATheme.GetThemedControl(AIndex: integer): TBGRAThemeControl;
|
|
begin
|
|
result := TBGRAThemeControl(FThemedControls[AIndex]);
|
|
end;
|
|
|
|
function TBGRATheme.GetThemedControlCount: integer;
|
|
begin
|
|
result := FThemedControls.Count;
|
|
end;
|
|
|
|
procedure TBGRATheme.AddThemedControl(AControl: TBGRAThemeControl);
|
|
begin
|
|
if FThemedControls.IndexOf(AControl) = -1 then
|
|
FThemedControls.Add(AControl);
|
|
end;
|
|
|
|
procedure TBGRATheme.RemoveThemedControl(AControl: TBGRAThemeControl);
|
|
begin
|
|
FThemedControls.Remove(AControl);
|
|
end;
|
|
|
|
constructor TBGRATheme.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
FThemedControls := TList.Create;
|
|
end;
|
|
|
|
destructor TBGRATheme.Destroy;
|
|
var i: integer;
|
|
begin
|
|
for i := ThemedControlCount-1 downto 0 do
|
|
ThemedControl[i].Theme := nil;
|
|
FThemedControls.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TBGRATheme.InvalidateThemedControls;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := 0 to ThemedControlCount-1 do
|
|
ThemedControl[i].Invalidate;
|
|
end;
|
|
|
|
function TBGRATheme.PreferredButtonWidth(const hasGlyph: boolean): Integer;
|
|
begin
|
|
Result := 125;
|
|
end;
|
|
|
|
function TBGRATheme.PreferredButtonHeight(const hasGlyph: boolean): Integer;
|
|
begin
|
|
Result := 35;
|
|
end;
|
|
|
|
procedure TBGRATheme.DrawButton(Caption: string; State: TBGRAThemeButtonState;
|
|
Focused: boolean; ARect: TRect; ASurface: TBGRAThemeSurface;
|
|
AImageIndex: Integer; AImageList: TBGRASVGImageList);
|
|
var
|
|
Style: TTextStyle;
|
|
begin
|
|
With ASurface do
|
|
begin
|
|
DestCanvas.Font.Color := clBlack;
|
|
case State of
|
|
btbsNormal: DestCanvas.Brush.Color := RGBToColor(225, 225, 225);
|
|
btbsHover: DestCanvas.Brush.Color := RGBToColor(229, 241, 251);
|
|
btbsActive: DestCanvas.Brush.Color := RGBToColor(204, 228, 247);
|
|
btbsDisabled: DestCanvas.Brush.Color := RGBToColor(204, 204, 204);
|
|
end;
|
|
|
|
DestCanvas.Pen.Color := DestCanvas.Brush.Color;
|
|
DestCanvas.Rectangle(ARect);
|
|
|
|
if Focused then
|
|
begin
|
|
DestCanvas.Pen.Color := clBlack;
|
|
DestCanvas.Rectangle(ARect);
|
|
end;
|
|
|
|
if Caption <> '' then
|
|
begin
|
|
fillchar(Style, sizeof(Style), 0);
|
|
Style.Alignment := taCenter;
|
|
Style.Layout := tlCenter;
|
|
Style.Wordbreak := True;
|
|
DestCanvas.TextRect(ARect, 0, 0, Caption, Style);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TBGRATheme.DrawRadioButton(Caption: string; State: TBGRAThemeButtonState;
|
|
Focused: boolean; Checked: boolean; ARect: TRect; ASurface: TBGRAThemeSurface);
|
|
var
|
|
Style: TTextStyle;
|
|
Color: TBGRAPixel;
|
|
begin
|
|
with ASurface do
|
|
begin
|
|
DestCanvas.Font.Color := clBlack;
|
|
case State of
|
|
btbsHover: Color := BGRA(0, 120, 215);
|
|
btbsActive: Color := BGRA(0, 84, 153);
|
|
btbsDisabled:
|
|
begin
|
|
DestCanvas.Font.Color := clGray;
|
|
Color := BGRA(204, 204, 204);
|
|
end;
|
|
else {btbsNormal}
|
|
Color := BGRABlack;
|
|
end;
|
|
|
|
BitmapRect := RectWithSize(ARect.Left, ARect.Top, ARect.Height, ARect.Height);
|
|
Bitmap.FillEllipseAntialias(Bitmap.Height / 2, Bitmap.Height / 2,
|
|
Bitmap.Height / 2 - 2, Bitmap.Height / 2 - 2, BGRAWhite);
|
|
Bitmap.EllipseAntialias(Bitmap.Height / 2, Bitmap.Height / 2,
|
|
Bitmap.Height / 2 - 2, Bitmap.Height / 2 - 2, Color{%H-}, 1);
|
|
if Checked then
|
|
Bitmap.FillEllipseAntialias(Bitmap.Height / 2, Bitmap.Height /
|
|
2, Bitmap.Height / 4, Bitmap.Height / 4, Color);
|
|
DrawBitmap;
|
|
|
|
if Caption <> '' then
|
|
begin
|
|
fillchar(Style, sizeof(Style), 0);
|
|
Style.Alignment := taLeftJustify;
|
|
Style.Layout := tlCenter;
|
|
Style.Wordbreak := True;
|
|
DestCanvas.TextRect(Rect(Arect.Height, 0, ARect.Right, ARect.Bottom),
|
|
ARect.Height, 0, Caption, Style);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TBGRATheme.DrawCheckBox(Caption: string; State: TBGRAThemeButtonState;
|
|
Focused: boolean; Checked: boolean; ARect: TRect; ASurface: TBGRAThemeSurface);
|
|
var
|
|
Style: TTextStyle;
|
|
Bitmap: TBGRABitmap;
|
|
Color: TBGRAPixel;
|
|
aleft, atop, aright, abottom: integer;
|
|
begin
|
|
with ASurface do
|
|
begin
|
|
DestCanvas.Font.Color := clBlack;
|
|
case State of
|
|
btbsHover: Color := BGRA(0, 120, 215);
|
|
btbsActive: Color := BGRA(0, 84, 153);
|
|
btbsDisabled:
|
|
begin
|
|
DestCanvas.Font.Color := clGray;
|
|
Color := BGRA(204, 204, 204);
|
|
end;
|
|
else {btbsNormal}
|
|
Color := BGRABlack;
|
|
end;
|
|
|
|
BitmapRect := RectWithSize(ARect.Left, ARect.Top, ARect.Height, ARect.Height);
|
|
Bitmap.Rectangle(0, 0, Bitmap.Height, Bitmap.Height, Color, BGRAWhite);
|
|
aleft := 0;
|
|
aright := Bitmap.Height;
|
|
atop := 0;
|
|
abottom := Bitmap.Height;
|
|
if Checked then
|
|
Bitmap.DrawPolyLineAntialias(Bitmap.ComputeBezierSpline(
|
|
[BezierCurve(pointF(aleft + 2, atop + 3), PointF((aleft + aright - 1) / 2, abottom - 3)),
|
|
BezierCurve(PointF((aleft + aright - 1) / 2, abottom - 3), PointF(
|
|
(aleft + aright - 1) / 2, (atop * 2 + abottom - 1) / 3), PointF(aright - 2, atop - 2))]),
|
|
Color, 1.5);
|
|
DrawBitmap;
|
|
|
|
if Caption <> '' then
|
|
begin
|
|
fillchar(Style, sizeof(Style), 0);
|
|
Style.Alignment := taLeftJustify;
|
|
Style.Layout := tlCenter;
|
|
Style.Wordbreak := True;
|
|
DestCanvas.TextRect(Rect(Arect.Height, 0, ARect.Right, ARect.Bottom),
|
|
ARect.Height, 0, Caption, Style);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
BasicTheme: TBGRATheme;
|
|
|
|
initialization
|
|
|
|
BasicTheme := TBGRATheme.Create(nil);
|
|
BGRADefaultTheme := BasicTheme;
|
|
|
|
finalization
|
|
FreeAndNil(BasicTheme);
|
|
|
|
end.
|