supergauge.pas 73 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264
  1. // SPDX-License-Identifier: LGPL-3.0-linking-exception
  2. {
  3. Part of BGRA Controls. Made by third party.
  4. For detailed information see readme.txt
  5. Site: https://sourceforge.net/p/bgra-controls/
  6. Wiki: http://wiki.lazarus.freepascal.org/BGRAControls
  7. Forum: http://forum.lazarus.freepascal.org/index.php/board,46.0.html
  8. }
  9. {******************************* CONTRIBUTOR(S) ******************************
  10. - Edivando S. Santos Brasil | [email protected]
  11. (Compatibility with delphi VCL 11/2018)
  12. - Sandy Ganz | [email protected]
  13. Evolved from DTAnalogCommon, specific for New Gauge Work
  14. Massive overhaul, fixes and features, begat Super Gauge
  15. Needed to split off as changes broke compatibility badly
  16. ***************************** END CONTRIBUTOR(S) *****************************}
  17. unit SuperGauge;
  18. {$I bgracontrols.inc}
  19. interface
  20. uses
  21. Classes, SysUtils, Graphics, {$IFDEF FPC}LResources,{$ELSE} BGRAGraphics, {$ENDIF} Forms, Controls, Dialogs, SuperGaugeCommon,
  22. BGRABitmap, BGRABitmapTypes, BGRAVectorize, BGRAPath, math, bctypes, bctools;
  23. const
  24. INTERNAL_GAUGE_MIN_VALUE = 0; // internal lowest value
  25. INTERNAL_GAUGE_MAX_VALUE = 270; // internal highest value
  26. VERSIONSTR = '1.02'; // SG version, Should ALWAYS show as a delta when merging!
  27. type
  28. { TSGCustomSuperGauge }
  29. TBandsArray = array[0..3] of TSGBandSettings;
  30. TTextsArray = array[0..2] of TSGTextSettings;
  31. TMarkersArray = array[0..2] of TSGMarkerSettings;
  32. TTextsBitmapArray = array[0..2] of TBGRABitmap;
  33. TSGRangeStateErrorEvent = procedure(Sender: TObject; OutOfRangeValue: single) of object; // called anytime out of range
  34. TSGRangeStateOKEvent = procedure(Sender: TObject; RangeValue: single) of object; // called only when back to in range
  35. TSGRangeStateChangeEvent = procedure(Sender: TObject; Value: single) of object; // called when state RangeLed Active changes to True
  36. TSGCustomSuperGauge = class(TGraphicControl)
  37. private
  38. { Private declarations }
  39. FDirty: boolean;
  40. FFaceSettings: TSGFaceSettings;
  41. FFrameSettings: TSGFrameSettings;
  42. FPointerCapSettings: TSGPointerCapSettings;
  43. FScaleSettings: TSGScaleSettings;
  44. FBandsSettings: TBandsArray;
  45. FTextsSettings: TTextsArray;
  46. FPointerSettings: TSGPointerSettings;
  47. FRangeLEDSettings: TSGRangeCheckLEDSettings;
  48. FMarkersSettings: TMarkersArray;
  49. FGaugeBitmap: TBGRABitmap;
  50. FFrameBitmap: TBGRABitmap;
  51. FFaceBitmap: TBGRABitmap;
  52. FTextBitmap: TBGRABitmap;
  53. FScaleBitmap: TBGRABitmap;
  54. FBandBitmap: TBGRABitmap;
  55. FTextsBitmaps: TTextsBitmapArray;
  56. FMultiBitmap: TBGRABitmap;
  57. FPointerBitmap: TBGRABitmap;
  58. FMarkerBitmap: TBGRABitmap;
  59. FPointerCapBitmap: TBGRABitmap;
  60. FLEDActiveBitmap: TBGRABitmap;
  61. FLEDInActiveBitmap: TBGRABitmap;
  62. FMinValue: single; // the min value mapped to lowest/leftmost angle on the gauge
  63. FMaxValue: single; // the max value mapped to highest/rightmost angle on the gauge
  64. FValue: single; // this is the VALUE not a position
  65. FOutOfRange: TSGRangeStateErrorEvent; // change of state ONLY
  66. FBackInRange: TSGRangeStateOKEvent; // change of state ONLY
  67. FRangeLEDActive: TSGRangeStateChangeEvent; // change of state ONLY
  68. FRangeLEDInactive: TSGRangeStateChangeEvent; // change of state ONLY
  69. FOutOfRangeState: boolean;
  70. FRangeLEDStateChanged: boolean;
  71. FAutoScale: boolean;
  72. procedure SetBandSettings1(AValue: TSGBandSettings);
  73. procedure SetBandSettings2(AValue: TSGBandSettings);
  74. procedure SetBandSettings3(AValue: TSGBandSettings);
  75. procedure SetBandSettings4(AValue: TSGBandSettings);
  76. procedure SetTextSettings1(AValue: TSGTextSettings);
  77. procedure SetTextSettings2(AValue: TSGTextSettings);
  78. procedure SetTextSettings3(AValue: TSGTextSettings);
  79. procedure SetMarkerSettings1(AValue: TSGMarkerSettings);
  80. procedure SetMarkerSettings2(AValue: TSGMarkerSettings);
  81. procedure SetMarkerSettings3(AValue: TSGMarkerSettings);
  82. procedure SetFaceSettings(AValue: TSGFaceSettings);
  83. procedure SetScaleSettings(AValue: TSGScaleSettings);
  84. procedure SetFrameSettings(AValue: TSGFrameSettings);
  85. procedure SetPointerSettings(AValue: TSGPointerSettings);
  86. procedure SetRangeLedSettings(AValue: TSGRangeCheckLEDSettings);
  87. procedure SetPointerCapSettings(AValue: TSGPointerCapSettings);
  88. procedure SetMaxValue(AValue: single);
  89. procedure SetMinValue(AValue: single);
  90. function GetMaxValue: single;
  91. function GetMinValue: single;
  92. procedure SetValue(AValue: single);
  93. function GetValue: single;
  94. procedure SetAutoScale(AValue: boolean);
  95. function CheckOutOfRange(AValue: single): single;
  96. protected
  97. { Protected declarations }
  98. procedure DoSetBounds(ALeft, ATop, AWidth, AHeight: Integer); override;
  99. procedure DoChange({%H-}Sender: TObject);
  100. procedure DoRangeLEDChange({%H-}Sender: TObject);
  101. procedure DoPictureChange({%H-}Sender: TObject);
  102. procedure DoChangeFont1({%H-}ASender: TObject; {%H-}AData: PtrInt); // Wrapper for FontEx DoChange
  103. procedure DoChangeFont2({%H-}ASender: TObject; {%H-}AData: PtrInt); // Wrapper for FontEx DoChange
  104. procedure DoChangeFont3({%H-}ASender: TObject; {%H-}AData: PtrInt); // Wrapper for FontEx DoChange
  105. procedure SetAllBandsDirtyState(AValue: boolean);
  106. procedure SetAllTextsDirtyState(AValue: boolean);
  107. procedure SetAllMarkersDirtyState(AValue: boolean);
  108. function IsAnyBandDirty: boolean;
  109. function IsAnyMarkerDirty: boolean;
  110. property Dirty: boolean read FDirty write FDirty;
  111. public
  112. { Public declarations }
  113. constructor Create(AOwner: TComponent); override;
  114. destructor Destroy; override;
  115. property PointerSettings: TSGPointerSettings read FPointerSettings write SetPointerSettings; // sjg added
  116. property PointerCapSettings: TSGPointerCapSettings read FPointerCapSettings write SetPointerCapSettings;
  117. property FaceSettings: TSGFaceSettings read FFaceSettings write SetFaceSettings;
  118. property FrameSettings: TSGFrameSettings read FFrameSettings write SetFrameSettings;
  119. property ScaleSettings: TSGScaleSettings read FScaleSettings write SetScaleSettings;
  120. property BandSettings1: TSGBandSettings read FBandsSettings[0] write SetBandSettings1; // will need an array thing here
  121. property BandSettings2: TSGBandSettings read FBandsSettings[1] write SetBandSettings2; // will need an array thing here
  122. property BandSettings3: TSGBandSettings read FBandsSettings[2] write SetBandSettings3; // will need an array thing here
  123. property BandSettings4: TSGBandSettings read FBandsSettings[3] write SetBandSettings4; // will need an array thing here
  124. property TextSettings1: TSGTextSettings read FTextsSettings[0] write SetTextSettings1;
  125. property TextSettings2: TSGTextSettings read FTextsSettings[1] write SetTextSettings2;
  126. property TextSettings3: TSGTextSettings read FTextsSettings[2] write SetTextSettings3;
  127. property RangeLedSettings: TSGRangeCheckLEDSettings read FRangeLEDSettings write SetRangeLedSettings;
  128. property MarkerSettings1: TSGMarkerSettings read FMarkersSettings[0] write SetMarkerSettings1;
  129. property MarkerSettings2: TSGMarkerSettings read FMarkersSettings[1] write SetMarkerSettings2;
  130. property MarkerSettings3: TSGMarkerSettings read FMarkersSettings[2] write SetMarkerSettings3;
  131. property MinValue: single read GetMinValue write SetMinValue default 0.0;
  132. property MaxValue: single read GetMaxValue write SetMaxValue default 100.0;
  133. property AutoScale: boolean read FAutoScale write SetAutoScale default False;
  134. property Value: single read GetValue write SetValue default 0.0;
  135. property OutOfRange: TSGRangeStateErrorEvent read FOutOfRange write FOutOfRange;
  136. property BackInRange: TSGRangeStateOKEvent read FBackInRange write FBackInRange;
  137. property RangeLEDActive: TSGRangeStateChangeEvent read FRangeLEDActive write FRangeLEDActive;
  138. property RangeLEDInActive: TSGRangeStateChangeEvent read FRangeLEDInactive write FRangeLEDInactive;
  139. function RemapRange(OldValue: single; OldMin, OldMax, NewMin, NewMax: single): single;
  140. function GaugeToUser(GaugeValue: single): single;
  141. function UserToGauge(UserValue: single): single;
  142. procedure Paint; override;
  143. procedure DrawFrame;
  144. procedure DrawFace;
  145. procedure DrawScale;
  146. procedure DrawBand(const BandSettings: TSGBandSettings);
  147. procedure DrawBands;
  148. procedure DrawMulti;
  149. procedure DrawText(TextBitmap: TBGRABitmap; const TextSettings: TSGTextSettings);
  150. procedure DrawLed;
  151. procedure DrawMarker(MarkerBitmap: TBGRABitmap; const MarkerSettings: TSGMarkerSettings);
  152. procedure DrawMarkers;
  153. procedure DrawPointer;
  154. procedure DrawPointerCap;
  155. function CheckRangeLED(AValue: single): boolean;
  156. end;
  157. { TSuperGauge }
  158. TSuperGauge = class(TSGCustomSuperGauge)
  159. private
  160. { Private declarations }
  161. protected
  162. { Protected declarations }
  163. public
  164. { Public declarations }
  165. published
  166. { Published declarations }
  167. property MinValue;
  168. property MaxValue;
  169. property FaceSettings;
  170. property BandSettings1;
  171. property BandSettings2;
  172. property BandSettings3;
  173. property BandSettings4;
  174. property TextSettings1;
  175. property TextSettings2;
  176. property TextSettings3;
  177. property FrameSettings;
  178. property ScaleSettings;
  179. property RangeLedSettings;
  180. property MarkerSettings1;
  181. property MarkerSettings2;
  182. property MarkerSettings3;
  183. property PointerSettings;
  184. property PointerCapSettings;
  185. property AutoScale;
  186. property Value;
  187. property OutOfRange;
  188. property BackInRange;
  189. property RangeLEDActive;
  190. property RangeLEDInactive;
  191. property Color default clNone;
  192. // Added missing events
  193. property Anchors;
  194. property OnClick;
  195. property OnDblClick;
  196. property OnMouseDown;
  197. property OnMouseUp;
  198. property OnMouseMove;
  199. property OnMouseEnter;
  200. property OnMouseLeave;
  201. end;
  202. {$IFDEF FPC}procedure Register;{$ENDIF}
  203. implementation
  204. {$IFDEF FPC}
  205. procedure Register;
  206. begin
  207. RegisterComponents('BGRA Controls', [TSuperGauge]);
  208. end;
  209. {$ENDIF}
  210. { TSGCustomSuperGauge }
  211. constructor TSGCustomSuperGauge.Create(AOwner: TComponent);
  212. var
  213. i: integer;
  214. begin
  215. inherited Create(AOwner);
  216. Width := 240;
  217. Height := 240;
  218. FFaceSettings := TSGFaceSettings.Create;
  219. FaceSettings.OnChange := DoChange;
  220. FaceSettings.Picture.OnChange := DoPictureChange; // need to set this so we can catch changes to the picture!
  221. FFrameSettings := TSGFrameSettings.Create;
  222. FrameSettings.OnChange := DoChange;
  223. FScaleSettings := TSGScaleSettings.Create;
  224. ScaleSettings.OnChange := DoChange;
  225. for i := low(FBandsSettings) to high(FBandsSettings) do
  226. begin
  227. FBandsSettings[i] := TSGBandSettings.Create;
  228. FBandsSettings[i].OnChange := DoChange;
  229. FBandsSettings[i].Text := 'Band ' + IntToStr(i + 1);
  230. end;
  231. for i := low(FTextsSettings) to high(FTextsSettings) do
  232. begin
  233. FTextsSettings[i] := TSGTextSettings.Create;
  234. FTextsSettings[i].OnChange := DoChange;
  235. FTextsBitmaps[i] := TBGRABitmap.Create;
  236. end;
  237. // Set Text font change events and defaults
  238. FTextsSettings[0].FontEx.OnChange := DoChangeFont1;
  239. FTextsSettings[1].FontEx.OnChange := DoChangeFont2;
  240. FTextsSettings[2].FontEx.OnChange := DoChangeFont3;
  241. FTextsSettings[0].Text := 'Text1';
  242. FTextsSettings[1].Text := 'Text2';
  243. FTextsSettings[2].Text := 'Text3';
  244. // Pointer Cap
  245. FPointerCapSettings := TSGPointerCapSettings.Create;
  246. FPointerCapSettings.OnChange := DoChange;
  247. // Pointer
  248. FPointerSettings := TSGPointerSettings.Create;
  249. FPointerSettings.OnChange := DoChange;
  250. FPointerSettings.Color := BGRA(255, 127, 63); // orange
  251. // RangeLED
  252. FRangeLEDSettings := TSGRangeCheckLEDSettings.Create;
  253. FRangeLEDSettings.OnChange := DoRangeLEDChange;
  254. // Markers
  255. for i := low(FMarkersSettings) to high(FMarkersSettings) do
  256. begin
  257. FMarkersSettings[i] := TSGMarkerSettings.Create;
  258. FMarkersSettings[i].OnChange := DoChange;
  259. end;
  260. // make marker each different to save confusion
  261. FMarkersSettings[0].Color := clLime;
  262. FMarkersSettings[1].Color := clRed;
  263. FMarkersSettings[2].Color := clYellow;
  264. // create needed bitmaps, Don't Forget to FREE!!!
  265. FFaceBitmap := TBGRABitmap.Create;
  266. FFrameBitmap := TBGRABitmap.Create;
  267. FGaugeBitmap := TBGRABitmap.Create;
  268. FTextBitmap := TBGRABitmap.Create;
  269. FPointerBitmap := TBGRABitmap.Create;
  270. FPointerCapBitmap := TBGRABitmap.Create;
  271. FScaleBitmap := TBGRABitmap.Create;
  272. FBandBitmap := TBGRABitmap.Create;
  273. FMultiBitmap := TBGRABitmap.Create;
  274. FLEDActiveBitmap := TBGRABitmap.Create;
  275. FLEDInActiveBitmap := TBGRABitmap.Create;
  276. FMarkerBitmap := TBGRABitmap.Create;
  277. // initialized (some above)
  278. FOutOfRangeState := False;
  279. FRangeLEDStateChanged := False;
  280. FValue := 0;
  281. FAutoScale := false;
  282. FMinValue := 0;
  283. FMaxValue := 100;
  284. Color := clNone;
  285. FDirty := True; // Always force initial paint/draw on everything!
  286. end;
  287. destructor TSGCustomSuperGauge.Destroy;
  288. var
  289. i: integer;
  290. begin
  291. FPointerCapSettings.OnChange := nil;
  292. FPointerCapSettings.Free;
  293. FPointerSettings.OnChange := nil;
  294. FPointerSettings.Free;
  295. FRangeLEDSettings.OnChange := nil;
  296. FRangeLEDSettings.Free;
  297. ScaleSettings.OnChange := nil;
  298. FScaleSettings.Free;
  299. for i := low(FTextsSettings) to high(FTextsSettings) do
  300. begin
  301. FBandsSettings[i].OnChange := nil;
  302. FBandsSettings[i].Free;
  303. end;
  304. for i := low(FTextsSettings) to high(FTextsSettings) do
  305. begin
  306. FTextsSettings[i].OnChange := nil;
  307. FTextsSettings[i].FontEx.OnChange := nil;
  308. FTextsSettings[i].Free;
  309. FTextsBitmaps[i].Free;
  310. end;
  311. for i := low(FMarkersSettings) to high(FMarkersSettings) do
  312. begin
  313. FMarkersSettings[i].OnChange := nil;
  314. FMarkersSettings[i].Free;
  315. end;
  316. FFaceSettings.OnChange := nil;
  317. FFaceSettings.Free;
  318. FFrameSettings.OnChange := nil;
  319. FFrameSettings.Free;
  320. // now clean bitmaps, should match what's in creat method
  321. FLEDActiveBitmap.Free;
  322. FLEDInactiveBitmap.Free;
  323. FMarkerBitmap.Free;
  324. FBandBitmap.Free;
  325. FScaleBitmap.Free;
  326. FPointerBitmap.Free;
  327. FPointerCapBitmap.Free;
  328. FTextBitmap.Free;
  329. FFaceBitmap.Free;
  330. FMultiBitmap.Free;
  331. FFrameBitmap.Free;
  332. FGaugeBitmap.Free;
  333. inherited Destroy;
  334. end;
  335. function TSGCustomSuperGauge.RemapRange(OldValue: single; OldMin, OldMax, NewMin, NewMax: single): single;
  336. begin
  337. // Generic mapping of ranges. Value is the number to remap, returns number
  338. // in the new range. Looks for odd div by 0 condition and fixes
  339. if OldMin = OldMax then
  340. begin
  341. // need to return something reasonable
  342. if OldValue <= OldMin then
  343. Exit(NewMin)
  344. else
  345. Exit(NewMax);
  346. end;
  347. Result := (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin;
  348. end;
  349. function TSGCustomSuperGauge.GaugeToUser(GaugeValue: single): single;
  350. begin
  351. // Helper to translates internal gauge value to external user value
  352. Result := RemapRange(GaugeValue, INTERNAL_GAUGE_MIN_VALUE, INTERNAL_GAUGE_MAX_VALUE, FMinValue, FMaxValue);
  353. end;
  354. function TSGCustomSuperGauge.UserToGauge(UserValue: single): single;
  355. begin
  356. // Helper to translates external user value to internal gauge value
  357. Result := RemapRange(UserValue, FMinValue, FMaxValue, INTERNAL_GAUGE_MIN_VALUE, INTERNAL_GAUGE_MAX_VALUE);
  358. end;
  359. function TSGCustomSuperGauge.GetValue: single;
  360. begin
  361. // Scale from internal back to user range
  362. Result := GaugeToUser(FValue);
  363. end;
  364. procedure TSGCustomSuperGauge.SetValue(AValue: single);
  365. var
  366. gaugeValue: single;
  367. begin
  368. // Tricky case here, since we are calling the RangeLED range check
  369. // here too, if that is in any way dirty we should process the value
  370. // and not skip. Triggering any change on RangeLEDSettings should call this.
  371. // Get the user value into gauge value space
  372. gaugeValue := UserToGauge(AValue);
  373. // skip if a few conditions exit. This is a bit tricky as the gauge value will reset
  374. // to min or max values on overload so need to always update if that's the case. Should
  375. // not affect performance. Similar for LED, if dirty no skip.
  376. if (FValue = gaugeValue) and (not FRangeLEDSettings.Dirty) and (not FOutOfRangeState) then
  377. Exit;
  378. // If out of range conditions are at play the gauge Value (FValue) will never
  379. // be out of range. This value is passed to the out of range handler for the
  380. // user to deal with and DO SOMETHING to indicate it.
  381. FValue := CheckOutOfRange(gaugeValue);
  382. // If we have a change in the of the LED's Active property we need
  383. // to call the event handlers too. Also we will check it's values and set
  384. // if needed. NOTE : that if the range type is set to rtNone, it will always
  385. // return the state of the RangeLEDSettings.Active, otherwise it will calculate
  386. // the needed value for a range check as set. FRangeLEDStateChanged is set in
  387. // IsRangeLEDActive function so should be called before this!
  388. // MUST NOT CALL .Active as this will cause a recursive call, use the
  389. // hacked ActiveNoDoChange which will just set the property value with
  390. // no side effects
  391. // True if LED Should be On, False if not, AValue is in User space for LED's
  392. FRangeLEDSettings.ActiveNoDoChange := CheckRangeLED(AValue);
  393. // We must dirty the Pointer here or no redraw
  394. PointerSettings.Dirty := True;
  395. DoChange(self);
  396. end;
  397. function TSGCustomSuperGauge.CheckOutOfRange(AValue: single): Single;
  398. begin
  399. // These values are in gauge space, so typically never less than 0, or > 270
  400. Result := AValue; // SAFE so always will return a value
  401. if AValue < INTERNAL_GAUGE_MIN_VALUE then
  402. begin
  403. // Under Range event
  404. FOutOfRangeState := True;
  405. if Assigned(FOutOfRange) then
  406. FOutOfRange(Self, GaugeToUser(AValue));
  407. Result := INTERNAL_GAUGE_MIN_VALUE;
  408. end
  409. else
  410. if AValue > INTERNAL_GAUGE_MAX_VALUE then
  411. begin
  412. // Over Range event
  413. FOutOfRangeState := True;
  414. if Assigned(FOutOfRange) then
  415. FOutOfRange(Self, GaugeToUser(AValue)); // must translate back to user space
  416. Result := INTERNAL_GAUGE_MAX_VALUE;
  417. end
  418. else
  419. begin
  420. // If NOT over/under flow then will need to clear
  421. // that state/flag and reset any indicators if was in a overange state
  422. if FOutOfRangeState then
  423. begin
  424. if Assigned(FBackInRange) then
  425. FBackInRange(self, GaugeToUser(AValue)); // here to, get into user space
  426. FOutOfRangeState := False; // reset so no more calls
  427. end;
  428. end;
  429. end;
  430. procedure TSGCustomSuperGauge.SetAutoScale(AValue: boolean);
  431. begin
  432. if FAutoScale = AValue then
  433. exit;
  434. FAutoScale := AValue;
  435. FScaleSettings.Dirty := True; // set it, as it will need a repaint
  436. DoChange(self);
  437. end;
  438. function TSGCustomSuperGauge.GetMaxValue: single;
  439. begin
  440. Result := FMaxValue;
  441. end;
  442. procedure TSGCustomSuperGauge.SetMaxValue(AValue: single);
  443. var
  444. currUser: single;
  445. begin
  446. // Note : MinValue and MaxValue can span negative ranges and be increasing
  447. // or decreasing
  448. // Min and Max out of order, bounce
  449. if (FMinValue >= AValue) then
  450. exit;
  451. // If changing min/max must refresh the value to adjust
  452. currUser := GaugeToUser(FValue);
  453. FMaxValue := AValue; // setting this will change UserToGauge() in SetValue!
  454. // Recompute
  455. SetValue(currUser);
  456. end;
  457. function TSGCustomSuperGauge.GetMinValue: single;
  458. begin
  459. Result := FMinValue;
  460. end;
  461. procedure TSGCustomSuperGauge.SetMinValue(AValue: single);
  462. var
  463. currUser: single;
  464. begin
  465. // Note : MinValue and MaxValue can span negative ranges and be increasing
  466. // or decreasing
  467. // Min and Max out of order, bounce
  468. if (FMaxValue <= AValue) then
  469. exit;
  470. // If changing min/max must refresh the value to adjust
  471. currUser := GaugeToUser(FValue);
  472. FMinValue := AValue; // setting this will change UserToGauge() in SetValue!
  473. // Recompute
  474. SetValue(currUser);
  475. end;
  476. procedure TSGCustomSuperGauge.SetFaceSettings(AValue: TSGFaceSettings);
  477. begin
  478. if FFaceSettings = AValue then
  479. Exit;
  480. FFaceSettings := AValue;
  481. FFaceSettings.Dirty := True; // set it, as it will need a repaint
  482. DoChange(self);
  483. end;
  484. procedure TSGCustomSuperGauge.SetFrameSettings(AValue: TSGFrameSettings);
  485. begin
  486. if FFrameSettings = AValue then
  487. Exit;
  488. FFrameSettings := AValue;
  489. FFrameSettings.Dirty := True; // set it, as it will need a repaint
  490. DoChange(self);
  491. end;
  492. procedure TSGCustomSuperGauge.SetScaleSettings(AValue: TSGScaleSettings);
  493. begin
  494. if FScaleSettings = AValue then
  495. Exit;
  496. FScaleSettings := AValue;
  497. FScaleSettings.Dirty := True;
  498. DoChange(self);
  499. end;
  500. procedure TSGCustomSuperGauge.SetAllBandsDirtyState(AValue: boolean);
  501. var
  502. i: integer;
  503. begin
  504. // helper to just set all bands to a specific state!
  505. for i := low(FBandsSettings) to high(FBandsSettings) do
  506. FBandsSettings[i].Dirty := AValue;
  507. end;
  508. function TSGCustomSuperGauge.IsAnyBandDirty : boolean;
  509. var
  510. i: integer;
  511. begin
  512. // helper to just see if any band has a dirty flag
  513. for i := low(FBandsSettings) to high(FBandsSettings) do
  514. begin
  515. if FBandsSettings[i].Dirty then
  516. exit(True);
  517. end;
  518. result := False;
  519. end;
  520. procedure TSGCustomSuperGauge.SetBandSettings1(AValue: TSGBandSettings);
  521. begin
  522. if FBandsSettings[0] = AValue then
  523. Exit;
  524. FBandsSettings[0] := AValue;
  525. FBandsSettings[0].Dirty := True;
  526. DoChange(self);
  527. end;
  528. procedure TSGCustomSuperGauge.SetBandSettings2(AValue: TSGBandSettings);
  529. begin
  530. if FBandsSettings[1] = AValue then
  531. Exit;
  532. FBandsSettings[1] := AValue;
  533. FBandsSettings[1].Dirty := True;
  534. DoChange(self);
  535. end;
  536. procedure TSGCustomSuperGauge.SetBandSettings3(AValue: TSGBandSettings);
  537. begin
  538. if FBandsSettings[2] = AValue then
  539. Exit;
  540. FBandsSettings[2] := AValue;
  541. FBandsSettings[2].Dirty := True;
  542. DoChange(self);
  543. end;
  544. procedure TSGCustomSuperGauge.SetBandSettings4(AValue: TSGBandSettings);
  545. begin
  546. if FBandsSettings[3] = AValue then
  547. Exit;
  548. FBandsSettings[3] := AValue;
  549. FBandsSettings[3].Dirty := True;
  550. DoChange(self);
  551. end;
  552. procedure TSGCustomSuperGauge.SetAllTextsDirtyState(AValue: boolean);
  553. var
  554. i: integer;
  555. begin
  556. // helper to just set all texts to a specific state!
  557. for i := low(FTextsSettings) to high(FTextsSettings) do
  558. FTextsSettings[i].Dirty := AValue;
  559. end;
  560. procedure TSGCustomSuperGauge.SetTextSettings1(AValue: TSGTextSettings);
  561. begin
  562. if FTextsSettings[0] = AValue then
  563. Exit;
  564. FTextsSettings[0] := AValue;
  565. FTextsSettings[0].Dirty := True; // set it, as it will need a repaint
  566. DoChange(self);
  567. end;
  568. procedure TSGCustomSuperGauge.SetTextSettings2(AValue: TSGTextSettings);
  569. begin
  570. if FTextsSettings[1] = AValue then
  571. Exit;
  572. FTextsSettings[1] := AValue;
  573. FTextsSettings[1].Dirty := True;
  574. DoChange(self);
  575. end;
  576. procedure TSGCustomSuperGauge.SetTextSettings3(AValue: TSGTextSettings);
  577. begin
  578. if FTextsSettings[2] = AValue then
  579. Exit;
  580. FTextsSettings[2] := AValue;
  581. FTextsSettings[2].Dirty := True;
  582. DoChange(self);
  583. end;
  584. function TSGCustomSuperGauge.IsAnyMarkerDirty: boolean;
  585. var
  586. i: integer;
  587. begin
  588. // helper to just see if any band has a dirty flag
  589. for i := low(FMarkersSettings) to high(FMarkersSettings) do
  590. begin
  591. if FMarkersSettings[i].Dirty then
  592. exit(True);
  593. end;
  594. result := False;
  595. end;
  596. procedure TSGCustomSuperGauge.SetAllMarkersDirtyState(AValue: boolean);
  597. var
  598. i: integer;
  599. begin
  600. // helper to just set all markers to a specific state!
  601. for i := low(FMarkersSettings) to high(FMarkersSettings) do
  602. FMarkersSettings[i].Dirty := AValue;
  603. end;
  604. procedure TSGCustomSuperGauge.SetMarkerSettings1(AValue: TSGMarkerSettings);
  605. begin
  606. if FMarkersSettings[0] = AValue then
  607. Exit;
  608. FMarkersSettings[0] := AValue;
  609. FMarkersSettings[0].Dirty := True;
  610. DoChange(self);
  611. end;
  612. procedure TSGCustomSuperGauge.SetMarkerSettings2(AValue: TSGMarkerSettings);
  613. begin
  614. if FMarkersSettings[1] = AValue then
  615. Exit;
  616. FMarkersSettings[1] := AValue;
  617. FMarkersSettings[1].Dirty := True;
  618. DoChange(self);
  619. end;
  620. procedure TSGCustomSuperGauge.SetMarkerSettings3(AValue: TSGMarkerSettings);
  621. begin
  622. if FMarkersSettings[2] = AValue then
  623. Exit;
  624. FMarkersSettings[2] := AValue;
  625. FMarkersSettings[2].Dirty := True;
  626. DoChange(self);
  627. end;
  628. procedure TSGCustomSuperGauge.SetPointerSettings(AValue: TSGPointerSettings);
  629. begin
  630. if FPointerSettings = AValue then
  631. Exit;
  632. FPointerSettings := AValue;
  633. FPointerSettings.Dirty := True;
  634. DoChange(self);
  635. end;
  636. procedure TSGCustomSuperGauge.SetRangeLedSettings(AValue: TSGRangeCheckLEDSettings);
  637. begin
  638. if FRangeLEDSettings = AValue then
  639. Exit;
  640. FRangeLEDSettings := AValue;
  641. FRangeLEDSettings.Dirty := True;
  642. DoChange(self);
  643. end;
  644. procedure TSGCustomSuperGauge.SetPointerCapSettings(AValue: TSGPointerCapSettings);
  645. begin
  646. if FPointerCapSettings = AValue then
  647. Exit;
  648. FPointerCapSettings := AValue;
  649. FPointerCapSettings.Dirty := True;
  650. DoChange(self);
  651. end;
  652. procedure TSGCustomSuperGauge.DoSetBounds(ALeft, ATop, AWidth, AHeight: Integer);
  653. begin
  654. inherited;
  655. FDirty := true; // Called on Resize of component
  656. end;
  657. procedure TSGCustomSuperGauge.DoChange(Sender: TObject);
  658. begin
  659. Invalidate;
  660. end;
  661. procedure TSGCustomSuperGauge.DoRangeLEDChange(Sender: TObject);
  662. begin
  663. // This is needed as anytime a RangeLED settings is updated we
  664. // MAY need to update and call event handlers. update as the RangeLEDSettings.Dirty
  665. CheckRangeLED(Value); // Tricky may not work!
  666. DoChange(self);
  667. end;
  668. procedure TSGCustomSuperGauge.DoPictureChange(Sender: TObject);
  669. begin
  670. // This is similar to DoRangeLEDChange, if we have a picture change this
  671. // is how we can propagate it up to the gauge to tell if a repaint is needed.
  672. FaceSettings.Dirty := True; // trigger a redraw since the image has changed
  673. DoChange(Sender);
  674. end;
  675. procedure TSGCustomSuperGauge.DoChangeFont1(ASender: TObject; AData: PtrInt);
  676. begin
  677. // Simlar to the regular dochange but needed a different procedure signature
  678. // so just a wrapper, TObject is not a gauge so use Self here for DoChange()
  679. FTextsSettings[0].Dirty := True;
  680. DoChange(self);
  681. end;
  682. procedure TSGCustomSuperGauge.DoChangeFont2(ASender: TObject; AData: PtrInt);
  683. begin
  684. // Simlar to the regular dochange but needed a different procedure signature
  685. // so just a wrapper, TObject is not a gauge so use Self here for DoChange()
  686. FTextsSettings[1].Dirty := True;
  687. DoChange(self);
  688. end;
  689. procedure TSGCustomSuperGauge.DoChangeFont3(ASender: TObject; AData: PtrInt);
  690. begin
  691. // Simlar to the regular dochange but needed a different procedure signature
  692. // so just a wrapper, TObject is not a gauge so use Self here for DoChange()
  693. FTextsSettings[2].Dirty := True;
  694. DoChange(self);
  695. end;
  696. procedure TSGCustomSuperGauge.Paint;
  697. var
  698. i: integer;
  699. offsetX, offsetY: integer;
  700. gaugeCenX, gaugeCenY: integer;
  701. begin
  702. inherited Paint;
  703. // IF the component is resized OR moved (this is safer) we
  704. // need to make sure EVERYTHING redraws. The base class will
  705. // also do it's own thing to invalidate and redraw it all.
  706. if FDirty then
  707. begin
  708. FFrameSettings.Dirty := True;
  709. FFaceSettings.Dirty := True;
  710. FScaleSettings.Dirty := True;
  711. SetAllBandsDirtyState(True);
  712. SetAllTextsDirtyState(True);
  713. FRangeLEDSettings.Dirty := True;
  714. FPointerCapSettings.Dirty := True;
  715. FPointerSettings.Dirty := True;
  716. SetAllMarkersDirtyState(True);
  717. FDirty := False; // everything here marked, so can reset
  718. end;
  719. // Now start Drawing into the offscreen bitmaps. IF the particular
  720. // subcomponent is not changed, the DrawXXXX will just leave it as is
  721. // and not waste cycles to redraw it.
  722. FGaugeBitmap.SetSize(Width, Height);
  723. // If the gauge color is clNone then we start with a transparent background,
  724. // Otherwise we start with the users color.
  725. if Color = clNone then
  726. FGaugeBitmap.Fill(BGRA(0, 0, 0, 0)) // fill transparent
  727. else
  728. FGaugeBitmap.Fill(ColorToBGRA(Color, 255)); // fill solid color
  729. gaugeCenX := FGaugeBitmap.Width div 2;
  730. gaugeCenY := FGaugeBitmap.Height div 2;
  731. // Face, Frame, Scale and Bands are usually static, so do yet another
  732. // bitmap for these that will require less Blend Images.
  733. DrawMulti;
  734. FGaugeBitmap.BlendImage(0, 0, FMultiBitmap, boLinearBlend);
  735. // now draw any texts if enabled and dirty
  736. for i := low(FTextsSettings) to high(FTextsSettings) do
  737. begin
  738. if FTextsSettings[i].Enabled then
  739. begin
  740. DrawText(FTextsBitmaps[i], FTextsSettings[i]);
  741. offsetX := FTextsSettings[i].OffsetX + gaugeCenX - FTextsBitmaps[i].Width div 2;
  742. offsetY := FTextsSettings[i].OffsetY + gaugeCenY - FTextsBitmaps[i].Height div 2;
  743. FGaugeBitmap.BlendImage(offsetX, offsetY, FTextsBitmaps[i], boLinearBlend);
  744. end;
  745. end;
  746. FGaugeBitmap.BlendImage(offsetX, offsetY, FTextBitmap, boLinearBlend);
  747. // Draw range LED, small bitmap so center and move
  748. DrawLed;
  749. offsetX := FRangeLEDSettings.OffsetX + gaugeCenX - FLEDActiveBitmap.Width div 2;
  750. offsetY := FRangeLEDSettings.OffsetY + gaugeCenY - FLEDActiveBitmap.height div 2;
  751. // set up the led, if user sets Active state will keep led on even if
  752. // the out of range state is set.
  753. if FRangeLEDSettings.Active then
  754. FGaugeBitmap.BlendImage(offsetX, offsetY, FLEDActiveBitmap, boLinearBlend)
  755. else
  756. FGaugeBitmap.BlendImage(offsetX, offsetY, FLEDInActiveBitmap, boLinearBlend);
  757. // Draw Markers BEFORE the pointer(s)
  758. DrawMarkers;
  759. FGaugeBitmap.BlendImage(0, 0, FMarkerBitmap,boLinearBlend);
  760. // draw cap over or under the pointer. Note that the pointer is a special
  761. // case when drawing since it's almost always dirty.
  762. if PointerCapSettings.CapStyle <> csNone then
  763. begin
  764. DrawPointerCap;
  765. offsetX := gaugeCenX - FPointerCapBitmap.Width div 2;
  766. offsetY := gaugeCenY - FPointerCapBitmap.Height div 2;
  767. if PointerCapSettings.CapPosition = cpOver then
  768. begin
  769. DrawPointer;
  770. FGaugeBitmap.BlendImage(offsetX, offsetY, FPointerCapBitmap, boLinearBlend); // Cap on top
  771. end
  772. else
  773. begin
  774. FGaugeBitmap.BlendImage(offsetX, offsetY, FPointerCapBitmap, boLinearBlend); // Cap on Bottom
  775. DrawPointer;
  776. end;
  777. end
  778. else
  779. DrawPointer;
  780. // make it all visable to the user!
  781. FGaugeBitmap.Draw(Canvas, 0, 0, False);
  782. end;
  783. procedure TSGCustomSuperGauge.DrawMulti;
  784. begin
  785. // The strategy here is that these typically only change infrequently
  786. // so if so, just draw as a bundle and saves some blendimages calls. Each of the
  787. // drawXXX still handles it's own dirty flag. The bitmap will be set up
  788. // as on instantiation so all of the others have their dirty flag set True, so no
  789. // need to do any initialization. Makes painting much faster even
  790. // with the individual dirty flags!
  791. if FFrameSettings.Dirty or FFaceSettings.Dirty or FScaleSettings.Dirty or IsAnyBandDirty then
  792. begin
  793. Initializebitmap(FMultiBitmap, Width, Height);
  794. DrawFrame;
  795. FMultiBitmap.BlendImage(0, 0, FFrameBitmap, boLinearBlend);
  796. DrawFace;
  797. FMultiBitmap.BlendImage(0, 0, FFaceBitmap, boLinearBlend);
  798. DrawBands; // will handle the enable/disable and draw of each band
  799. FMultiBitmap.BlendImage(0, 0, FBandBitmap, boLinearBlend);
  800. DrawScale;
  801. FMultiBitmap.BlendImage(0, 0, FScaleBitmap, boLinearBlend);
  802. end;
  803. end;
  804. procedure TSGCustomSuperGauge.DrawFrame;
  805. var
  806. Origin: TSGOrigin;
  807. r: integer;
  808. begin
  809. if not FrameSettings.Dirty then
  810. Exit;
  811. FrameSettings.Dirty := False;
  812. Origin := Initializebitmap(FFrameBitmap, Width, Height);
  813. // Always fills the space so AutoScale is sorta' always on
  814. r := round(Origin.Radius * 0.95);
  815. // Draw Bitmap frame
  816. FFrameBitmap.FillEllipseAntialias(Origin.CenterPoint.x,
  817. Origin.CenterPoint.y,
  818. r, r, FFrameSettings.FrameColor);
  819. // Draw thin antialiased border to smooth against background
  820. FFrameBitmap.EllipseAntialias(Origin.CenterPoint.x,
  821. Origin.CenterPoint.y,
  822. r, r, FFrameSettings.BorderColor, FFrameSettings.BorderRadius);
  823. end;
  824. procedure TSGCustomSuperGauge.DrawFace;
  825. var
  826. OriginFace: TSGOrigin;
  827. r, d: integer;
  828. xb, yb: integer;
  829. d2, h: single;
  830. Center: TPointF;
  831. v: TPointF;
  832. p: PBGRAPixel;
  833. Image: TBGRABitmap;
  834. Mask: TBGRABitmap;
  835. Map: TBGRABitmap;
  836. begin
  837. if not FaceSettings.Dirty then
  838. Exit;
  839. FaceSettings.Dirty := False;
  840. OriginFace := Initializebitmap(FFaceBitmap, Width, Height);
  841. // Always fills the space so AutoScale is sorta' always on for the face
  842. r := round(OriginFace.Radius * 0.95) - 5;
  843. // Fill types : fsNone, fsGradient, fsFlat, fsPhong
  844. case FFaceSettings.FillStyle of
  845. fsGradient:
  846. begin
  847. FFaceBitmap.FillEllipseLinearColorAntialias(OriginFace.CenterPoint.x,
  848. OriginFace.CenterPoint.y, r, r, FFaceSettings.OuterColor,
  849. FFaceSettings.InnerColor);
  850. end;
  851. fsFlat:
  852. begin
  853. FFaceBitmap.FillEllipseAntialias(OriginFace.CenterPoint.x, OriginFace.CenterPoint.y,
  854. r, r, FFaceSettings.InnerColor);
  855. end;
  856. fsPhong:
  857. begin
  858. d := r * 2;
  859. Center := PointF((d - 1) / 2, (d - 1) / 2);
  860. Map := TBGRABitmap.Create(d, d);
  861. for yb := 0 to d - 1 do
  862. begin
  863. p := Map.ScanLine[yb];
  864. for xb := 0 to d - 1 do
  865. begin
  866. // compute vector between center and current pixel
  867. v := PointF(xb, yb) - Center;
  868. // scale down to unit circle (with 1 pixel margin for soft border)
  869. v.x := v.x / (r + 1);
  870. v.y := v.y / (r + 1);
  871. // compute squared distance with scalar product
  872. d2 := v {$if FPC_FULLVERSION < 30203}*{$ELSE}**{$ENDIF} v;
  873. // interpolate as quadratic curve and apply power function
  874. if d2 > 1 then
  875. h := 0
  876. else
  877. h := power(1 - d2, FFaceSettings.CurveExponent);
  878. p^ := MapHeightToBGRA(h, 255);
  879. Inc(p);
  880. end;
  881. end;
  882. // mask image round with and antialiased border
  883. Mask := TBGRABitmap.Create(d, d, BGRABlack);
  884. Mask.FillEllipseAntialias(Center.x, Center.y, r, r, BGRAWhite);
  885. Map.ApplyMask(Mask);
  886. Mask.Free;
  887. // now draw
  888. FFaceSettings.FPhong.Draw(FFaceBitmap, Map, 30,
  889. OriginFace.CenterPoint.x - r, OriginFace.CenterPoint.y - r,
  890. FFaceSettings.InnerColor);
  891. Map.Free;
  892. end;
  893. end;
  894. // see if valid size and enabled, draw if so!
  895. if ((FaceSettings.Picture.Width > 0) or (FaceSettings.Picture.Height > 0)) and (FFaceSettings.PictureEnabled) then
  896. begin
  897. Image := TBGRABitmap.Create(FaceSettings.Picture.Bitmap);
  898. FFaceBitmap.BlendImage(
  899. OriginFace.CenterPoint.X + FaceSettings.PictureOffsetX,
  900. OriginFace.CenterPoint.y + FaceSettings.PictureOffsetY,
  901. image,
  902. boLinearBlend);
  903. Image.Free; // needed!
  904. end;
  905. end;
  906. procedure TSGCustomSuperGauge.DrawBands;
  907. var
  908. i: integer;
  909. begin
  910. // Draw mult bands on the same bitmap. we can do this since
  911. // we are drawing over the entire fullsized bitmap. Since
  912. // this is the case, you can draw all of the bands here in one shot
  913. // and on one bitmap. Init bitmap here!
  914. // Only change if something dirty
  915. // nothing dirty, no init, no draw, just bounce!
  916. if not IsAnyBandDirty then
  917. exit;
  918. Initializebitmap(FBandBitmap, Width, Height); // clear it before we draw anything
  919. for i := low(FBandsSettings) to high(FBandsSettings) do
  920. begin
  921. FBandsSettings[i].Dirty := True; // force draw, if any band is dirty they are are dirty
  922. DrawBand(FBandsSettings[i]); // will clear any dirty for specific band
  923. end;
  924. end;
  925. procedure TSGCustomSuperGauge.DrawBand(const BandSettings : TSGBandSettings);
  926. var
  927. BandRadius, TextRadius: single;
  928. TextSize: integer;
  929. baseAngle, startAngle, endAngle: single;
  930. cenX, cenY: integer;
  931. fontRenderer: TBGRAVectorizedFontRenderer;
  932. TextPath: TBGRAPath;
  933. begin
  934. // TODO : Maybe be removed since calling here always paints them all
  935. if not BandSettings.Dirty then
  936. Exit;
  937. BandSettings.Dirty := False;
  938. // Now, if not enabled we can leave if flag reset!
  939. if not BandSettings.Enabled then
  940. exit;
  941. TextSize := BandSettings.TextSize * 15;
  942. // Origin := Initializebitmap(FBandBitmap, Width, Height); drawbands needs to set this up
  943. cenX := Width div 2;
  944. cenY := Height div 2;
  945. BandRadius := BandSettings.BandRadius - BandSettings.Thickness div 2; // may need to adjust for band thickness
  946. TextRadius := BandSettings.TextRadius - BandSettings.TextSize div 2 - BandSettings.Thickness div 2; // offset to center
  947. // Start = 225 degree is 0 on gague scale (Not the angle), and -45 degree is 100 on scale
  948. // 270, down (gauge angle 0)180 flat, increase moves towards 0 decrease towards 100
  949. // 0 is flat line, right most end. Increase goes backwards towards 0, -45 is 100 percent on scale
  950. baseAngle := 225 * PI / 180;
  951. startAngle := baseAngle - ((BandSettings.StartValue * 270 / 100) * PI / 180);
  952. endAngle := baseAngle - ((BandSettings.EndValue * 270 / 100) * PI / 180);
  953. FBandBitmap.LineCap := pecFlat; // caps should be flat
  954. FBandBitmap.Arc(
  955. cenX, cenY,
  956. BandRadius + 0.5, BandRadius + 0.5, // push down a bit
  957. // (360-135) 225, -45
  958. // 3.92699,-0.785398, // must use start and end angle, internally Point calcs won't work due to arcsin2() limits
  959. startAngle, endAngle,
  960. BandSettings.BandColor,
  961. BandSettings.Thickness,
  962. false,
  963. BGRA(0,0,0,0) // last param is alpha, so no interior color, inner routings ONLY draw the arc, no fill
  964. );
  965. if BandSettings.EnableText then
  966. begin
  967. FontRenderer := TBGRAVectorizedFontRenderer.Create;
  968. FBandBitmap.FontRenderer := fontRenderer; // assign text vectorial font renderer
  969. FBandBitmap.FontHeight := round(TextSize * 0.09);
  970. FBandBitmap.FontQuality := fqFineAntialiasing;
  971. FBandBitmap.FontName := BandSettings.TextFont;
  972. FBandBitmap.FontStyle := BandSettings.TextStyle;
  973. FontRenderer.OutlineColor := BGRABlack;
  974. FontRenderer.OutlineWidth := TextSize / 600;
  975. FontRenderer.OutlineVisible := true;
  976. FBandBitmap.FontVerticalAnchor := fvaBaseline;
  977. TextPath := TBGRAPath.Create;
  978. // drawing is backwards on textpath
  979. TextPath.Arc(cenX, cenY, TextRadius, -startAngle, -endAngle, False);
  980. FBandBitmap.TextOutCurved(TextPath, BandSettings.Text, BandSettings.TextColor, taCenter, 0);
  981. end;
  982. end;
  983. procedure TSGCustomSuperGauge.DrawText(TextBitmap: TBGRABitmap; const TextSettings: TSGTextSettings);
  984. var
  985. TextBoxWidth, TextBoxHeight: integer;
  986. TextRect: TRect;
  987. begin
  988. if not TextSettings.Dirty then
  989. Exit;
  990. TextSettings.Dirty := False;
  991. // get the bounding box so we can create a SMALLER bitmap. This will be referenced
  992. // to the Center of the text and gauge
  993. CalculateTextSize(TextSettings.Text, TextSettings.FontEx, TextBoxWidth, TextBoxHeight, TextSettings.FontEx.Shadow);
  994. Initializebitmap(TextBitmap, TextBoxWidth, TextBoxHeight);
  995. // Set up text bounding box,
  996. TextRect.Left := 0;
  997. TextRect.Top := 0;
  998. TextRect.Height := TextBoxHeight;
  999. TextRect.Width := TextBoxWidth;
  1000. // Draw into the TextBitmap for later use
  1001. RenderText(TextRect, TextSettings.FontEx, TextSettings.Text, TextBitmap, Enabled);
  1002. end;
  1003. procedure TSGCustomSuperGauge.DrawScale;
  1004. var
  1005. Origin: TSGOrigin;
  1006. i, n, x, y, xt, yt: integer;
  1007. scaleStartValue, scaleBump: integer;
  1008. ScaleRadius, TextRadius: single;
  1009. TextSize: integer;
  1010. pStart, pEnd: TPointF;
  1011. startAngle, endAngle: single;
  1012. innerTicRadius: single;
  1013. begin
  1014. // if nothing dirty then skip it, we have a bitmap with
  1015. // the scale already drawn. This is slow so saves a lot of time
  1016. // as scales are slow to draw
  1017. if not ScaleSettings.Dirty then
  1018. Exit;
  1019. ScaleSettings.Dirty := False; // mark as clean, so next run will not need a rebuild!
  1020. Origin := Initializebitmap(FScaleBitmap, Width, Height);
  1021. // Calc radius for scale and text or set it from the user
  1022. if FAutoScale then
  1023. begin
  1024. ScaleRadius := Round(Origin.Radius * 0.90);
  1025. TextRadius := Round(Origin.Radius * 0.65);
  1026. TextSize := Round(Origin.Radius * 0.15);
  1027. // fix up scaling for small or large gauges
  1028. if (Width < 250) or (Height < 250) then
  1029. begin
  1030. TextSize := 15;
  1031. TextRadius := TextRadius - 10;
  1032. end
  1033. else
  1034. begin
  1035. if (Width > 500) or (Height > 500) then
  1036. begin
  1037. TextSize := TextSize + 5;
  1038. TextRadius := TextRadius + 10;
  1039. end;
  1040. end;
  1041. end
  1042. else
  1043. begin
  1044. ScaleRadius := ScaleSettings.ScaleRadius;
  1045. TextRadius := ScaleSettings.TextRadius;
  1046. TextSize := ScaleSettings.TextSize;
  1047. end;
  1048. // Draw SubTicks
  1049. if ScaleSettings.EnableSubTicks then
  1050. begin
  1051. n := ScaleSettings.MainTickCount * ScaleSettings.SubTickCount;
  1052. for i := 0 to n do
  1053. begin
  1054. // Calculate draw from point
  1055. X := Origin.CenterPoint.x - Round(ScaleRadius * cos((-45 + i * 270 / n) * Pi / 180));
  1056. Y := Origin.CenterPoint.y - Round(ScaleRadius * sin((-45 + i * 270 / n) * Pi / 180));
  1057. // Calculate draw to point
  1058. Xt := Origin.CenterPoint.x - Round((ScaleRadius - ScaleSettings.LengthSubTick) *
  1059. cos((-45 + i * 270 / n) * Pi / 180));
  1060. Yt := Origin.CenterPoint.y - Round((ScaleRadius - ScaleSettings.LengthSubTick) *
  1061. sin((-45 + i * 270 / n) * Pi / 180));
  1062. FScaleBitmap.DrawLineAntialias(x, y, xt, yt, ScaleSettings.TickColor, ScaleSettings.ThicknessSubTick);
  1063. if (ScaleSettings.TickArcStyle = taboth) and (not ScaleSettings.EnableMainTicks) then
  1064. begin
  1065. // need caps on the ends so the gauge doesn't look stupid if both inner and outer
  1066. // tic arcs are visiable
  1067. if (i = 0) or (i = n) then
  1068. begin
  1069. if not ScaleSettings.EnableMainTicks then
  1070. innerTicRadius := ScaleSettings.LengthSubTick
  1071. else
  1072. innerTicRadius := ScaleSettings.LengthMainTick;
  1073. // draw end pieces in the MainTick thickness to match
  1074. Xt := Origin.CenterPoint.x - Round((ScaleRadius - innerTicRadius) *
  1075. cos((-45 + i * 270 / n) * Pi / 180));
  1076. Yt := Origin.CenterPoint.y - Round((ScaleRadius - innerTicRadius) *
  1077. sin((-45 + i * 270 / n) * Pi / 180));
  1078. FScaleBitmap.DrawLineAntialias(x, y, xt, yt, ScaleSettings.TickColor,
  1079. ScaleSettings.ThicknessMainTick);
  1080. end;
  1081. end;
  1082. end;
  1083. end;
  1084. // Draw after the sub-tics
  1085. if ScaleSettings.EnableMainTicks then
  1086. begin
  1087. n := ScaleSettings.MainTickCount;
  1088. for i := 0 to n do
  1089. begin
  1090. // Draw main ticks
  1091. // Calculate draw from point bottom
  1092. x := Origin.CenterPoint.x - Round(ScaleRadius * cos((-45 + i * 270 / n) * Pi / 180));
  1093. y := Origin.CenterPoint.y - Round(ScaleRadius * sin((-45 + i * 270 / n) * Pi / 180));
  1094. // Calculate draw to point top
  1095. xt := Origin.CenterPoint.x - Round((ScaleRadius - ScaleSettings.LengthMainTick) *
  1096. cos((-45 + i * 270 / n) * Pi / 180));
  1097. yt := Origin.CenterPoint.y - Round((ScaleRadius - ScaleSettings.LengthMainTick) *
  1098. sin((-45 + i * 270 / n) * Pi / 180));
  1099. FScaleBitmap.DrawLineAntialias(x, y, xt, yt, ScaleSettings.TickColor, ScaleSettings.ThicknessMainTick);
  1100. end;
  1101. end;
  1102. // Draw text, these are only for the Main Ticks
  1103. if ScaleSettings.EnableScaleText then
  1104. begin
  1105. FScaleBitmap.FontName := ScaleSettings.TextFont;
  1106. FScaleBitmap.FontHeight := TextSize;
  1107. FScaleBitmap.FontQuality := fqFineAntialiasing;
  1108. FScaleBitmap.FontStyle := FScaleSettings.TextStyle;
  1109. n := ScaleSettings.MainTickCount;
  1110. // if draw the scale reversed, do some tricky stuff so we can
  1111. // count up or down. Start is swapped with the actual end value here
  1112. if ScaleSettings.ReverseScale then
  1113. begin
  1114. scaleBump := -1;
  1115. scaleStartValue := n * ScaleSettings.Step + ScaleSettings.Start;
  1116. end
  1117. else
  1118. begin
  1119. scaleBump := 1;
  1120. scaleStartValue := ScaleSettings.Start;
  1121. end;
  1122. // Draw text for main ticks
  1123. for i := 0 to n do
  1124. begin
  1125. xt := Origin.CenterPoint.x - Round(TextRadius * cos((-45 + i * 270 / n) * Pi / 180));
  1126. yt := Origin.CenterPoint.y - Round(TextRadius * sin((-45 + i * 270 / n) * Pi / 180));
  1127. FScaleBitmap.TextOut(xt, yt - (FScaleBitmap.FontHeight / 1.7),
  1128. IntToStr(scaleStartValue + i * ScaleSettings.Step * scaleBump),
  1129. ScaleSettings.TextColor, taCenter);
  1130. end;
  1131. end;
  1132. // draw outer rings/bands
  1133. if (ScaleSettings.TickArcStyle = taOuter) or (ScaleSettings.TickArcStyle = taboth) then
  1134. begin
  1135. // draw arc OUSIDE on the tics, doesn't matter main or sub, all at the top
  1136. // inner of tic
  1137. pStart.x := Origin.CenterPoint.x - Round(ScaleRadius * cos(-45 * Pi / 180));
  1138. pStart.y := Origin.CenterPoint.y - Round(ScaleRadius * sin(-45 * Pi / 180));
  1139. startAngle := arctan2((Origin.CenterPoint.y - pStart.y),(Origin.CenterPoint.x - pStart.x)) + 4.71239; // add 270
  1140. // Calculate draw to point outer
  1141. pEnd.x := Origin.CenterPoint.x - Round((ScaleRadius - ScaleSettings.LengthMainTick) * cos(225 * Pi / 180));
  1142. pEnd.y := Origin.CenterPoint.y - Round((ScaleRadius - ScaleSettings.LengthMainTick) * sin(225 * Pi / 180));
  1143. endAngle := -arctan2((pEnd.y - Origin.CenterPoint.y),(pEnd.x - Origin.CenterPoint.x));
  1144. FScaleBitmap.Arc(
  1145. Origin.CenterPoint.x, Origin.CenterPoint.y,
  1146. ScaleRadius + 0.5, ScaleRadius + 0.5, // push down a bit
  1147. startAngle, endAngle,
  1148. ScaleSettings.TickColor,
  1149. ScaleSettings.ThicknessMainTick,
  1150. false,
  1151. BGRA(0,0,0,0) // last param is alpha, so no interior color, inner routings ONLY draw the arc, no fill
  1152. );
  1153. end;
  1154. if (ScaleSettings.TickArcStyle = taInner) or (ScaleSettings.TickArcStyle = taBoth) then
  1155. begin
  1156. // Inner will chose main tics (for now) if both main and sub tics on)
  1157. // will need to find out the radius for what selected... or do something
  1158. // like use what ever tic is LONGER (logic here will need a change)
  1159. // draw arc OUSIDE on the tics, doesn't matter main or sub, all at the top
  1160. // inner of tick
  1161. pStart.x := Origin.CenterPoint.x - Round(ScaleRadius * cos(-45 * Pi / 180));
  1162. pStart.y := Origin.CenterPoint.y - Round(ScaleRadius * sin(-45 * Pi / 180));
  1163. startAngle := arctan2((Origin.CenterPoint.y - pStart.y),(Origin.CenterPoint.x - pStart.x)) + 4.71239; // add 270
  1164. // Calculate draw to point outer
  1165. pEnd.x := Origin.CenterPoint.x - Round((ScaleRadius - ScaleSettings.LengthMainTick) * cos(225 * Pi / 180));
  1166. pEnd.y := Origin.CenterPoint.y - Round((ScaleRadius - ScaleSettings.LengthMainTick) * sin(225 * Pi / 180));
  1167. endAngle := -arctan2((pEnd.y - Origin.CenterPoint.y),(pEnd.x - Origin.CenterPoint.x));
  1168. // be nice and if not displaying main tics, use the sub tic length to bottom
  1169. // up against them
  1170. if not ScaleSettings.EnableMainTicks then
  1171. innerTicRadius := ScaleSettings.LengthSubTick
  1172. else
  1173. innerTicRadius := ScaleSettings.LengthMainTick;
  1174. FScaleBitmap.Arc(
  1175. Origin.CenterPoint.x, Origin.CenterPoint.y,
  1176. ScaleRadius - 0.5 - innerTicRadius, ScaleRadius - 0.5 - innerTicRadius,
  1177. startAngle, endAngle,
  1178. ScaleSettings.TickColor,
  1179. ScaleSettings.ThicknessMainTick,
  1180. false,
  1181. BGRA(0,0,0,0) // last param is alpha, so no interior color, inner routings ONLY draw the arc, no fill
  1182. );
  1183. end;
  1184. end;
  1185. procedure TSGCustomSuperGauge.DrawPointer;
  1186. var
  1187. Origin: TSGOrigin;
  1188. x, y, x1, y1, extLen: integer;
  1189. commonSubEx: single;
  1190. PointerLength: single;
  1191. startAngle, endAngle: single;
  1192. bandRadius: single;
  1193. vecLen: single;
  1194. A, B, U, V: TPointF;
  1195. begin
  1196. // Note : Min and max values are the GAUGE Settings, not the Scales,
  1197. // the scale display is independant of the value of the gauge to
  1198. // allow for multiple pointers if later needed
  1199. if not PointerSettings.Dirty then
  1200. Exit;
  1201. Origin.CenterPoint.X:= FGaugeBitmap.Width div 2;
  1202. Origin.CenterPoint.Y:= FGaugeBitmap.Height div 2;
  1203. // radius is smaller of the 2 dimensions
  1204. if Origin.CenterPoint.x < Origin.CenterPoint.y then
  1205. Origin.Radius := Origin.CenterPoint.x
  1206. else
  1207. Origin.Radius := Origin.CenterPoint.Y;
  1208. // Set the pointer length, does not apply to arc
  1209. if FAutoScale then
  1210. begin
  1211. PointerLength := Round(Origin.Radius * 0.85);
  1212. end
  1213. else
  1214. begin
  1215. PointerLength := PointerSettings.Length;
  1216. end;
  1217. // draw the arc style of pointer
  1218. if (PointerSettings.Style = psLine) or (PointerSettings.Style = psLineExt) then
  1219. begin
  1220. // if we are need to draw the extension behind the cap, we can
  1221. // recalc the ending point to just do one line draw instead of
  1222. // 2 discrete lines from the center. That is easier, but slower
  1223. // If extension len is 0, skip as will show a partial pixel
  1224. FGaugeBitMap.LineCap := pecRound; // caps should be round for line type pointers
  1225. if (PointerSettings.Style = psLineExt) and (PointerSettings.ExtensionLength > 0) then
  1226. begin
  1227. // The extension is always pixels visable from the center or edge of the
  1228. // cap, fix as needed. Makes nice for the user.
  1229. if PointerCapSettings.CapStyle = csNone then
  1230. extLen := PointerSettings.ExtensionLength
  1231. else
  1232. extLen := PointerSettings.ExtensionLength + PointerCapSettings.Radius;
  1233. // compute end point of pointer if an extension
  1234. commonSubEx := (-225 + FValue) * Pi / 180;
  1235. x1 := Origin.CenterPoint.x - Round(extLen * cos(commonSubEx));
  1236. y1 := Origin.CenterPoint.y - Round(extLen * sin(commonSubEx));
  1237. end
  1238. else
  1239. begin
  1240. // no extension or extension length is 0, just draw to center
  1241. x1 := Origin.CenterPoint.x;
  1242. y1 := Origin.CenterPoint.y;
  1243. end;
  1244. // computer start point of pointer
  1245. commonSubEx := (-45 + FValue) * Pi / 180;
  1246. x := Origin.CenterPoint.x - Round(PointerLength * cos(commonSubEx));
  1247. y := Origin.CenterPoint.y - Round(PointerLength * sin(commonSubEx));
  1248. // finally draw it
  1249. FGaugeBitMap.DrawLineAntialias(x, y, x1, y1, PointerSettings.Color, PointerSettings.Thickness)
  1250. end
  1251. else
  1252. if PointerSettings.Style = psTriangle then
  1253. begin
  1254. // Draw a Triangle style pointer
  1255. // Draw from center point out
  1256. commonSubEx := (-45 + FValue) * Pi / 180;
  1257. x := Origin.CenterPoint.x;
  1258. y := Origin.CenterPoint.y;
  1259. A := PointF(x, y);
  1260. // Calculate draw to point top
  1261. x1 := Origin.CenterPoint.x - Round(PointerSettings.Length * cos(commonSubEx));
  1262. y1 := Origin.CenterPoint.y - Round(PointerSettings.Length * sin(commonSubEx));
  1263. B := PointF(x1, y1);
  1264. // set line cap just in case
  1265. FMarkerBitmap.LineCap := pecRound; // Ensure Round Cap
  1266. // This is the vector that runs from outer to inner
  1267. U := B - A;
  1268. // build the perpendicular vector
  1269. // (clockwise in screen coordinates while the opposite would be counter clockwise)
  1270. V := PointF(-U.y, U.x);
  1271. // scale it to set the new segment length
  1272. vecLen := VectLen(V);
  1273. // catch odd case of zero len vector, do nothing
  1274. if vecLen = 0.0 then
  1275. Exit;
  1276. V := V * (PointerSettings.Thickness / vecLen);
  1277. // draw a full triangle pointer
  1278. FGaugeBitMap.FillPolyAntialias([B, A + V, A - V], PointerSettings.Color);
  1279. end
  1280. else
  1281. if PointerSettings.Style = psArc then
  1282. begin
  1283. // drawn arc pointer, ensure not negative or crash, zero no need to draw
  1284. if FValue <= 0.0 then
  1285. Exit;
  1286. BandRadius := PointerLength - PointerSettings.Thickness div 2; // adjust for band thickness so end of pointer is top
  1287. // Start = 225 degree is 0 on gague scale (Not the angle), and -45 degree is 100 on scale
  1288. // 270, down (gauge angle 0)180 flat, increase moves towards 0 decrease towards 100
  1289. // 0 is flat line, right most end. Increase goes backwards towards 0, -45 is 100 percent on scale
  1290. startAngle := 225 * PI / 180; // start at 0 on the gauge
  1291. endAngle := startAngle - FValue * PI / 180;
  1292. FGaugeBitMap.LineCap := pecFlat; // caps should be flat, rounded does not align to scales well
  1293. FGaugeBitMap.Arc(
  1294. Origin.CenterPoint.x, Origin.CenterPoint.y,
  1295. BandRadius + 0.5, BandRadius + 0.5, // push down a bit
  1296. startAngle, endAngle,
  1297. PointerSettings.Color,
  1298. PointerSettings.Thickness,
  1299. false,
  1300. BGRA(0,0,0,0) // last param is alpha, so no interior color, inner routings ONLY draw the arc, no fill
  1301. );
  1302. end;
  1303. end;
  1304. procedure TSGCustomSuperGauge.DrawPointerCap;
  1305. var
  1306. Origin: TSGOrigin;
  1307. sizeWH : integer;
  1308. pCapEdge : integer;
  1309. tx, ty: integer;
  1310. h: single;
  1311. d2: single;
  1312. v: TPointF;
  1313. p: PBGRAPixel;
  1314. Center: TPointF;
  1315. yb: integer;
  1316. xb: integer;
  1317. mask: TBGRABitmap;
  1318. Map: TBGRABitmap;
  1319. begin
  1320. // skip drawing if nothing changed
  1321. if not PointerCapSettings.Dirty then
  1322. Exit;
  1323. PointerCapSettings.Dirty := False;
  1324. // drawing is the size of the cap, not of the entire gauge!
  1325. sizeWH := (PointerCapSettings.Radius + PointerCapSettings.EdgeThickness) * 2 + 2;
  1326. Origin := Initializebitmap(FPointerCapBitmap, SizeWH, SizeWH);
  1327. pCapEdge := PointerCapSettings.Radius + PointerCapSettings.EdgeThickness div 2;
  1328. if PointerCapSettings.CapStyle = csFlat then
  1329. begin
  1330. // Draw the flat cap, but make sure size is similar to the shaded below or will be odd
  1331. FPointerCapBitmap.EllipseAntialias(Origin.CenterPoint.x, Origin.CenterPoint.y,
  1332. pCapEdge,
  1333. pCapEdge,
  1334. PointerCapSettings.EdgeColor,
  1335. PointerCapSettings.EdgeThickness,
  1336. PointerCapSettings.FillColor);
  1337. end
  1338. else
  1339. begin
  1340. tx := PointerCapSettings.Radius * 2; // keeps size consistent with flat cap
  1341. ty := tx;
  1342. if (tx = 0) or (ty = 0) then
  1343. Exit;
  1344. if PointerCapSettings.CapStyle = csPhong then
  1345. begin
  1346. //compute knob height map
  1347. Center := PointF((tx - 1) / 2, (ty - 1) / 2);
  1348. Map := TBGRABitmap.Create(tx, ty);
  1349. for yb := 0 to ty - 1 do
  1350. begin
  1351. p := map.ScanLine[yb];
  1352. for xb := 0 to tx - 1 do
  1353. begin
  1354. //compute vector between center and current pixel
  1355. v := PointF(xb, yb) - Center;
  1356. //scale down to unit circle (with 1 pixel margin for soft border)
  1357. v.x := v.x / (tx / 2 + 1);
  1358. v.y := v.y / (ty / 2 + 1);
  1359. //compute squared distance with scalar product
  1360. d2 := v {$if FPC_FULLVERSION < 30203}*{$ELSE}**{$ENDIF} v;
  1361. //interpolate as quadratic curve and apply power function
  1362. if d2 > 1 then
  1363. h := 0
  1364. else
  1365. h := power(1 - d2, PointerCapSettings.CurveExponent);
  1366. p^ := MapHeightToBGRA(h, 255);
  1367. Inc(p);
  1368. end;
  1369. end;
  1370. // mask image round with and antialiased border
  1371. mask := TBGRABitmap.Create(tx, ty, BGRABlack);
  1372. Mask.FillEllipseAntialias(Center.x, Center.y, tx / 2, ty / 2, BGRAWhite);
  1373. map.ApplyMask(mask);
  1374. Mask.Free;
  1375. // now draw
  1376. PointerCapSettings.FPhong.Draw(FPointerCapBitmap, Map, 30,
  1377. origin.CenterPoint.x - tx div 2, origin.CenterPoint.y - ty div 2,
  1378. PointerCapSettings.FillColor);
  1379. Map.Free;
  1380. // Draw a flat radius around the cap if set, must be alpha 0 or will not
  1381. // be an outline
  1382. if PointerCapSettings.EdgeThickness > 0 then
  1383. FPointerCapBitmap.EllipseAntialias(origin.CenterPoint.x, origin.CenterPoint.y,
  1384. pCapEdge,
  1385. pCapEdge,
  1386. PointerCapSettings.EdgeColor,
  1387. PointerCapSettings.EdgeThickness, BGRA(0,0,0,0));
  1388. end
  1389. else
  1390. begin
  1391. // Regular shading
  1392. FPointerCapBitmap.FillEllipseLinearColorAntialias(origin.CenterPoint.x, origin.CenterPoint.y,
  1393. pCapEdge,
  1394. pCapEdge,
  1395. PointerCapSettings.FillColor,
  1396. PointerCapSettings.EdgeColor
  1397. );
  1398. // draw edge since the shading is backwards ending on fill color not Edge
  1399. FPointerCapBitmap.EllipseAntialias(origin.CenterPoint.x, origin.CenterPoint.y,
  1400. pCapEdge,
  1401. pCapEdge,
  1402. PointerCapSettings.EdgeColor,
  1403. PointerCapSettings.EdgeThickness, BGRA(0,0,0,0)
  1404. );
  1405. end;
  1406. end;
  1407. end;
  1408. procedure TSGCustomSuperGauge.DrawLed;
  1409. var
  1410. Origin: TSGOrigin;
  1411. sizeWH : integer;
  1412. mask: TBGRABitmap;
  1413. begin
  1414. // skip drawing if nothing changed or not drawn
  1415. if not FRangeLEDSettings.Dirty then
  1416. Exit;
  1417. FRangeLEDSettings.Dirty := False;
  1418. // compute the size needed NOT a full gauge bitmap
  1419. sizeWH := FRangeLEDSettings.Size * 2 + 2; // square size at lease LED radius and a bit more
  1420. Origin := Initializebitmap(FLEDActiveBitmap, sizeWH, sizeWH);
  1421. Initializebitmap(FLEDInActiveBitmap, sizeWH, sizeWH);
  1422. // offset must be done later in the Paint proc to
  1423. // keep bitmap small so the center point is the centerpoint of the bitmap
  1424. // The caller MUST move to the correct offset
  1425. // draw both active and inactive so we never need to do either unless props changed
  1426. // need to find/get x, y to place the LED
  1427. if RangeLEDSettings.Shape = lshRound then
  1428. begin
  1429. if FRangeLEDSettings.Style = lsFlat then
  1430. begin
  1431. FLEDActiveBitmap.EllipseAntialias(Origin.CenterPoint.x, Origin.CenterPoint.y,
  1432. FRangeLEDSettings.Size,
  1433. FRangeLEDSettings.Size,
  1434. FRangeLEDSettings.BorderColor,
  1435. 1,
  1436. FRangeLEDSettings.ActiveColor);
  1437. end
  1438. else
  1439. if FRangeLEDSettings.Style = lsShaded then
  1440. begin
  1441. // draw shaded, could do better here but good for starts
  1442. FLEDActiveBitmap.FillEllipseLinearColorAntialias(
  1443. Origin.CenterPoint.x,
  1444. Origin.CenterPoint.y,
  1445. FRangeLEDSettings.Size,
  1446. FRangeLEDSettings.Size,
  1447. FRangeLEDSettings.InactiveColor,
  1448. FRangeLEDSettings.ActiveColor);
  1449. // draw border
  1450. FLEDActiveBitmap.EllipseAntialias(
  1451. Origin.CenterPoint.x, Origin.CenterPoint.y,
  1452. FRangeLEDSettings.Size,
  1453. FRangeLEDSettings.Size,
  1454. FRangeLEDSettings.BorderColor,
  1455. 1,
  1456. BGRA(0,0,0,0)); // fill transparent
  1457. end;
  1458. // Simple flat round for inactive always
  1459. if RangeLedSettings.Style <> lsNone then
  1460. begin
  1461. FLEDInactiveBitmap.EllipseAntialias(Origin.CenterPoint.x, Origin.CenterPoint.y,
  1462. FRangeLEDSettings.Size,
  1463. FRangeLEDSettings.Size,
  1464. FRangeLEDSettings.BorderColor,
  1465. 1,
  1466. FRangeLEDSettings.InActiveColor);
  1467. end;
  1468. end // Round
  1469. else
  1470. if RangeLEDSettings.Shape = lshSquare then
  1471. begin
  1472. // draw a Square LED
  1473. if FRangeLEDSettings.Style = lsFlat then
  1474. begin
  1475. // Flat
  1476. FLEDActiveBitmap.FillRoundRectAntialias(
  1477. 0, 0,
  1478. FLEDActiveBitmap.Width,
  1479. FLEDActiveBitmap.Height,
  1480. Origin.Radius / 2,
  1481. Origin.Radius / 2,
  1482. FRangeLEDSettings.ActiveColor);
  1483. // draw border for Flat
  1484. FLEDActiveBitmap.RoundRectAntialias(
  1485. 0,0,
  1486. FLEDActiveBitmap.Width - 1,
  1487. FLEDActiveBitmap.Height - 1,
  1488. Origin.Radius / 2,
  1489. Origin.Radius / 2,
  1490. FRangeLEDSettings.BorderColor,
  1491. 1);
  1492. end
  1493. else
  1494. if FRangeLEDSettings.Style = lsShaded then
  1495. begin
  1496. // draw shaded
  1497. FLEDActiveBitmap.GradientFill(
  1498. 0, 0,
  1499. FLEDActiveBitmap.Width,
  1500. FLEDActiveBitmap.Height,
  1501. FRangeLEDSettings.ActiveColor,
  1502. BGRA(0,0,0),
  1503. gtRadial,
  1504. PointF(FLEDActiveBitmap.Width / 2, FLEDActiveBitmap.Height / 2),
  1505. PointF(FLEDActiveBitmap.Width * 1.5,FLEDActiveBitmap.Height * 1.5),
  1506. dmSet);
  1507. mask := TBGRABitmap.Create(FLEDActiveBitmap.Width, FLEDActiveBitmap.Height, BGRABlack);
  1508. mask.FillRoundRectAntialias(
  1509. 0, 0,
  1510. FLEDActiveBitmap.Width,
  1511. FLEDActiveBitmap.Height,
  1512. Origin.Radius / 2,
  1513. Origin.Radius / 2,
  1514. BGRAWhite);
  1515. FLEDActiveBitmap.ApplyMask(mask);
  1516. mask.Free;
  1517. // draw border for shaded
  1518. FLEDActiveBitmap.RoundRectAntialias(
  1519. 0, 0,
  1520. FLEDActiveBitmap.Width - 1,
  1521. FLEDActiveBitmap.Height - 1,
  1522. Origin.Radius / 2,
  1523. Origin.Radius / 2,
  1524. FRangeLEDSettings.BorderColor,
  1525. 1);
  1526. end;
  1527. // Simple flat square for inactive always
  1528. if RangeLEDSettings.Style <> lsNone then
  1529. begin
  1530. // Need to draw the filled
  1531. FLEDInactiveBitmap.FillRoundRectAntialias(
  1532. 0, 0,
  1533. FLEDActiveBitmap.Width,
  1534. FLEDActiveBitmap.Height,
  1535. Origin.Radius / 2,
  1536. Origin.Radius / 2,
  1537. FRangeLEDSettings.InactiveColor);
  1538. // Now the border
  1539. FLEDInactiveBitmap.RoundRectAntialias(
  1540. 0, 0,
  1541. FLEDActiveBitmap.Width - 1,
  1542. FLEDActiveBitmap.Height - 1,
  1543. Origin.Radius / 2,
  1544. Origin.Radius / 2,
  1545. FRangeLEDSettings.BorderColor,
  1546. 1);
  1547. end;
  1548. end // square
  1549. else
  1550. if RangeLEDSettings.Shape = lshTriangle then
  1551. begin
  1552. // draw a triangle and border
  1553. if FRangeLEDSettings.Style = lsFlat then // TODO : add lsShaded
  1554. begin
  1555. FLEDActiveBitmap.DrawPolyLineAntialias(
  1556. [ PointF(FLEDActiveBitmap.Width / 2, 1),
  1557. PointF(FLEDActiveBitmap.Width - 1, FLEDActiveBitmap.Height - 1),
  1558. PointF(1, FLEDActiveBitmap.Height - 1),
  1559. PointF(FLEDActiveBitmap.Width / 2, 1) // close it for border
  1560. ],
  1561. FRangeLEDSettings.BorderColor,
  1562. 1,
  1563. FRangeLEDSettings.ActiveColor);
  1564. end
  1565. else
  1566. if FRangeLEDSettings.Style = lsShaded then
  1567. begin
  1568. // draw shaded
  1569. FLEDActiveBitmap.FillPolyLinearColor(
  1570. [ PointF(FLEDActiveBitmap.Width / 2, 1),
  1571. PointF(FLEDActiveBitmap.Width - 1, FLEDActiveBitmap.Height - 1),
  1572. PointF(1, FLEDActiveBitmap.Height - 1)],
  1573. [FRangeLEDSettings.InactiveColor,
  1574. FRangeLEDSettings.ActiveColor,
  1575. FRangeLEDSettings.ActiveColor]);
  1576. // draw border
  1577. FLEDActiveBitmap.DrawPolyLineAntialias(
  1578. [ PointF(FLEDActiveBitmap.Width / 2, 1),
  1579. PointF(FLEDActiveBitmap.Width - 1, FLEDActiveBitmap.Height - 1),
  1580. PointF(1, FLEDActiveBitmap.Height - 1),
  1581. PointF(FLEDActiveBitmap.Width / 2, 1) // close it for border
  1582. ],
  1583. FRangeLEDSettings.BorderColor,
  1584. 1,
  1585. BGRA(0,0,0,0));
  1586. end;
  1587. if RangeLEDSettings.Style <> lsNone then
  1588. begin
  1589. FLEDInactiveBitmap.DrawPolyLineAntialias(
  1590. [ PointF(FLEDActiveBitmap.Width / 2, 1),
  1591. PointF(FLEDActiveBitmap.Width - 1, FLEDActiveBitmap.Height - 1),
  1592. PointF(1, FLEDActiveBitmap.Height - 1),
  1593. PointF(FLEDActiveBitmap.Width / 2, 1) // close it for border
  1594. ],
  1595. FRangeLEDSettings.BorderColor,
  1596. 1,
  1597. FRangeLEDSettings.InactiveColor);
  1598. end;
  1599. end // triangle
  1600. else
  1601. if RangeLEDSettings.Shape = lshDownTriangle then
  1602. begin
  1603. // draw a downward pointing triangle and border
  1604. if FRangeLEDSettings.Style = lsFlat then
  1605. begin
  1606. FLEDActiveBitmap.DrawPolyLineAntialias(
  1607. [ PointF(1,1),
  1608. PointF(FLEDActiveBitmap.Width / 2, FLEDActiveBitmap.Height - 1),
  1609. PointF(FLEDActiveBitmap.Width - 1, 1),
  1610. PointF(1,1)
  1611. ],
  1612. FRangeLEDSettings.BorderColor,
  1613. 1,
  1614. FRangeLEDSettings.ActiveColor);
  1615. end
  1616. else
  1617. if FRangeLEDSettings.Style = lsShaded then
  1618. begin
  1619. // draw shaded
  1620. FLEDActiveBitmap.FillPolyLinearColor(
  1621. [ PointF(1,1),
  1622. PointF(FLEDActiveBitmap.Width / 2, FLEDActiveBitmap.Height - 1),
  1623. PointF(FLEDActiveBitmap.Width - 1, 1)
  1624. ],
  1625. [FRangeLEDSettings.InactiveColor,
  1626. FRangeLEDSettings.ActiveColor,
  1627. FRangeLEDSettings.ActiveColor]);
  1628. // draw border
  1629. FLEDActiveBitmap.DrawPolyLineAntialias(
  1630. [ PointF(1,1),
  1631. PointF(FLEDActiveBitmap.Width / 2, FLEDActiveBitmap.Height - 1),
  1632. PointF(FLEDActiveBitmap.Width - 1, 1),
  1633. PointF(1,1)
  1634. ],
  1635. FRangeLEDSettings.BorderColor,
  1636. 1,
  1637. BGRA(0,0,0,0));
  1638. end;
  1639. // Draw Inactive DownTri
  1640. if RangeLEDSettings.Style <> lsNone then
  1641. begin
  1642. FLEDInactiveBitmap.DrawPolyLineAntialias(
  1643. [ PointF(1,1),
  1644. PointF(FLEDActiveBitmap.Width / 2, FLEDActiveBitmap.Height - 1),
  1645. PointF(FLEDActiveBitmap.Width - 1, 1),
  1646. PointF(1,1)
  1647. ],
  1648. FRangeLEDSettings.BorderColor,
  1649. 1,
  1650. FRangeLEDSettings.InactiveColor);
  1651. end;
  1652. end;
  1653. end;
  1654. procedure TSGCustomSuperGauge.DrawMarkers;
  1655. var
  1656. i: integer;
  1657. begin
  1658. if not IsAnyMarkerDirty then
  1659. exit;
  1660. Initializebitmap(FMarkerBitmap, Width, Height); // clear it before we draw anything
  1661. // Draws low to high, so if overlapping, last will be visible
  1662. for i := low(FMarkersSettings) to high(FMArkersSettings) do
  1663. begin
  1664. FMarkersSettings[i].Dirty := True; // need to dirty them all
  1665. DrawMarker(FMarkerBitmap, FMarkersSettings[i]); // will clear any dirty
  1666. end;
  1667. end;
  1668. procedure TSGCustomSuperGauge.DrawMarker(MarkerBitmap: TBGRABitmap; const MarkerSettings: TSGMarkerSettings);
  1669. var
  1670. x1, y1, x2, y2: integer;
  1671. cenX, cenY: integer;
  1672. j, vecLen: single;
  1673. A, B, U, V: TPointF;
  1674. begin
  1675. // skip drawing if nothing changed or not drawn
  1676. if not MarkerSettings.Dirty then
  1677. Exit;
  1678. MarkerSettings.Dirty := False;
  1679. if not MarkerSettings.Enabled then
  1680. Exit;
  1681. // Center of bitmap
  1682. cenX := MarkerBitmap.Width div 2;
  1683. cenY := MarkerBitmap.Height div 2;
  1684. j := (180 - 270) / 2;
  1685. x1 := cenX - Round(MarkerSettings.Radius * cos((j + MarkerSettings.Value * 270 / 100) * Pi / 180));
  1686. y1 := cenY - Round(MarkerSettings.Radius * sin((j + MarkerSettings.Value * 270 / 100) * Pi / 180));
  1687. A := PointF(x1,y1);
  1688. // Calculate draw to point top
  1689. x2 := cenX - Round((MarkerSettings.Radius - MarkerSettings.Height) * cos((j + MarkerSettings.Value * 270 / 100) * Pi / 180));
  1690. y2 := cenY - Round((MarkerSettings.Radius - MarkerSettings.Height) * sin((j + MarkerSettings.Value * 270 / 100) * Pi / 180));
  1691. B := PointF(X2, y2);
  1692. // set line cap just in case
  1693. FMarkerBitmap.LineCap := pecRound; // Ensure Round Cap
  1694. // This is the vector that runs from outer to inner
  1695. U := B - A;
  1696. // build the perpendicular vector
  1697. // (clockwise in screen coordinates while the opposite would be counter clockwise)
  1698. V := PointF(-U.y, U.x);
  1699. // scale it to set the new segment length
  1700. vecLen := VectLen(V);
  1701. // catch odd case of zero len vector, do nothing
  1702. if vecLen = 0.0 then
  1703. Exit;
  1704. V := V * (MarkerSettings.Width / vecLen);
  1705. case MarkerSettings.Style of
  1706. msCenter: // triangle centered on the value
  1707. begin
  1708. MarkerBitmap.FillPolyAntialias([B, A + V, A - V], MarkerSettings.Color);
  1709. end;
  1710. msLeft: // triangle left side only (if looking at it at half way on the gauge)
  1711. begin
  1712. MarkerBitmap.FillPolyAntialias([B, A + V, A], MarkerSettings.Color);
  1713. end;
  1714. msRight:
  1715. begin // triangle right side only
  1716. MarkerBitmap.FillPolyAntialias([B, A, A - V], MarkerSettings.Color);
  1717. end;
  1718. end;
  1719. end;
  1720. /////////////////
  1721. function TSGCustomSuperGauge.CheckRangeLED(AValue: single): boolean;
  1722. begin
  1723. // If a single value is used for both StartRangeValue and
  1724. // EndRangeValue the option for rcBetween makes no sense and is a not valid
  1725. // and will never trigger. Also Manually setting the .Active prop will ONLY
  1726. // work if rcNone is set, otherwise the range checks will prevail as the
  1727. // way the Active state is set and overide the manual setting.
  1728. //
  1729. // Current List
  1730. // TSGRangeCheckType = (rcNone, rcBetween, rcBothInclusive, rcStartInclusive,
  1731. // rcEndInclusive, rcBothBetweenOutside,
  1732. // rcBothInclusiveOutside, rcGreaterStart, RangeEndValue);
  1733. //
  1734. // NOTE - rcGreaterStart, RangeEndValue ignore RangeEnd and RangeStart respectivly
  1735. if FRangeLEDSettings.RangeType = rcNone then
  1736. begin
  1737. Result := FRangeLEDSettings.Active; // need to always return the current state here, Will never trigger RangeLED Events
  1738. end
  1739. else
  1740. if FRangeLEDSettings.Rangetype = rcGaugeOutOfRange then // Special case to ONLY look at the gauge state, ignores the start/end
  1741. Result := FOutOfRangeState // Will NOT trigger any events for RangeLED, this is handled elsewhere
  1742. else
  1743. if FRangeLEDSettings.RangeType = rcGreaterStart then
  1744. Result := (AValue > FRangeLEDSettings.RangeStartValue) // ignore range end, most common case
  1745. else
  1746. if FRangeLEDSettings.RangeType = rcLessEnd then
  1747. Result := (AValue < FRangeLEDSettings.RangeEndValue) // ignor range start
  1748. else
  1749. if FRangeLEDSettings.RangeType = rcBetween then
  1750. Result := (AValue > FRangeLEDSettings.RangeStartValue) and (AValue < FRangeLEDSettings.RangeEndValue)
  1751. else
  1752. if FRangeLEDSettings.Rangetype = rcBothInclusive then
  1753. Result := (AValue >= FRangeLEDSettings.RangeStartValue) and (AValue <= FRangeLEDSettings.RangeEndValue)
  1754. else
  1755. if FRangeLEDSettings.Rangetype = rcBothBetweenOutside then
  1756. Result := (AValue < FRangeLEDSettings.RangeStartValue) or (AValue > FRangeLEDSettings.RangeEndValue)
  1757. else
  1758. if FRangeLEDSettings.Rangetype = rcStartInclusive then
  1759. Result := (AValue >= FRangeLEDSettings.RangeStartValue) and (AValue < FRangeLEDSettings.RangeEndValue)
  1760. else
  1761. if FRangeLEDSettings.Rangetype = rcEndInclusive then
  1762. Result := (AValue > FRangeLEDSettings.RangeStartValue) and (AValue <= FRangeLEDSettings.RangeEndValue)
  1763. else
  1764. if FRangeLEDSettings.Rangetype = rcBothInclusiveOutside then
  1765. Result := (AValue <= FRangeLEDSettings.RangeStartValue) or (AValue >= FRangeLEDSettings.RangeEndValue);
  1766. // Now set the flag we have changed so others SetValue() can update as needed
  1767. FRangeLEDStateChanged := FRangeLEDStateChanged or (Result <> FRangeLEDSettings.Active);
  1768. // Try the callbacks now, should hit one or the other depending on Active state
  1769. // if they are assigned! Rember some will NEVER casuse a call back, rcNone and
  1770. // rcGaugeOutOfRange
  1771. if FRangeLEDStateChanged and (FRangeLEDSettings.RangeType <> rcNone)
  1772. and (FRangeLEDSettings.RangeType <> rcGaugeOutOfRange) then
  1773. begin
  1774. if Assigned(FRangeLedActive) and Result then
  1775. FRangeLEDActive(Self, AValue)
  1776. else
  1777. if Assigned(FRangeLedActive) and (not Result) then
  1778. FRangeLEDInactive(Self, AValue);
  1779. FRangeLEDStateChanged := False; // clear the state
  1780. end;
  1781. FRangeLEDSettings.ActiveNoDoChange := Result;
  1782. end;
  1783. end.