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.