bgrashape.pas 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. // SPDX-License-Identifier: LGPL-3.0-linking-exception
  2. {
  3. Created by BGRA Controls Team
  4. Dibo, Circular, lainz (007) and contributors.
  5. For detailed information see readme.txt
  6. Site: https://sourceforge.net/p/bgra-controls/
  7. Wiki: http://wiki.lazarus.freepascal.org/BGRAControls
  8. Forum: http://forum.lazarus.freepascal.org/index.php/board,46.0.html
  9. }
  10. {******************************* CONTRIBUTOR(S) ******************************
  11. - Edivando S. Santos Brasil | [email protected]
  12. (Compatibility with delphi VCL 11/2018)
  13. - Sandy Ganz ([email protected])
  14. 2025-07 Added capture for Fill and Border Gradient property changes so
  15. repaint will happen.
  16. ***************************** END CONTRIBUTOR(S) *****************************}
  17. unit BGRAShape;
  18. {$I bgracontrols.inc}
  19. interface
  20. uses
  21. Classes, SysUtils, {$IFDEF FPC}LResources,{$ENDIF} Forms, Controls, Graphics, Dialogs,
  22. {$IFNDEF FPC}Types, BGRAGraphics, GraphType, FPImage, {$ENDIF}
  23. BCBaseCtrls, BGRABitmap, BGRABitmapTypes, BCTypes;
  24. type
  25. TBGRAShapeType = (stRegularPolygon, stEllipse);
  26. { TBGRAShape }
  27. TBGRAShape = class(TBGRAGraphicCtrl)
  28. private
  29. { Private declarations }
  30. FBorderColor: TColor;
  31. FBorderOpacity: byte;
  32. FBorderStyle: TPenStyle;
  33. FBorderWidth: integer;
  34. FBorderGradient: TBCGradient;
  35. FUseBorderGradient: boolean;
  36. FFillColor: TColor;
  37. FFillOpacity: byte;
  38. FFillGradient: TBCGradient;
  39. FUseFillGradient: boolean;
  40. FRoundRadius: integer;
  41. FBGRA: TBGRABitmap;
  42. FSideCount: integer;
  43. FRatioXY: single;
  44. FUseRatioXY: boolean;
  45. FAngle: single;
  46. FShapeType: TBGRAShapeType;
  47. procedure SetAngle(const AValue: single);
  48. procedure SetBorderColor(const AValue: TColor);
  49. procedure SetBorderGradient(const AValue: TBCGradient);
  50. procedure SetBorderOpacity(const AValue: byte);
  51. procedure SetBorderStyle(const AValue: TPenStyle);
  52. procedure SetBorderWidth(AValue: integer);
  53. procedure SetFillColor(const AValue: TColor);
  54. procedure SetFillGradient(const AValue: TBCGradient);
  55. procedure SetFillOpacity(const AValue: byte);
  56. procedure SetRatioXY(const AValue: single);
  57. procedure SetRoundRadius(AValue: integer);
  58. procedure SetShapeType(const AValue: TBGRAShapeType);
  59. procedure SetSideCount(AValue: integer);
  60. procedure SetUseBorderGradient(const AValue: boolean);
  61. procedure SetUseFillGradient(const AValue: boolean);
  62. procedure SetUseRatioXY(const AValue: boolean);
  63. protected
  64. { Protected declarations }
  65. procedure Paint; override;
  66. procedure DoChangeGradient(ASender: TObject; AData: PtrInt);
  67. public
  68. { Public declarations }
  69. constructor Create(AOwner: TComponent); override;
  70. destructor Destroy; override;
  71. public
  72. { Streaming }
  73. {$IFDEF FPC}
  74. procedure SaveToFile(AFileName: string);
  75. procedure LoadFromFile(AFileName: string);
  76. {$ENDIF}
  77. procedure OnFindClass({%H-}Reader: TReader; const AClassName: string;
  78. var ComponentClass: TComponentClass);
  79. published
  80. { Published declarations }
  81. property AutoSize;
  82. property Align;
  83. property Anchors;
  84. property Angle: single Read FAngle Write SetAngle {$IFDEF FPC}default 0{$ENDIF};
  85. property BorderWidth: integer Read FBorderWidth Write SetBorderWidth default 1;
  86. property BorderOpacity: byte Read FBorderOpacity Write SetBorderOpacity default 255;
  87. property BorderColor: TColor Read FBorderColor Write SetBorderColor;
  88. property BorderGradient: TBCGradient Read FBorderGradient Write SetBorderGradient;
  89. property BorderStyle: TPenStyle
  90. Read FBorderStyle Write SetBorderStyle default psSolid;
  91. property FillColor: TColor Read FFillColor Write SetFillColor;
  92. property FillOpacity: byte Read FFillOpacity Write SetFillOpacity;
  93. property FillGradient: TBCGradient Read FFillGradient Write SetFillGradient;
  94. property SideCount: integer Read FSideCount Write SetSideCount default 4;
  95. property RatioXY: single Read FRatioXY Write SetRatioXY {$IFDEF FPC}default 1{$ENDIF};
  96. property UseRatioXY: boolean Read FUseRatioXY Write SetUseRatioXY default False;
  97. property UseFillGradient: boolean Read FUseFillGradient
  98. Write SetUseFillGradient default False;
  99. property UseBorderGradient: boolean Read FUseBorderGradient
  100. Write SetUseBorderGradient default False;
  101. property ShapeType: TBGRAShapeType
  102. Read FShapeType Write SetShapeType default stRegularPolygon;
  103. property BorderSpacing;
  104. property Caption;
  105. property PopupMenu;
  106. property RoundRadius: integer Read FRoundRadius Write SetRoundRadius default 0;
  107. property Visible;
  108. property OnClick;
  109. property OnDblClick;
  110. property OnMouseDown;
  111. property OnMouseEnter;
  112. property OnMouseLeave;
  113. property OnMouseMove;
  114. property OnMouseUp;
  115. end;
  116. {$IFDEF FPC}procedure Register;{$ENDIF}
  117. implementation
  118. uses BCTools;
  119. {$IFDEF FPC}
  120. procedure Register;
  121. begin
  122. RegisterComponents('BGRA Controls', [TBGRAShape]);
  123. end;
  124. {$ENDIF}
  125. { TBGRAShape }
  126. // Added to catch any TBCGradient changes for Border and Fill Gradient objects
  127. procedure TBGRAShape.DoChangeGradient(ASender: TObject; AData: PtrInt);
  128. begin
  129. Invalidate;
  130. end;
  131. procedure TBGRAShape.SetBorderColor(const AValue: TColor);
  132. begin
  133. if FBorderColor = AValue then
  134. exit;
  135. FBorderColor := AValue;
  136. Invalidate;
  137. end;
  138. procedure TBGRAShape.SetBorderGradient(const AValue: TBCGradient);
  139. begin
  140. if FBorderGradient = AValue then
  141. exit;
  142. FBorderGradient.Assign(AValue);
  143. Invalidate;
  144. end;
  145. procedure TBGRAShape.SetAngle(const AValue: single);
  146. begin
  147. if FAngle = AValue then
  148. exit;
  149. FAngle := AValue;
  150. Invalidate;
  151. end;
  152. procedure TBGRAShape.SetBorderOpacity(const AValue: byte);
  153. begin
  154. if FBorderOpacity = AValue then
  155. exit;
  156. FBorderOpacity := AValue;
  157. Invalidate;
  158. end;
  159. procedure TBGRAShape.SetBorderStyle(const AValue: TPenStyle);
  160. begin
  161. if FBorderStyle = AValue then
  162. exit;
  163. FBorderStyle := AValue;
  164. Invalidate;
  165. end;
  166. procedure TBGRAShape.SetBorderWidth(AValue: integer);
  167. begin
  168. if AValue < 0 then
  169. AValue := 0;
  170. if FBorderWidth = AValue then
  171. exit;
  172. FBorderWidth := AValue;
  173. Invalidate;
  174. end;
  175. procedure TBGRAShape.SetFillColor(const AValue: TColor);
  176. begin
  177. if FFillColor = AValue then
  178. exit;
  179. FFillColor := AValue;
  180. Invalidate;
  181. end;
  182. procedure TBGRAShape.SetFillGradient(const AValue: TBCGradient);
  183. begin
  184. if FFillGradient = AValue then
  185. exit;
  186. FFillGradient.Assign(AValue);
  187. Invalidate;
  188. end;
  189. procedure TBGRAShape.SetFillOpacity(const AValue: byte);
  190. begin
  191. if FFillOpacity = AValue then
  192. exit;
  193. FFillOpacity := AValue;
  194. Invalidate;
  195. end;
  196. procedure TBGRAShape.SetRatioXY(const AValue: single);
  197. begin
  198. if FRatioXY = AValue then
  199. exit;
  200. FRatioXY := AValue;
  201. Invalidate;
  202. end;
  203. procedure TBGRAShape.SetRoundRadius(AValue: integer);
  204. begin
  205. if AValue < 0 then
  206. AValue := 0;
  207. if FRoundRadius = AValue then
  208. exit;
  209. FRoundRadius := AValue;
  210. Invalidate;
  211. end;
  212. procedure TBGRAShape.SetShapeType(const AValue: TBGRAShapeType);
  213. begin
  214. if FShapeType = AValue then
  215. exit;
  216. FShapeType := AValue;
  217. Invalidate;
  218. end;
  219. procedure TBGRAShape.SetSideCount(AValue: integer);
  220. begin
  221. if AValue < 3 then
  222. AValue := 3;
  223. if FSideCount = AValue then
  224. exit;
  225. FSideCount := AValue;
  226. Invalidate;
  227. end;
  228. procedure TBGRAShape.SetUseBorderGradient(const AValue: boolean);
  229. begin
  230. if FUseBorderGradient = AValue then
  231. exit;
  232. FUseBorderGradient := AValue;
  233. Invalidate;
  234. end;
  235. procedure TBGRAShape.SetUseFillGradient(const AValue: boolean);
  236. begin
  237. if FUseFillGradient = AValue then
  238. exit;
  239. FUseFillGradient := AValue;
  240. Invalidate;
  241. end;
  242. procedure TBGRAShape.SetUseRatioXY(const AValue: boolean);
  243. begin
  244. if FUseRatioXY = AValue then
  245. exit;
  246. FUseRatioXY := AValue;
  247. Invalidate;
  248. end;
  249. procedure TBGRAShape.Paint;
  250. var
  251. cx, cy, rx, ry, curRatio, a: single;
  252. coords: array of TPointF;
  253. minCoord, maxCoord: TPointF;
  254. i: integer;
  255. borderGrad, fillGrad: TBGRACustomScanner;
  256. scaling: Double;
  257. begin
  258. if FBGRA = nil then FBGRA := TBGRABitmap.Create;
  259. scaling := GetCanvasScaleFactor;
  260. FBGRA.SetSize(round(Width*scaling), round(Height*scaling));
  261. FBGRA.FillTransparent;
  262. FBGRA.PenStyle := FBorderStyle;
  263. with FBGRA.Canvas2D do
  264. begin
  265. lineJoin := 'round';
  266. if FUseBorderGradient then
  267. begin
  268. borderGrad := CreateGradient(FBorderGradient, Classes.rect(0, 0, FBGRA.Width, FBGRA.Height));
  269. strokeStyle(borderGrad);
  270. end
  271. else
  272. begin
  273. borderGrad := nil;
  274. strokeStyle(ColorToBGRA(ColorToRGB(FBorderColor), FBorderOpacity));
  275. end;
  276. lineStyle(FBGRA.CustomPenStyle);
  277. lineWidth := FBorderWidth*scaling;
  278. if FUseFillGradient then
  279. begin
  280. fillGrad := CreateGradient(FFillGradient, Classes.rect(0, 0, FBGRA.Width, FBGRA.Height));
  281. fillStyle(fillGrad);
  282. end
  283. else
  284. begin
  285. fillGrad := nil;
  286. fillStyle(ColorToBGRA(ColorToRGB(FFillColor), FFillOpacity));
  287. end;
  288. cx := FBGRA.Width / 2;
  289. cy := FBGRA.Height / 2;
  290. rx := (FBGRA.Width - FBorderWidth*scaling) / 2;
  291. ry := (FBGRA.Height - FBorderWidth*scaling) / 2;
  292. if FUseRatioXY and (ry <> 0) and (FRatioXY <> 0) then
  293. begin
  294. curRatio := rx / ry;
  295. if FRatioXY > curRatio then
  296. ry := ry / (FRatioXY / curRatio)
  297. else
  298. rx := rx / (curRatio / FRatioXY);
  299. end;
  300. if FShapeType = stRegularPolygon then
  301. begin
  302. setlength(coords, FSideCount);
  303. for i := 0 to high(coords) do
  304. begin
  305. a := (i / FSideCount + FAngle / 360) * 2 * Pi;
  306. coords[i] := PointF(sin(a), -cos(a));
  307. end;
  308. minCoord := coords[0];
  309. maxCoord := coords[0];
  310. for i := 1 to high(coords) do
  311. begin
  312. if coords[i].x < minCoord.x then
  313. minCoord.x := coords[i].x;
  314. if coords[i].y < minCoord.y then
  315. minCoord.y := coords[i].y;
  316. if coords[i].x > maxCoord.x then
  317. maxCoord.x := coords[i].x;
  318. if coords[i].y > maxCoord.y then
  319. maxCoord.y := coords[i].y;
  320. end;
  321. for i := 0 to high(coords) do
  322. begin
  323. with (coords[i] - minCoord) do
  324. coords[i] := PointF((x / (maxCoord.x - minCoord.x) - 0.5) *
  325. 2 * rx + cx, (y / (maxCoord.y - minCoord.y) - 0.5) * 2 * ry + cy);
  326. end;
  327. beginPath;
  328. for i := 0 to high(coords) do
  329. begin
  330. lineTo((coords[i] + coords[(i + 1) mod length(coords)]) * (1 / 2));
  331. arcTo(coords[(i + 1) mod length(coords)], coords[(i + 2) mod
  332. length(coords)], FRoundRadius);
  333. end;
  334. closePath;
  335. end
  336. else
  337. begin
  338. save;
  339. translate(cx, cy);
  340. scale(rx, ry);
  341. beginPath;
  342. arc(0, 0, 1, 0, 2 * Pi);
  343. restore;
  344. end;
  345. fill;
  346. if FBorderWidth <> 0 then
  347. stroke;
  348. fillStyle(BGRAWhite);
  349. strokeStyle(BGRABlack);
  350. fillGrad.Free;
  351. borderGrad.Free;
  352. end;
  353. FBGRA.Draw(Self.Canvas, rect(0,0,Width,Height), False);
  354. end;
  355. constructor TBGRAShape.Create(AOwner: TComponent);
  356. begin
  357. inherited Create(AOwner);
  358. with GetControlClassDefaultSize do
  359. SetInitialBounds(0, 0, CX, CY);
  360. FBGRA := nil;
  361. FBorderColor := clWindowText;
  362. FBorderOpacity := 255;
  363. FBorderWidth := 1;
  364. FBorderStyle := psSolid;
  365. FBorderGradient := TBCGradient.Create(Self);
  366. FBorderGradient.Point2XPercent := 100;
  367. FBorderGradient.StartColor := clWhite;
  368. FBorderGradient.EndColor := clBlack;
  369. FBorderGradient.OnChange := DoChangeGradient;
  370. FFillColor := clWindow;
  371. FFillOpacity := 255;
  372. FFillGradient := TBCGradient.Create(Self);
  373. FFillGradient.OnChange := DoChangeGradient;
  374. FRoundRadius := 0;
  375. FSideCount := 4;
  376. FRatioXY := 1;
  377. FUseRatioXY := False;
  378. end;
  379. destructor TBGRAShape.Destroy;
  380. begin
  381. FBGRA.Free;
  382. FFillGradient.Free;
  383. FBorderGradient.Free;
  384. inherited Destroy;
  385. end;
  386. {$IFDEF FPC}
  387. procedure TBGRAShape.SaveToFile(AFileName: string);
  388. var
  389. AStream: TMemoryStream;
  390. begin
  391. AStream := TMemoryStream.Create;
  392. try
  393. WriteComponentAsTextToStream(AStream, Self);
  394. AStream.SaveToFile(AFileName);
  395. finally
  396. AStream.Free;
  397. end;
  398. end;
  399. procedure TBGRAShape.LoadFromFile(AFileName: string);
  400. var
  401. AStream: TMemoryStream;
  402. begin
  403. AStream := TMemoryStream.Create;
  404. try
  405. AStream.LoadFromFile(AFileName);
  406. ReadComponentFromTextStream(AStream, TComponent(Self), OnFindClass);
  407. finally
  408. AStream.Free;
  409. end;
  410. end;
  411. {$ENDIF}
  412. procedure TBGRAShape.OnFindClass(Reader: TReader; const AClassName: string;
  413. var ComponentClass: TComponentClass);
  414. begin
  415. if CompareText(AClassName, 'TBGRAShape') = 0 then
  416. ComponentClass := TBGRAShape;
  417. end;
  418. end.