bgratheme.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. // SPDX-License-Identifier: LGPL-3.0-linking-exception
  2. unit BGRATheme;
  3. {$mode objfpc}{$H+}
  4. interface
  5. uses
  6. Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs,
  7. BGRABitmap, BGRABitmapTypes, BGRASVGImageList;
  8. type
  9. TBGRAThemeButtonState = (btbsNormal, btbsHover, btbsActive, btbsDisabled);
  10. { TBGRAThemeSurface }
  11. TBGRAThemeSurface = class
  12. private
  13. FBitmap: TBGRABitmap;
  14. FBitmapRect: TRect;
  15. FCanvasScale: single;
  16. FDestCanvas: TCanvas;
  17. FLclDPI: integer;
  18. function GetBitmap: TBGRABitmap;
  19. function GetBitmapDPI: integer;
  20. procedure SetBitmapRect(AValue: TRect);
  21. public
  22. constructor Create(AControl: TCustomControl);
  23. constructor Create(ADestRect: TRect; ADestCanvas: TCanvas; ACanvasScale: single; ALclDPI: integer);
  24. destructor Destroy; override;
  25. procedure DrawBitmap;
  26. procedure DiscardBitmap;
  27. procedure BitmapColorOverlay(AColor: TBGRAPixel; AOperation: TBlendOperation = boTransparent); overload;
  28. function ScaleForCanvas(AValue: integer; AFromDPI: integer = 96): integer;
  29. function ScaleForBitmap(AValue: integer; AFromDPI: integer = 96): integer;
  30. function ScaleForBitmap(const ARect: TRect; AFromDPI: integer = 96): TRect;
  31. property DestCanvas: TCanvas read FDestCanvas;
  32. property DestCanvasDPI: integer read FLclDPI;
  33. property Bitmap: TBGRABitmap read GetBitmap;
  34. property BitmapRect: TRect read FBitmapRect write SetBitmapRect;
  35. property BitmapDPI: integer read GetBitmapDPI;
  36. end;
  37. TBGRATheme = class;
  38. { TBGRAThemeControl }
  39. TBGRAThemeControl = class(TCustomControl)
  40. private
  41. FTheme: TBGRATheme;
  42. procedure SetTheme(AValue: TBGRATheme);
  43. public
  44. destructor Destroy; override;
  45. published
  46. property Theme: TBGRATheme read FTheme write SetTheme;
  47. end;
  48. { TBGRATheme }
  49. TBGRATheme = class(TComponent)
  50. private
  51. FThemedControls: TList;
  52. function GetThemedControl(AIndex: integer): TBGRAThemeControl;
  53. function GetThemedControlCount: integer;
  54. procedure AddThemedControl(AControl: TBGRAThemeControl);
  55. procedure RemoveThemedControl(AControl: TBGRAThemeControl);
  56. protected
  57. public
  58. constructor Create(AOwner: TComponent); override;
  59. destructor Destroy; override;
  60. procedure InvalidateThemedControls;
  61. function PreferredButtonWidth(const hasGlyph: boolean): Integer; virtual;
  62. function PreferredButtonHeight(const hasGlyph: boolean): Integer; virtual;
  63. procedure DrawButton(Caption: string; State: TBGRAThemeButtonState;
  64. Focused: boolean; ARect: TRect; ASurface: TBGRAThemeSurface; AImageIndex: Integer = -1; AImageList: TBGRASVGImageList = nil); virtual;
  65. procedure DrawRadioButton(Caption: string; State: TBGRAThemeButtonState;
  66. {%H-}Focused: boolean; Checked: boolean; ARect: TRect; ASurface: TBGRAThemeSurface); virtual;
  67. procedure DrawCheckBox(Caption: string; State: TBGRAThemeButtonState;
  68. {%H-}Focused: boolean; Checked: boolean; ARect: TRect; ASurface: TBGRAThemeSurface); virtual;
  69. property ThemedControlCount: integer read GetThemedControlCount;
  70. property ThemedControl[AIndex: integer]: TBGRAThemeControl read GetThemedControl;
  71. published
  72. end;
  73. var
  74. BGRADefaultTheme: TBGRATheme;
  75. procedure Register;
  76. implementation
  77. uses LCLType;
  78. procedure Register;
  79. begin
  80. RegisterComponents('BGRA Themes', [TBGRATheme]);
  81. end;
  82. { TBGRAThemeControl }
  83. procedure TBGRAThemeControl.SetTheme(AValue: TBGRATheme);
  84. begin
  85. if FTheme=AValue then Exit;
  86. if Assigned(AValue) then AValue.RemoveThemedControl(self);
  87. FTheme:=AValue;
  88. if Assigned(AValue) then AValue.AddThemedControl(self);
  89. Invalidate;
  90. end;
  91. destructor TBGRAThemeControl.Destroy;
  92. begin
  93. if Assigned(FTheme) then FTheme.RemoveThemedControl(self);
  94. inherited Destroy;
  95. end;
  96. { TBGRAThemeSurface }
  97. function TBGRAThemeSurface.GetBitmap: TBGRABitmap;
  98. begin
  99. if FBitmap = nil then
  100. FBitmap := TBGRABitmap.Create(round(FBitmapRect.Width * FCanvasScale),
  101. round(FBitmapRect.Height * FCanvasScale));
  102. result := FBitmap;
  103. end;
  104. function TBGRAThemeSurface.GetBitmapDPI: integer;
  105. begin
  106. result := round(FLclDPI*FCanvasScale);
  107. end;
  108. procedure TBGRAThemeSurface.SetBitmapRect(AValue: TRect);
  109. begin
  110. if FBitmapRect=AValue then Exit;
  111. DiscardBitmap;
  112. FBitmapRect:=AValue;
  113. end;
  114. constructor TBGRAThemeSurface.Create(AControl: TCustomControl);
  115. var
  116. parentForm: TCustomForm;
  117. lclDPI: Integer;
  118. begin
  119. parentForm := GetParentForm(AControl, False);
  120. if Assigned(parentForm) then
  121. lclDPI := parentForm.PixelsPerInch
  122. else lclDPI := Screen.PixelsPerInch;
  123. Create(AControl.ClientRect, AControl.Canvas, AControl.GetCanvasScaleFactor, lclDPI);
  124. end;
  125. constructor TBGRAThemeSurface.Create(ADestRect: TRect; ADestCanvas: TCanvas;
  126. ACanvasScale: single; ALclDPI: integer);
  127. begin
  128. FBitmap := nil;
  129. FBitmapRect := ADestRect;
  130. FDestCanvas := ADestCanvas;
  131. FCanvasScale:= ACanvasScale;
  132. FLclDPI:= ALclDPI;
  133. end;
  134. destructor TBGRAThemeSurface.Destroy;
  135. begin
  136. FBitmap.Free;
  137. inherited Destroy;
  138. end;
  139. procedure TBGRAThemeSurface.DrawBitmap;
  140. begin
  141. if FBitmap = nil then exit;
  142. FBitmap.Draw(FDestCanvas, FBitmapRect, false);
  143. end;
  144. procedure TBGRAThemeSurface.DiscardBitmap;
  145. begin
  146. FreeAndNil(FBitmap);
  147. end;
  148. procedure TBGRAThemeSurface.BitmapColorOverlay(AColor: TBGRAPixel;
  149. AOperation: TBlendOperation);
  150. begin
  151. if AColor.alpha <> 0 then
  152. Bitmap.BlendOver(AColor, AOperation, AColor.alpha, false, true);
  153. end;
  154. function TBGRAThemeSurface.ScaleForCanvas(AValue: integer; AFromDPI: integer): integer;
  155. begin
  156. result := MulDiv(AValue, DestCanvasDPI, AFromDPI);
  157. end;
  158. function TBGRAThemeSurface.ScaleForBitmap(AValue: integer; AFromDPI: integer): integer;
  159. begin
  160. result := MulDiv(AValue, BitmapDPI, AFromDPI);
  161. end;
  162. function TBGRAThemeSurface.ScaleForBitmap(const ARect: TRect; AFromDPI: integer): TRect;
  163. begin
  164. result.Left := ScaleForBitmap(ARect.Left, AFromDPI);
  165. result.Top := ScaleForBitmap(ARect.Top, AFromDPI);
  166. result.Right := ScaleForBitmap(ARect.Right, AFromDPI);
  167. result.Bottom := ScaleForBitmap(ARect.Bottom, AFromDPI);
  168. end;
  169. { TBGRATheme }
  170. function TBGRATheme.GetThemedControl(AIndex: integer): TBGRAThemeControl;
  171. begin
  172. result := TBGRAThemeControl(FThemedControls[AIndex]);
  173. end;
  174. function TBGRATheme.GetThemedControlCount: integer;
  175. begin
  176. result := FThemedControls.Count;
  177. end;
  178. procedure TBGRATheme.AddThemedControl(AControl: TBGRAThemeControl);
  179. begin
  180. if FThemedControls.IndexOf(AControl) = -1 then
  181. FThemedControls.Add(AControl);
  182. end;
  183. procedure TBGRATheme.RemoveThemedControl(AControl: TBGRAThemeControl);
  184. begin
  185. FThemedControls.Remove(AControl);
  186. end;
  187. constructor TBGRATheme.Create(AOwner: TComponent);
  188. begin
  189. inherited Create(AOwner);
  190. FThemedControls := TList.Create;
  191. end;
  192. destructor TBGRATheme.Destroy;
  193. var i: integer;
  194. begin
  195. for i := ThemedControlCount-1 downto 0 do
  196. ThemedControl[i].Theme := nil;
  197. FThemedControls.Free;
  198. inherited Destroy;
  199. end;
  200. procedure TBGRATheme.InvalidateThemedControls;
  201. var
  202. i: Integer;
  203. begin
  204. for i := 0 to ThemedControlCount-1 do
  205. ThemedControl[i].Invalidate;
  206. end;
  207. function TBGRATheme.PreferredButtonWidth(const hasGlyph: boolean): Integer;
  208. begin
  209. Result := 125;
  210. end;
  211. function TBGRATheme.PreferredButtonHeight(const hasGlyph: boolean): Integer;
  212. begin
  213. Result := 35;
  214. end;
  215. procedure TBGRATheme.DrawButton(Caption: string; State: TBGRAThemeButtonState;
  216. Focused: boolean; ARect: TRect; ASurface: TBGRAThemeSurface;
  217. AImageIndex: Integer; AImageList: TBGRASVGImageList);
  218. var
  219. Style: TTextStyle;
  220. begin
  221. With ASurface do
  222. begin
  223. DestCanvas.Font.Color := clBlack;
  224. case State of
  225. btbsNormal: DestCanvas.Brush.Color := RGBToColor(225, 225, 225);
  226. btbsHover: DestCanvas.Brush.Color := RGBToColor(229, 241, 251);
  227. btbsActive: DestCanvas.Brush.Color := RGBToColor(204, 228, 247);
  228. btbsDisabled: DestCanvas.Brush.Color := RGBToColor(204, 204, 204);
  229. end;
  230. DestCanvas.Pen.Color := DestCanvas.Brush.Color;
  231. DestCanvas.Rectangle(ARect);
  232. if Focused then
  233. begin
  234. DestCanvas.Pen.Color := clBlack;
  235. DestCanvas.Rectangle(ARect);
  236. end;
  237. if Caption <> '' then
  238. begin
  239. fillchar(Style, sizeof(Style), 0);
  240. Style.Alignment := taCenter;
  241. Style.Layout := tlCenter;
  242. Style.Wordbreak := True;
  243. DestCanvas.TextRect(ARect, 0, 0, Caption, Style);
  244. end;
  245. end;
  246. end;
  247. procedure TBGRATheme.DrawRadioButton(Caption: string; State: TBGRAThemeButtonState;
  248. Focused: boolean; Checked: boolean; ARect: TRect; ASurface: TBGRAThemeSurface);
  249. var
  250. Style: TTextStyle;
  251. Color: TBGRAPixel;
  252. begin
  253. with ASurface do
  254. begin
  255. DestCanvas.Font.Color := clBlack;
  256. case State of
  257. btbsHover: Color := BGRA(0, 120, 215);
  258. btbsActive: Color := BGRA(0, 84, 153);
  259. btbsDisabled:
  260. begin
  261. DestCanvas.Font.Color := clGray;
  262. Color := BGRA(204, 204, 204);
  263. end;
  264. else {btbsNormal}
  265. Color := BGRABlack;
  266. end;
  267. BitmapRect := RectWithSize(ARect.Left, ARect.Top, ARect.Height, ARect.Height);
  268. Bitmap.FillEllipseAntialias(Bitmap.Height / 2, Bitmap.Height / 2,
  269. Bitmap.Height / 2 - 2, Bitmap.Height / 2 - 2, BGRAWhite);
  270. Bitmap.EllipseAntialias(Bitmap.Height / 2, Bitmap.Height / 2,
  271. Bitmap.Height / 2 - 2, Bitmap.Height / 2 - 2, Color{%H-}, 1);
  272. if Checked then
  273. Bitmap.FillEllipseAntialias(Bitmap.Height / 2, Bitmap.Height /
  274. 2, Bitmap.Height / 4, Bitmap.Height / 4, Color);
  275. DrawBitmap;
  276. if Caption <> '' then
  277. begin
  278. fillchar(Style, sizeof(Style), 0);
  279. Style.Alignment := taLeftJustify;
  280. Style.Layout := tlCenter;
  281. Style.Wordbreak := True;
  282. DestCanvas.TextRect(Rect(Arect.Height, 0, ARect.Right, ARect.Bottom),
  283. ARect.Height, 0, Caption, Style);
  284. end;
  285. end;
  286. end;
  287. procedure TBGRATheme.DrawCheckBox(Caption: string; State: TBGRAThemeButtonState;
  288. Focused: boolean; Checked: boolean; ARect: TRect; ASurface: TBGRAThemeSurface);
  289. var
  290. Style: TTextStyle;
  291. Bitmap: TBGRABitmap;
  292. Color: TBGRAPixel;
  293. aleft, atop, aright, abottom: integer;
  294. begin
  295. with ASurface do
  296. begin
  297. DestCanvas.Font.Color := clBlack;
  298. case State of
  299. btbsHover: Color := BGRA(0, 120, 215);
  300. btbsActive: Color := BGRA(0, 84, 153);
  301. btbsDisabled:
  302. begin
  303. DestCanvas.Font.Color := clGray;
  304. Color := BGRA(204, 204, 204);
  305. end;
  306. else {btbsNormal}
  307. Color := BGRABlack;
  308. end;
  309. BitmapRect := RectWithSize(ARect.Left, ARect.Top, ARect.Height, ARect.Height);
  310. Bitmap.Rectangle(0, 0, Bitmap.Height, Bitmap.Height, Color, BGRAWhite);
  311. aleft := 0;
  312. aright := Bitmap.Height;
  313. atop := 0;
  314. abottom := Bitmap.Height;
  315. if Checked then
  316. Bitmap.DrawPolyLineAntialias(Bitmap.ComputeBezierSpline(
  317. [BezierCurve(pointF(aleft + 2, atop + 3), PointF((aleft + aright - 1) / 2, abottom - 3)),
  318. BezierCurve(PointF((aleft + aright - 1) / 2, abottom - 3), PointF(
  319. (aleft + aright - 1) / 2, (atop * 2 + abottom - 1) / 3), PointF(aright - 2, atop - 2))]),
  320. Color, 1.5);
  321. DrawBitmap;
  322. if Caption <> '' then
  323. begin
  324. fillchar(Style, sizeof(Style), 0);
  325. Style.Alignment := taLeftJustify;
  326. Style.Layout := tlCenter;
  327. Style.Wordbreak := True;
  328. DestCanvas.TextRect(Rect(Arect.Height, 0, ARect.Right, ARect.Bottom),
  329. ARect.Height, 0, Caption, Style);
  330. end;
  331. end;
  332. end;
  333. var
  334. BasicTheme: TBGRATheme;
  335. initialization
  336. BasicTheme := TBGRATheme.Create(nil);
  337. BGRADefaultTheme := BasicTheme;
  338. finalization
  339. FreeAndNil(BasicTheme);
  340. end.