supergauge.pas 92 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763
  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. {******************************** CHANGE LOG *********************************
  18. v2.00 - Breaking Changes from V1 SuperGauge Sandy Ganz, [email protected]
  19. Autoscale of SG now fully supported and seems to be working well.
  20. Fixed bug in About box that was displaying the wrong version numbers.
  21. Minor changes to the rendered gauge due to the autoscale code changes.
  22. Possible breaking change on Frame properties, better to fix now.
  23. Cleaned up how the Frames works, now 3 individual concentric, Outer, middle, inner.
  24. Pointer Thickness is now Width to be more consistant with other props (breaking Change).
  25. Added additional options for RangeLED to now check just Start or Ending value.
  26. Fixed Bug in that Marker Values were not scaled, so if the Min/Max was not 0/100 it would not draw correctly.
  27. Fixed bug in Arc pointer drawing using only the FValue for a range check instead of the passed in parameter.
  28. Added Second Scale and Pointer options, known as AuxPointer and AuxScale.
  29. Added missing events for the AuxValue so it can also emit OutOfRange type events.
  30. Removed unintended exposed property on RangeLED (OK to Remove from .lfm if warned)
  31. Changed RangeLED type of rcGaugeOutOfRange to rcGaugeOverload and events to
  32. make it language different then RangeCheckLED.
  33. v2.03 - Changed AutoScale functionality when NOT auto scaling to preserve the size
  34. of the original component, so really no changes based on zoom/resolution.
  35. This will alow it to draw correctly but possibly at a larger size when AutoScale is disabled.
  36. ******************************* END CHANGE LOG *******************************}
  37. unit SuperGauge;
  38. {$I bgracontrols.inc}
  39. interface
  40. uses
  41. Classes, SysUtils, Graphics, {$IFDEF FPC}LResources,{$ELSE} BGRAGraphics, {$ENDIF} Forms, Controls, Dialogs, SuperGaugeCommon,
  42. BGRABitmap, BGRABitmapTypes, BGRAVectorize, BGRAPath, math, bctypes, bctools;
  43. const
  44. VERSIONSTR = '2.03'; // SG version, Should ALWAYS show as a delta when merging!
  45. INTERNAL_GAUGE_MIN_VALUE = 0; // internal lowest value
  46. INTERNAL_GAUGE_MAX_VALUE = 270; // internal highest value
  47. BASELINE_SIZE = 300; // For ResolveSizes()
  48. type
  49. { TSGCustomSuperGauge }
  50. TBandsArray = array[0..3] of TSGBandSettings;
  51. TTextsArray = array[0..2] of TSGTextSettings;
  52. TMarkersArray = array[0..2] of TSGMarkerSettings;
  53. TTextsBitmapArray = array[0..2] of TBGRABitmap;
  54. TSGRangeStateErrorEvent = procedure(Sender: TObject; OutOfRangeValue: single) of object; // called anytime out of range
  55. TSGRangeStateOKEvent = procedure(Sender: TObject; RangeValue: single) of object; // called only when back to in range
  56. TSGRangeStateChangeEvent = procedure(Sender: TObject; Value: single) of object; // called when state RangeLed Active changes to True
  57. // TResolvedSizes is a helper for scaling. It's used
  58. // in most of the DrawXXX procedures to help with autoscale.
  59. TResolveSizes = Record
  60. MinRadius: integer;
  61. MinWH: integer;
  62. Scale: single;
  63. // Frame Specific since its already sorta' scaling
  64. OuterFrameThickness: single;
  65. MiddleFrameThickness: single;
  66. InnerFrameThickness: single;
  67. // keep track of the internal frame radius, helps later
  68. OuterFrameInsideRadius: single;
  69. MiddleFrameInsideRadius: single;
  70. InnerFrameInsideRadius: single;
  71. FrameThickness: single;
  72. // Face
  73. FaceRadiusStart: single;
  74. // add anything here that might need autosize
  75. // also initialize these in the constructor
  76. // and fill in the ResolveSizes()
  77. end;
  78. TSGCustomSuperGauge = class(TGraphicControl)
  79. private
  80. { Private declarations }
  81. FDirty: boolean;
  82. FAutoScale: boolean;
  83. FResolvedSizes: TResolveSizes;
  84. FFaceSettings: TSGFaceSettings;
  85. FFrameSettings: TSGFrameSettings;
  86. FPointerSettings: TSGPointerSettings;
  87. FAuxPointerSettings: TSGPointerSettings;
  88. FPointerCapSettings: TSGPointerCapSettings;
  89. FScaleSettings: TSGScaleSettings;
  90. FAuxScaleSettings: TSGScaleSettings;
  91. FBandsSettings: TBandsArray;
  92. FTextsSettings: TTextsArray;
  93. FRangeLEDSettings: TSGRangeCheckLEDSettings;
  94. FMarkersSettings: TMarkersArray;
  95. FGaugeBitmap: TBGRABitmap;
  96. FFrameBitmap: TBGRABitmap;
  97. FFaceBitmap: TBGRABitmap;
  98. FTextBitmap: TBGRABitmap;
  99. FScaleBitmap: TBGRABitmap;
  100. FBandBitmap: TBGRABitmap;
  101. FTextsBitmaps: TTextsBitmapArray;
  102. FMultiBitmap: TBGRABitmap;
  103. FPointerBitmap: TBGRABitmap;
  104. FMarkerBitmap: TBGRABitmap;
  105. FPointerCapBitmap: TBGRABitmap;
  106. FLEDActiveBitmap: TBGRABitmap;
  107. FLEDInActiveBitmap: TBGRABitmap;
  108. FMinValue: single; // the min value mapped to lowest/leftmost angle on the gauge
  109. FMaxValue: single; // the max value mapped to highest/rightmost angle on the gauge
  110. FValue: single; // this is the VALUE not a position
  111. FAuxMinValue: single; // Aux Pointer Values, only pointer no markers, no RangeLED interactions
  112. FAuxMaxValue: single;
  113. FAuxValue: single;
  114. FOverloadTriggered: TSGRangeStateErrorEvent;
  115. FOverloadRecovered: TSGRangeStateOKEvent;
  116. FOverloadTriggeredState: boolean;
  117. FAuxOverloadTriggered: TSGRangeStateErrorEvent;
  118. FAuxOverloadRecovered: TSGRangeStateOKEvent;
  119. FAuxOverloadTriggeredState: boolean;
  120. FRangeLEDActive: TSGRangeStateChangeEvent;
  121. FRangeLEDInactive: TSGRangeStateChangeEvent;
  122. FRangeLEDStateChanged: boolean;
  123. procedure SetBandSettings1(AValue: TSGBandSettings);
  124. procedure SetBandSettings2(AValue: TSGBandSettings);
  125. procedure SetBandSettings3(AValue: TSGBandSettings);
  126. procedure SetBandSettings4(AValue: TSGBandSettings);
  127. procedure SetTextSettings1(AValue: TSGTextSettings);
  128. procedure SetTextSettings2(AValue: TSGTextSettings);
  129. procedure SetTextSettings3(AValue: TSGTextSettings);
  130. procedure SetMarkerSettings1(AValue: TSGMarkerSettings);
  131. procedure SetMarkerSettings2(AValue: TSGMarkerSettings);
  132. procedure SetMarkerSettings3(AValue: TSGMarkerSettings);
  133. procedure SetFaceSettings(AValue: TSGFaceSettings);
  134. procedure SetScaleSettings(AValue: TSGScaleSettings);
  135. procedure SetAuxScaleSettings(AValue: TSGScaleSettings);
  136. procedure SetFrameSettings(AValue: TSGFrameSettings);
  137. procedure SetPointerSettings(AValue: TSGPointerSettings);
  138. procedure SetAuxPointerSettings(AValue: TSGPointerSettings);
  139. procedure SetRangeLEDSettings(AValue: TSGRangeCheckLEDSettings);
  140. procedure SetPointerCapSettings(AValue: TSGPointerCapSettings);
  141. procedure SetMaxValue(AValue: single);
  142. procedure SetMinValue(AValue: single);
  143. procedure SetValue(AValue: single);
  144. function GetValue: single;
  145. procedure SetAuxMaxValue(AValue: single);
  146. procedure SetAuxMinValue(AValue: single);
  147. procedure SetAuxValue(AValue: single);
  148. function GetAuxValue: single;
  149. procedure SetAutoScale(AValue: boolean);
  150. function CheckOutOfRange(AValue: single): single;
  151. function AuxCheckOutOfRange(AValue: single): single;
  152. protected
  153. { Protected declarations }
  154. class function GetControlClassDefaultSize: TSize; override;
  155. procedure DoSetBounds(ALeft, ATop, AWidth, AHeight: Integer); override;
  156. procedure DoChange({%H-}Sender: TObject);
  157. procedure DoRangeLEDChange({%H-}Sender: TObject);
  158. procedure DoPictureChange({%H-}Sender: TObject);
  159. procedure DoChangeFont1({%H-}ASender: TObject; {%H-}AData: PtrInt); // Wrapper for FontEx DoChange
  160. procedure DoChangeFont2({%H-}ASender: TObject; {%H-}AData: PtrInt); // Wrapper for FontEx DoChange
  161. procedure DoChangeFont3({%H-}ASender: TObject; {%H-}AData: PtrInt); // Wrapper for FontEx DoChange
  162. procedure SetAllBandsDirtyState(AValue: boolean);
  163. procedure SetAllTextsDirtyState(AValue: boolean);
  164. procedure SetAllMarkersDirtyState(AValue: boolean);
  165. function IsAnyBandDirty: boolean;
  166. function IsAnyMarkerDirty: boolean;
  167. function GetMinSize: integer;
  168. procedure ResolveSizes;
  169. property Dirty: boolean read FDirty write FDirty;
  170. public
  171. { Public declarations }
  172. procedure AutoAdjustLayout(AMode: TLayoutAdjustmentPolicy;
  173. const AFromPPI, AToPPI, AOldFormWidth, ANewFormWidth: Integer); override;
  174. constructor Create(AOwner: TComponent); override;
  175. destructor Destroy; override;
  176. property PointerSettings: TSGPointerSettings read FPointerSettings write SetPointerSettings;
  177. property AuxPointerSettings: TSGPointerSettings read FAuxPointerSettings write SetAuxPointerSettings;
  178. property PointerCapSettings: TSGPointerCapSettings read FPointerCapSettings write SetPointerCapSettings;
  179. property FaceSettings: TSGFaceSettings read FFaceSettings write SetFaceSettings;
  180. property FrameSettings: TSGFrameSettings read FFrameSettings write SetFrameSettings;
  181. property ScaleSettings: TSGScaleSettings read FScaleSettings write SetScaleSettings;
  182. property AuxScaleSettings: TSGScaleSettings read FAuxScaleSettings write SetAuxScaleSettings;
  183. property BandSettings1: TSGBandSettings read FBandsSettings[0] write SetBandSettings1;
  184. property BandSettings2: TSGBandSettings read FBandsSettings[1] write SetBandSettings2;
  185. property BandSettings3: TSGBandSettings read FBandsSettings[2] write SetBandSettings3;
  186. property BandSettings4: TSGBandSettings read FBandsSettings[3] write SetBandSettings4;
  187. property TextSettings1: TSGTextSettings read FTextsSettings[0] write SetTextSettings1;
  188. property TextSettings2: TSGTextSettings read FTextsSettings[1] write SetTextSettings2;
  189. property TextSettings3: TSGTextSettings read FTextsSettings[2] write SetTextSettings3;
  190. property RangeLEDSettings: TSGRangeCheckLEDSettings read FRangeLEDSettings write SetRangeLEDSettings;
  191. property MarkerSettings1: TSGMarkerSettings read FMarkersSettings[0] write SetMarkerSettings1;
  192. property MarkerSettings2: TSGMarkerSettings read FMarkersSettings[1] write SetMarkerSettings2;
  193. property MarkerSettings3: TSGMarkerSettings read FMarkersSettings[2] write SetMarkerSettings3;
  194. property MinValue: single read FMinValue write SetMinValue default 0.0;
  195. property MaxValue: single read FMaxValue write SetMaxValue default 100.0;
  196. property Value: single read GetValue write SetValue default 0.0;
  197. property AuxMinValue: single read FAuxMinValue write SetAuxMinValue default 0.0;
  198. property AuxMaxValue: single read FAuxMaxValue write SetAuxMaxValue default 100.0;
  199. property AuxValue: single read GetAuxValue write SetAuxValue default 0.0;
  200. property OverloadTriggered: TSGRangeStateErrorEvent read FOverloadTriggered write FOverloadTriggered;
  201. property OverloadRecovered: TSGRangeStateOKEvent read FOverloadRecovered write FOverloadRecovered;
  202. property AuxOverloadTriggered: TSGRangeStateErrorEvent read FAuxOverloadTriggered write FAuxOverloadTriggered;
  203. property AuxOverloadRecovered: TSGRangeStateOKEvent read FAuxOverloadRecovered write FAuxOverloadRecovered;
  204. property RangeLEDActive: TSGRangeStateChangeEvent read FRangeLEDActive write FRangeLEDActive;
  205. property RangeLEDInActive: TSGRangeStateChangeEvent read FRangeLEDInactive write FRangeLEDInactive;
  206. property AutoScale: boolean read FAutoScale write SetAutoScale default False;
  207. function RemapRange(OldValue: single; OldMin, OldMax, NewMin, NewMax: single): single;
  208. function GaugeToUser(GaugeValue, MinVal, MaxVal: single): single;
  209. function UserToGauge(UserValue, MinVal, MaxVal: single): single;
  210. procedure Paint; override;
  211. procedure DrawFrame;
  212. procedure DrawFace;
  213. procedure DrawScales;
  214. procedure DrawScale(const Settings: TSGScaleSettings; Scale: single);
  215. procedure DrawBand(const BandSettings: TSGBandSettings; BandScale: single);
  216. procedure DrawBands;
  217. procedure DrawMulti;
  218. procedure DrawText(TextBitmap: TBGRABitmap; const TextSettings: TSGTextSettings);
  219. procedure DrawLED;
  220. procedure DrawMarker(MarkerBitmap: TBGRABitmap; const MarkerSettings: TSGMarkerSettings);
  221. procedure DrawMarkers;
  222. procedure DrawPointer(const Settings: TSGPointerSettings; Value: single);
  223. procedure DrawPointerCap;
  224. function CheckRangeLED(AValue: single): boolean;
  225. end;
  226. { TSuperGauge }
  227. TSuperGauge = class(TSGCustomSuperGauge)
  228. private
  229. { Private declarations }
  230. protected
  231. { Protected declarations }
  232. public
  233. { Public declarations }
  234. published
  235. { Published declarations }
  236. property MinValue;
  237. property MaxValue;
  238. property Value;
  239. property AuxMinValue;
  240. property AuxMaxValue;
  241. property AuxValue;
  242. property FaceSettings;
  243. property BandSettings1;
  244. property BandSettings2;
  245. property BandSettings3;
  246. property BandSettings4;
  247. property TextSettings1;
  248. property TextSettings2;
  249. property TextSettings3;
  250. property FrameSettings;
  251. property ScaleSettings;
  252. property AuxScaleSettings;
  253. property RangeLEDSettings;
  254. property MarkerSettings1;
  255. property MarkerSettings2;
  256. property MarkerSettings3;
  257. property PointerSettings;
  258. property AuxPointerSettings;
  259. property PointerCapSettings;
  260. property AutoScale;
  261. property OverloadTriggered;
  262. property OverloadRecovered;
  263. property AuxOverloadTriggered;
  264. property AuxOverloadRecovered;
  265. property RangeLEDActive;
  266. property RangeLEDInactive;
  267. property Color default clNone;
  268. // Added missing events
  269. property Anchors;
  270. property OnClick;
  271. property OnDblClick;
  272. property OnMouseDown;
  273. property OnMouseUp;
  274. property OnMouseMove;
  275. property OnMouseEnter;
  276. property OnMouseLeave;
  277. end;
  278. {$IFDEF FPC}procedure Register;{$ENDIF}
  279. implementation
  280. {$IFDEF FPC}
  281. procedure Register;
  282. begin
  283. RegisterComponents('BGRA Controls', [TSuperGauge]);
  284. end;
  285. {$ENDIF}
  286. { TSGCustomSuperGauge }
  287. procedure TSGCustomSuperGauge.AutoAdjustLayout(AMode: TLayoutAdjustmentPolicy;
  288. const AFromPPI, AToPPI, AOldFormWidth, ANewFormWidth: Integer);
  289. begin
  290. // If autoscaling then we will let the system mess with the component size
  291. // otherwise it will just leave it along as the ACTUAL size in the designer
  292. // as 1:1 with no scaling on anything. By not calling AutoAdjustLayout()
  293. // Scaling will be 1:1
  294. //
  295. // Note - that toggling the AutoScale setting will cause a repaint
  296. // but NOT a resize of the Components client area
  297. if FAutoScale then
  298. inherited AutoAdjustLayout(AMode, AFromPPI, AToPPI, AOldFormWidth, ANewFormWidth);
  299. end;
  300. constructor TSGCustomSuperGauge.Create(AOwner: TComponent);
  301. var
  302. i: integer;
  303. begin
  304. inherited Create(AOwner);
  305. // remember if form is scaled CX, CY values will be too!
  306. // this may not do anything!!!
  307. with GetControlClassDefaultSize do
  308. SetInitialBounds(0, 0, CX, CY);
  309. FFaceSettings := TSGFaceSettings.Create;
  310. FaceSettings.OnChange := DoChange;
  311. FaceSettings.Picture.OnChange := DoPictureChange; // need to set this so we can catch changes to the picture!
  312. FFrameSettings := TSGFrameSettings.Create;
  313. FrameSettings.OnChange := DoChange;
  314. FScaleSettings := TSGScaleSettings.Create;
  315. FScaleSettings.OnChange := DoChange;
  316. FAuxScaleSettings := TSGScaleSettings.Create;
  317. FAuxScaleSettings.OnChange := DoChange;
  318. FAuxScaleSettings.Enabled := False;
  319. for i := low(FBandsSettings) to high(FBandsSettings) do
  320. begin
  321. FBandsSettings[i] := TSGBandSettings.Create;
  322. FBandsSettings[i].OnChange := DoChange;
  323. FBandsSettings[i].Text := 'Band ' + IntToStr(i + 1);
  324. end;
  325. for i := low(FTextsSettings) to high(FTextsSettings) do
  326. begin
  327. FTextsSettings[i] := TSGTextSettings.Create;
  328. FTextsSettings[i].OnChange := DoChange;
  329. FTextsBitmaps[i] := TBGRABitmap.Create;
  330. end;
  331. // Set Text font change events and defaults
  332. FTextsSettings[0].FontEx.OnChange := DoChangeFont1;
  333. FTextsSettings[1].FontEx.OnChange := DoChangeFont2;
  334. FTextsSettings[2].FontEx.OnChange := DoChangeFont3;
  335. FTextsSettings[0].Text := 'Text1';
  336. FTextsSettings[1].Text := 'Text2';
  337. FTextsSettings[2].Text := 'Text3';
  338. // Pointer Cap
  339. FPointerCapSettings := TSGPointerCapSettings.Create;
  340. FPointerCapSettings.OnChange := DoChange;
  341. // Pointers
  342. FPointerSettings := TSGPointerSettings.Create;
  343. FPointerSettings.OnChange := DoChange;
  344. FPointerSettings.Color := BGRA(255, 127, 63); // orange
  345. FPointerSettings.Enabled := True;
  346. FAuxPointerSettings := TSGPointerSettings.Create;
  347. FAuxPointerSettings.OnChange := DoChange;
  348. FAuxPointerSettings.Color := clRed;
  349. FAuxPointerSettings.Enabled := False;
  350. // RangeLED
  351. FRangeLEDSettings := TSGRangeCheckLEDSettings.Create;
  352. FRangeLEDSettings.OnChange := DoRangeLEDChange;
  353. // Markers
  354. for i := low(FMarkersSettings) to high(FMarkersSettings) do
  355. begin
  356. FMarkersSettings[i] := TSGMarkerSettings.Create;
  357. FMarkersSettings[i].OnChange := DoChange;
  358. end;
  359. // make marker each different to save confusion
  360. FMarkersSettings[0].Color := clLime;
  361. FMarkersSettings[1].Color := clRed;
  362. FMarkersSettings[2].Color := clYellow;
  363. // create needed bitmaps, Don't Forget to FREE!!!
  364. FFaceBitmap := TBGRABitmap.Create;
  365. FFrameBitmap := TBGRABitmap.Create;
  366. FGaugeBitmap := TBGRABitmap.Create;
  367. FTextBitmap := TBGRABitmap.Create;
  368. FPointerBitmap := TBGRABitmap.Create;
  369. FPointerCapBitmap := TBGRABitmap.Create;
  370. FScaleBitmap := TBGRABitmap.Create;
  371. FBandBitmap := TBGRABitmap.Create;
  372. FMultiBitmap := TBGRABitmap.Create;
  373. FLEDActiveBitmap := TBGRABitmap.Create;
  374. FLEDInActiveBitmap := TBGRABitmap.Create;
  375. FMarkerBitmap := TBGRABitmap.Create;
  376. // initialized (some above)
  377. FOverloadTriggeredState := False;
  378. FAuxOverloadTriggeredState := False;
  379. FRangeLEDStateChanged := False;
  380. FValue := 0;
  381. FAutoScale := False;
  382. FMinValue := 0;
  383. FMaxValue := 100;
  384. FAuxMinValue := 0;
  385. FAuxMaxValue := 100;
  386. Color := clNone;
  387. // set up baseline values from the defaults, good starting point any-a-ways
  388. FResolvedSizes.MinRadius := 0; // can't know MinRadius or MinWH yet, not resolved
  389. FResolvedSizes.MinWH := 0;
  390. FDirty := True; // Always force initial paint/draw on everything!
  391. end;
  392. destructor TSGCustomSuperGauge.Destroy;
  393. var
  394. i: integer;
  395. begin
  396. FPointerCapSettings.OnChange := nil;
  397. FPointerCapSettings.Free;
  398. FPointerSettings.OnChange := nil;
  399. FPointerSettings.Free;
  400. FAuxPointerSettings.OnChange := nil;
  401. FAuxPointerSettings.Free;
  402. FRangeLEDSettings.OnChange := nil;
  403. FRangeLEDSettings.Free;
  404. ScaleSettings.OnChange := nil;
  405. FScaleSettings.Free;
  406. AuxScaleSettings.OnChange := nil;
  407. FAuxScaleSettings.Free;
  408. for i := low(FTextsSettings) to high(FTextsSettings) do
  409. begin
  410. FBandsSettings[i].OnChange := nil;
  411. FBandsSettings[i].Free;
  412. end;
  413. for i := low(FTextsSettings) to high(FTextsSettings) do
  414. begin
  415. FTextsSettings[i].OnChange := nil;
  416. FTextsSettings[i].FontEx.OnChange := nil;
  417. FTextsSettings[i].Free;
  418. FTextsBitmaps[i].Free;
  419. end;
  420. for i := low(FMarkersSettings) to high(FMarkersSettings) do
  421. begin
  422. FMarkersSettings[i].OnChange := nil;
  423. FMarkersSettings[i].Free;
  424. end;
  425. FFaceSettings.OnChange := nil;
  426. FFaceSettings.Free;
  427. FFrameSettings.OnChange := nil;
  428. FFrameSettings.Free;
  429. // now clean bitmaps, should match what's in creat method
  430. FLEDActiveBitmap.Free;
  431. FLEDInactiveBitmap.Free;
  432. FMarkerBitmap.Free;
  433. FBandBitmap.Free;
  434. FScaleBitmap.Free;
  435. FPointerBitmap.Free;
  436. FPointerCapBitmap.Free;
  437. FTextBitmap.Free;
  438. FFaceBitmap.Free;
  439. FMultiBitmap.Free;
  440. FFrameBitmap.Free;
  441. FGaugeBitmap.Free;
  442. inherited Destroy;
  443. end;
  444. function TSGCustomSuperGauge.RemapRange(OldValue: single; OldMin, OldMax, NewMin, NewMax: single): single;
  445. begin
  446. // Generic mapping of ranges. Value is the number to remap, returns number
  447. // in the new range. Looks for odd div by 0 condition and fixes
  448. if OldMin = OldMax then
  449. begin
  450. // need to return something reasonable
  451. if OldValue <= OldMin then
  452. Exit(NewMin)
  453. else
  454. Exit(NewMax);
  455. end;
  456. Result := (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin;
  457. end;
  458. // These are generally used for the pointer and range check scaling
  459. // Passing in the Main Min/Max or the Aux Min/Max valves to set them.
  460. // Again NOTE this will not affect the visable scale on the gauge, just the
  461. // position of the pointers and range check as the displayed Scale is indpendant of this.
  462. function TSGCustomSuperGauge.GaugeToUser(GaugeValue, MinVal, MaxVal: single): single;
  463. begin
  464. // Helper to translate internal gauge value to external user value
  465. Result := RemapRange(GaugeValue, INTERNAL_GAUGE_MIN_VALUE, INTERNAL_GAUGE_MAX_VALUE, MinVal, MaxVal);
  466. end;
  467. function TSGCustomSuperGauge.UserToGauge(UserValue, MinVal, MaxVal: single): single;
  468. begin
  469. // Helper to translate external user value to internal gauge value
  470. Result := RemapRange(UserValue, MinVal, MaxVal, INTERNAL_GAUGE_MIN_VALUE, INTERNAL_GAUGE_MAX_VALUE);
  471. end;
  472. function TSGCustomSuperGauge.GetValue: single;
  473. begin
  474. // Scale from internal back to user range
  475. Result := GaugeToUser(FValue, FMinValue, FMaxValue);
  476. end;
  477. function TSGCustomSuperGauge.CheckOutOfRange(AValue: single): Single;
  478. begin
  479. // These values are in gauge space, so typically never less than 0, or > 270
  480. Result := AValue; // SAFE so always will return a value
  481. if AValue < INTERNAL_GAUGE_MIN_VALUE then
  482. begin
  483. // Under Range event
  484. FOverloadTriggeredState := True;
  485. if Assigned(FOverloadTriggered) then
  486. FOverloadTriggered(Self, GaugeToUser(AValue, FMinValue, FMaxValue));
  487. Result := INTERNAL_GAUGE_MIN_VALUE;
  488. end
  489. else
  490. if AValue > INTERNAL_GAUGE_MAX_VALUE then
  491. begin
  492. // Over Range event
  493. FOverloadTriggeredState := True;
  494. if Assigned(FOverloadTriggered) then
  495. FOverloadTriggered(Self, GaugeToUser(AValue, FMinValue, FMaxValue)); // must translate back to user space
  496. Result := INTERNAL_GAUGE_MAX_VALUE;
  497. end
  498. else
  499. begin
  500. // If NOT over/under flow then will need to clear
  501. // that state/flag and reset any indicators if was in a overange state
  502. if FOverloadTriggeredState then
  503. begin
  504. if Assigned(FOverloadRecovered) then
  505. FOverloadRecovered(self, GaugeToUser(AValue, FMinValue, FMaxValue)); // here to, get into user space
  506. FOverloadTriggeredState := False; // reset so no more calls
  507. end;
  508. end;
  509. end;
  510. function TSGCustomSuperGauge.AuxCheckOutOfRange(AValue: single): Single;
  511. begin
  512. // Same as above, but for the Aux Values
  513. // These values are in gauge space, so typically never less than 0, or > 270
  514. Result := AValue; // SAFE so always will return a value
  515. if AValue < INTERNAL_GAUGE_MIN_VALUE then
  516. begin
  517. // Under Range event
  518. FAuxOverloadTriggeredState := True;
  519. if Assigned(FAuxOverloadTriggered) then
  520. FAuxOverloadTriggered(Self, GaugeToUser(AValue, FAuxMinValue, FAuxMaxValue));
  521. Result := INTERNAL_GAUGE_MIN_VALUE;
  522. end
  523. else
  524. if AValue > INTERNAL_GAUGE_MAX_VALUE then
  525. begin
  526. // Over Range event
  527. FAuxOverloadTriggeredState := True;
  528. if Assigned(FAuxOverloadTriggered) then
  529. FAuxOverloadTriggered(Self, GaugeToUser(AValue, FAuxMinValue, FAuxMaxValue)); // must translate back to user space
  530. Result := INTERNAL_GAUGE_MAX_VALUE;
  531. end
  532. else
  533. begin
  534. // If NOT over/under flow then will need to clear
  535. // that state/flag and reset any indicators if was in a overange state
  536. if FAuxOverloadTriggeredState then
  537. begin
  538. if Assigned(FAuxOverloadRecovered) then
  539. FAuxOverloadRecovered(self, GaugeToUser(AValue, FAuxMinValue, FAuxMaxValue)); // here to, get into user space
  540. FAuxOverloadTriggeredState := False; // reset so no more calls
  541. end;
  542. end;
  543. end;
  544. // Override the base class which has a rectangular dimension
  545. class function TSGCustomSuperGauge.GetControlClassDefaultSize: TSize;
  546. begin
  547. // Note the preferred size for the control is 300xy, however in highdpi modes
  548. // on windows (maybe others) the control is scaled since the by default the forms
  549. // scale will affect the actual value on creation. So as an example, Windows 11,
  550. // 4k monitor, 150% scaling (windows settings), will cause the component to be
  551. // created and rendered with the size of 300x300. So these numbers get scaled
  552. // UP in this instance. If you run the scaling on Windows 11 at 100%, settings
  553. // after LCL does it's business is 200x200.
  554. Result.CX := 200;
  555. Result.CY := 200;
  556. end;
  557. function TSGCustomSuperGauge.GetMinSize: integer;
  558. begin
  559. // Take the smallest width or height so we can use for max size gauge
  560. if ClientWidth < ClientHeight then
  561. Exit(ClientWidth)
  562. else
  563. Exit(ClientHeight);
  564. end;
  565. procedure TSGCustomSuperGauge.ResolveSizes;
  566. var
  567. r: integer;
  568. begin
  569. // Calculate anything needed to scale all elements of the Gauge. This is
  570. // called at the beginning of Paint so put individual scaling directly
  571. // in the DrawXXX procedures since they are often not redrawn and this
  572. // can save some computations in that case (Not dirty elements)
  573. // Drawing sized based on proportions of the DEFAULT component size (Baseline)
  574. FResolvedSizes.MinWH := GetMinSize;
  575. FResolvedSizes.MinRadius := FResolvedSizes.MinWH div 2;
  576. if FAutoScale then
  577. begin
  578. FResolvedSizes.Scale := FResolvedSizes.MinWH / BASELINE_SIZE;
  579. FResolvedSizes.OuterFrameThickness := FFrameSettings.OuterFrameThickness * FResolvedSizes.Scale;
  580. FResolvedSizes.MiddleFrameThickness := FFrameSettings.MiddleFrameThickness * FResolvedSizes.Scale;
  581. FResolvedSizes.InnerFrameThickness := FFrameSettings.InnerFrameThickness * FResolvedSizes.Scale;
  582. end
  583. else
  584. begin
  585. // Easy, not scaling
  586. FResolvedSizes.OuterFrameThickness := FFrameSettings.OuterFrameThickness;
  587. FResolvedSizes.MiddleFrameThickness := FFrameSettings.MiddleFrameThickness;
  588. FResolvedSizes.InnerFrameThickness := FFrameSettings.InnerFrameThickness;
  589. FResolvedSizes.Scale := 1.0;
  590. end;
  591. r := FResolvedSizes.MinRadius - 1; // Fudge factor to help with div 2
  592. // Outer Frame EndRadius
  593. FResolvedSizes.OuterFrameInsideRadius := r - FResolvedSizes.OuterFrameThickness / 2; // Moves in from size of client (min size that is)
  594. // Middle Frame EndRadus
  595. FResolvedSizes.MiddleFrameInsideRadius := r - FResolvedSizes.MiddleFrameThickness / 2 - FResolvedSizes.OuterFrameThickness;
  596. // Innermost Frame End Radius
  597. FResolvedSizes.InnerFrameInsideRadius := r - FResolvedSizes.InnerFrameThickness / 2 - FResolvedSizes.OuterFrameThickness - FResolvedSizes.MiddleFrameThickness;
  598. FResolvedSizes.FrameThickness := FResolvedSizes.OuterFrameThickness
  599. + FResolvedSizes.MiddleFrameThickness + FResolvedSizes.InnerFrameThickness;
  600. // Get the drawing position for the face
  601. FResolvedSizes.FaceRadiusStart := FResolvedSizes.InnerFrameInsideRadius - FResolvedSizes.InnerFrameThickness / 2; // compensate for width of last ring
  602. end;
  603. procedure TSGCustomSuperGauge.SetAutoScale(AValue: boolean);
  604. begin
  605. if FAutoScale = AValue then
  606. Exit;
  607. FAutoScale := AValue;
  608. FDirty := True; // set it, as it will need a full repaint
  609. DoChange(self);
  610. end;
  611. procedure TSGCustomSuperGauge.SetMaxValue(AValue: single);
  612. var
  613. currUser: single;
  614. begin
  615. // Note : MinValue and MaxValue can span negative ranges and be increasing
  616. // or decreasing. Can't really range check here
  617. // If changing min/max must refresh the value to adjust
  618. currUser := GaugeToUser(FValue, FMinValue, FMaxValue);
  619. FMaxValue := AValue; // setting this will change UserToGauge() in SetValue!
  620. // Recompute
  621. SetValue(currUser);
  622. end;
  623. procedure TSGCustomSuperGauge.SetMinValue(AValue: single);
  624. var
  625. currUser: single;
  626. begin
  627. // Note : MinValue and MaxValue can span negative ranges and be increasing
  628. // or decreasing. Can't really range check here
  629. // If changing min/max must refresh the value to adjust
  630. currUser := GaugeToUser(FValue, FMinValue, FMaxValue);
  631. FMinValue := AValue; // setting this will change UserToGauge() in SetValue!
  632. // Recompute
  633. SetValue(currUser);
  634. end;
  635. procedure TSGCustomSuperGauge.SetValue(AValue: single);
  636. var
  637. gaugeValue: single;
  638. begin
  639. // Tricky case here, since we are calling the RangeLED range check
  640. // here too, if that is in any way dirty we should process the value
  641. // and not skip. Triggering any change on RangeLEDSettings should call this.
  642. // Get the user value into gauge value space
  643. gaugeValue := UserToGauge(AValue, FMinValue, FMaxValue);
  644. // Skip if a few conditions exit. This is a bit tricky as the gauge value will reset
  645. // to min or max values on overload so need to always update if that's the case. Should
  646. // not affect performance. Similar for LED, if dirty no skip.
  647. if (FValue = gaugeValue) and (not FRangeLEDSettings.Dirty) and (not FOverloadTriggeredState) then
  648. Exit;
  649. // If out of range conditions are at play the gauge Value (FValue) will never
  650. // be out of range. This value is passed to the out of range handler for the
  651. // user to deal with and DO SOMETHING to indicate it.
  652. FValue := CheckOutOfRange(gaugeValue);
  653. // If we have a change in the of the LED's Active property we need
  654. // to call the event handlers too. Also we will check it's values and set
  655. // if needed. NOTE : that if the range type is set to rtNone, it will always
  656. // return the state of the RangeLEDSettings.Active, otherwise it will calculate
  657. // the needed value for a range check as set. FRangeLEDStateChanged is set in
  658. // IsRangeLEDActive function so should be called before this!
  659. // MUST NOT CALL .Active as this will cause a recursive call, use the
  660. // hacked ActiveNoDoChange which will just set the property value with
  661. // no side effects
  662. // True if LED Should be On, False if not, AValue is in User space for LED's
  663. FRangeLEDSettings.ActiveNoDoChange := CheckRangeLED(AValue);
  664. // We must dirty the Pointer here or no redraw
  665. PointerSettings.Dirty := True;
  666. DoChange(self);
  667. end;
  668. procedure TSGCustomSuperGauge.SetAuxMaxValue(AValue: single);
  669. var
  670. auxCurrUser: single;
  671. begin
  672. // Note : MinValue and MaxValue can span negative ranges and be increasing
  673. // or decreasing. Can't really range check here
  674. // If changing min/max must refresh the value to adjust
  675. auxCurrUser := GaugeToUser(FAuxValue, FAuxMinValue, FAuxMaxValue);
  676. FAuxMaxValue := AValue; // setting this will change UserToGauge() in SetValue!
  677. // Recompute
  678. SetAuxValue(auxCurrUser);
  679. end;
  680. procedure TSGCustomSuperGauge.SetAuxMinValue(AValue: single);
  681. var
  682. auxCurrUser: single;
  683. begin
  684. // Note : MinValue and MaxValue can span negative ranges and be increasing
  685. // or decreasing. Can't really range check here
  686. // If changing min/max must refresh the value to adjust
  687. auxCurrUser := GaugeToUser(FAuxValue, FAuxMinValue, FAuxMaxValue);
  688. FAuxMinValue := AValue; // setting this will change UserToGauge() in SetValue!
  689. // Recompute
  690. SetAuxValue(auxCurrUser);
  691. end;
  692. function TSGCustomSuperGauge.GetAuxValue: single;
  693. begin
  694. // Scale from internal back to user range
  695. Result := GaugeToUser(FAuxValue, FAuxMinValue, FAuxMaxValue);
  696. end;
  697. procedure TSGCustomSuperGauge.SetAuxValue(AValue: single);
  698. var
  699. auxGaugeValue: single;
  700. begin
  701. // Get the user value into gauge value space
  702. auxGaugeValue := UserToGauge(AValue, FAuxMinValue, FAuxMaxValue);
  703. if (FAuxValue = auxGaugeValue) and (not FAuxOverloadTriggeredState) then
  704. Exit;
  705. // Simple overflow here, It will Peg Gauge at MIN or MAX.
  706. // If out of range conditions are at play the gauge Value (FValue) will never
  707. // be out of range. This value is passed to the out of range handler for the
  708. // user to deal with and DO SOMETHING to indicate it.
  709. FAuxValue := AuxCheckOutOfRange(auxGaugeValue);
  710. // We must dirty the Pointer here or no redraw
  711. FAuxPointerSettings.Dirty := True;
  712. DoChange(self);
  713. end;
  714. procedure TSGCustomSuperGauge.SetFaceSettings(AValue: TSGFaceSettings);
  715. begin
  716. if FFaceSettings = AValue then
  717. Exit;
  718. FFaceSettings := AValue;
  719. FFaceSettings.Dirty := True; // set it, as it will need a repaint
  720. DoChange(self);
  721. end;
  722. procedure TSGCustomSuperGauge.SetFrameSettings(AValue: TSGFrameSettings);
  723. begin
  724. if FFrameSettings = AValue then
  725. Exit;
  726. FFrameSettings := AValue;
  727. FFrameSettings.Dirty := True; // set it, as it will need a repaint
  728. DoChange(self);
  729. end;
  730. procedure TSGCustomSuperGauge.SetScaleSettings(AValue: TSGScaleSettings);
  731. begin
  732. if FScaleSettings = AValue then
  733. Exit;
  734. FScaleSettings := AValue;
  735. FScaleSettings.Dirty := True;
  736. DoChange(self);
  737. end;
  738. procedure TSGCustomSuperGauge.SetAuxScaleSettings(AValue: TSGScaleSettings);
  739. begin
  740. if FAuxScaleSettings = AValue then
  741. Exit;
  742. FAuxScaleSettings := AValue;
  743. FAuxScaleSettings.Dirty := True;
  744. DoChange(self);
  745. end;
  746. procedure TSGCustomSuperGauge.SetAllBandsDirtyState(AValue: boolean);
  747. var
  748. i: integer;
  749. begin
  750. // helper to just set all bands to a specific state
  751. for i := low(FBandsSettings) to high(FBandsSettings) do
  752. FBandsSettings[i].Dirty := AValue;
  753. end;
  754. function TSGCustomSuperGauge.IsAnyBandDirty : boolean;
  755. var
  756. i: integer;
  757. begin
  758. // helper to just see if any band has a dirty flag
  759. for i := low(FBandsSettings) to high(FBandsSettings) do
  760. begin
  761. if FBandsSettings[i].Dirty then
  762. exit(True);
  763. end;
  764. result := False;
  765. end;
  766. procedure TSGCustomSuperGauge.SetBandSettings1(AValue: TSGBandSettings);
  767. begin
  768. if FBandsSettings[0] = AValue then
  769. Exit;
  770. FBandsSettings[0] := AValue;
  771. FBandsSettings[0].Dirty := True;
  772. DoChange(self);
  773. end;
  774. procedure TSGCustomSuperGauge.SetBandSettings2(AValue: TSGBandSettings);
  775. begin
  776. if FBandsSettings[1] = AValue then
  777. Exit;
  778. FBandsSettings[1] := AValue;
  779. FBandsSettings[1].Dirty := True;
  780. DoChange(self);
  781. end;
  782. procedure TSGCustomSuperGauge.SetBandSettings3(AValue: TSGBandSettings);
  783. begin
  784. if FBandsSettings[2] = AValue then
  785. Exit;
  786. FBandsSettings[2] := AValue;
  787. FBandsSettings[2].Dirty := True;
  788. DoChange(self);
  789. end;
  790. procedure TSGCustomSuperGauge.SetBandSettings4(AValue: TSGBandSettings);
  791. begin
  792. if FBandsSettings[3] = AValue then
  793. Exit;
  794. FBandsSettings[3] := AValue;
  795. FBandsSettings[3].Dirty := True;
  796. DoChange(self);
  797. end;
  798. procedure TSGCustomSuperGauge.SetAllTextsDirtyState(AValue: boolean);
  799. var
  800. i: integer;
  801. begin
  802. // helper to just set all texts to a specific state
  803. for i := low(FTextsSettings) to high(FTextsSettings) do
  804. FTextsSettings[i].Dirty := AValue;
  805. end;
  806. procedure TSGCustomSuperGauge.SetTextSettings1(AValue: TSGTextSettings);
  807. begin
  808. if FTextsSettings[0] = AValue then
  809. Exit;
  810. FTextsSettings[0] := AValue;
  811. FTextsSettings[0].Dirty := True; // set it, as it will need a repaint
  812. DoChange(self);
  813. end;
  814. procedure TSGCustomSuperGauge.SetTextSettings2(AValue: TSGTextSettings);
  815. begin
  816. if FTextsSettings[1] = AValue then
  817. Exit;
  818. FTextsSettings[1] := AValue;
  819. FTextsSettings[1].Dirty := True;
  820. DoChange(self);
  821. end;
  822. procedure TSGCustomSuperGauge.SetTextSettings3(AValue: TSGTextSettings);
  823. begin
  824. if FTextsSettings[2] = AValue then
  825. Exit;
  826. FTextsSettings[2] := AValue;
  827. FTextsSettings[2].Dirty := True;
  828. DoChange(self);
  829. end;
  830. function TSGCustomSuperGauge.IsAnyMarkerDirty: boolean;
  831. var
  832. i: integer;
  833. begin
  834. // helper to just see if any band has a dirty flag
  835. for i := low(FMarkersSettings) to high(FMarkersSettings) do
  836. begin
  837. if FMarkersSettings[i].Dirty then
  838. exit(True);
  839. end;
  840. result := False;
  841. end;
  842. procedure TSGCustomSuperGauge.SetAllMarkersDirtyState(AValue: boolean);
  843. var
  844. i: integer;
  845. begin
  846. // helper to just set all markers to a specific state
  847. for i := low(FMarkersSettings) to high(FMarkersSettings) do
  848. FMarkersSettings[i].Dirty := AValue;
  849. end;
  850. procedure TSGCustomSuperGauge.SetMarkerSettings1(AValue: TSGMarkerSettings);
  851. begin
  852. if FMarkersSettings[0] = AValue then
  853. Exit;
  854. FMarkersSettings[0] := AValue;
  855. FMarkersSettings[0].Dirty := True;
  856. DoChange(self);
  857. end;
  858. procedure TSGCustomSuperGauge.SetMarkerSettings2(AValue: TSGMarkerSettings);
  859. begin
  860. if FMarkersSettings[1] = AValue then
  861. Exit;
  862. FMarkersSettings[1] := AValue;
  863. FMarkersSettings[1].Dirty := True;
  864. DoChange(self);
  865. end;
  866. procedure TSGCustomSuperGauge.SetMarkerSettings3(AValue: TSGMarkerSettings);
  867. begin
  868. if FMarkersSettings[2] = AValue then
  869. Exit;
  870. FMarkersSettings[2] := AValue;
  871. FMarkersSettings[2].Dirty := True;
  872. DoChange(self);
  873. end;
  874. procedure TSGCustomSuperGauge.SetPointerSettings(AValue: TSGPointerSettings);
  875. begin
  876. if FPointerSettings = AValue then
  877. Exit;
  878. FPointerSettings := AValue;
  879. FPointerSettings.Dirty := True;
  880. DoChange(self);
  881. end;
  882. procedure TSGCustomSuperGauge.SetAuxPointerSettings(AValue: TSGPointerSettings);
  883. begin
  884. if FAuxPointerSettings = AValue then
  885. Exit;
  886. FAuxPointerSettings := AValue;
  887. FAuxPointerSettings.Dirty := True;
  888. DoChange(self);
  889. end;
  890. procedure TSGCustomSuperGauge.SetRangeLEDSettings(AValue: TSGRangeCheckLEDSettings);
  891. begin
  892. if FRangeLEDSettings = AValue then
  893. Exit;
  894. FRangeLEDSettings := AValue;
  895. FRangeLEDSettings.Dirty := True;
  896. DoChange(self);
  897. end;
  898. procedure TSGCustomSuperGauge.SetPointerCapSettings(AValue: TSGPointerCapSettings);
  899. begin
  900. if FPointerCapSettings = AValue then
  901. Exit;
  902. FPointerCapSettings := AValue;
  903. FPointerCapSettings.Dirty := True;
  904. DoChange(self);
  905. end;
  906. procedure TSGCustomSuperGauge.DoSetBounds(ALeft, ATop, AWidth, AHeight: Integer);
  907. begin
  908. inherited;
  909. FDirty := true; // Called on Resize of component
  910. end;
  911. procedure TSGCustomSuperGauge.DoChange(Sender: TObject);
  912. begin
  913. Invalidate;
  914. end;
  915. procedure TSGCustomSuperGauge.DoRangeLEDChange(Sender: TObject);
  916. begin
  917. // This is needed as anytime a RangeLED settings is updated we
  918. // MAY need to update and call event handlers. update as the RangeLEDSettings.Dirty
  919. CheckRangeLED(Value);
  920. DoChange(self);
  921. end;
  922. procedure TSGCustomSuperGauge.DoPictureChange(Sender: TObject);
  923. begin
  924. // This is similar to DoRangeLEDChange, if we have a picture change this
  925. // is how we can propagate it up to the gauge to tell if a repaint is needed.
  926. FaceSettings.Dirty := True; // trigger a redraw since the image has changed
  927. DoChange(Sender);
  928. end;
  929. procedure TSGCustomSuperGauge.DoChangeFont1(ASender: TObject; AData: PtrInt);
  930. begin
  931. // Simlar to the regular dochange but needed a different procedure signature
  932. // so just a wrapper, TObject is not a gauge so use Self here for DoChange()
  933. FTextsSettings[0].Dirty := True;
  934. DoChange(self);
  935. end;
  936. procedure TSGCustomSuperGauge.DoChangeFont2(ASender: TObject; AData: PtrInt);
  937. begin
  938. // Simlar to the regular dochange but needed a different procedure signature
  939. // so just a wrapper, TObject is not a gauge so use Self here for DoChange()
  940. FTextsSettings[1].Dirty := True;
  941. DoChange(self);
  942. end;
  943. procedure TSGCustomSuperGauge.DoChangeFont3(ASender: TObject; AData: PtrInt);
  944. begin
  945. // Simlar to the regular dochange but needed a different procedure signature
  946. // so just a wrapper, TObject is not a gauge so use Self here for DoChange()
  947. FTextsSettings[2].Dirty := True;
  948. DoChange(self);
  949. end;
  950. procedure TSGCustomSuperGauge.Paint;
  951. var
  952. i: integer;
  953. offsetX, offsetY: integer;
  954. gaugeCenX, gaugeCenY: integer;
  955. begin
  956. inherited Paint;
  957. // ResolveSizes calculates scale and a few other sizes as needed. It must
  958. // be called PRIOR to drawing anything to get scale and related set up.
  959. ResolveSizes;
  960. // IF the component is resized OR moved (this is safer) we
  961. // need to make sure EVERYTHING redraws. The base class will
  962. // also do it's own thing to invalidate and redraw it all.
  963. if FDirty then
  964. begin
  965. FFrameSettings.Dirty := True;
  966. FFaceSettings.Dirty := True;
  967. FScaleSettings.Dirty := True;
  968. FAuxScaleSettings.Dirty := True;
  969. SetAllBandsDirtyState(True);
  970. SetAllTextsDirtyState(True);
  971. FRangeLEDSettings.Dirty := True;
  972. FPointerCapSettings.Dirty := True;
  973. FPointerSettings.Dirty := True;
  974. FAuxPointerSettings.Dirty := True;
  975. SetAllMarkersDirtyState(True);
  976. FDirty := False; // everything here marked, so can reset
  977. end;
  978. // Now start Drawing into the offscreen bitmaps. IF the particular
  979. // subcomponent is not changed, the DrawXXXX will just leave it as is
  980. // and not waste cycles to redraw it wi
  981. FGaugeBitmap.SetSize(Width, Height); // should always be the components full size
  982. // If the gauge color is clNone then we start with a transparent background,
  983. // Otherwise we start with the users color.
  984. if Color = clNone then
  985. FGaugeBitmap.Fill(BGRA(0, 0, 0, 0)) // fill transparent
  986. else
  987. FGaugeBitmap.Fill(ColorToBGRA(Color, 255)); // fill solid color
  988. gaugeCenX := FGaugeBitmap.Width div 2;
  989. gaugeCenY := FGaugeBitmap.Height div 2;
  990. // Face, Frame, Scale and Bands are usually static, so do yet another
  991. // bitmap for these that will require less Blend Images.
  992. DrawMulti;
  993. FGaugeBitmap.BlendImage(0, 0, FMultiBitmap, boLinearBlend);
  994. // now draw any texts if enabled and dirty
  995. for i := low(FTextsSettings) to high(FTextsSettings) do
  996. begin
  997. if FTextsSettings[i].Enabled then
  998. begin
  999. DrawText(FTextsBitmaps[i], FTextsSettings[i]);
  1000. offsetX := Round(FResolvedSizes.Scale * FTextsSettings[i].OffsetX) + gaugeCenX - FTextsBitmaps[i].Width div 2;
  1001. offsetY := Round(FResolvedSizes.Scale * FTextsSettings[i].OffsetY) + gaugeCenY - FTextsBitmaps[i].Height div 2;
  1002. FGaugeBitmap.BlendImage(offsetX, offsetY, FTextsBitmaps[i], boLinearBlend);
  1003. end;
  1004. end;
  1005. FGaugeBitmap.BlendImage(offsetX, offsetY, FTextBitmap, boLinearBlend);
  1006. // Draw range LED, small bitmap so center and move to needed position
  1007. DrawLED;
  1008. offsetX := Round(FResolvedSizes.Scale * FRangeLEDSettings.OffsetX + gaugeCenX - FLEDActiveBitmap.Width / 2);
  1009. offsetY := Round(FResolvedSizes.Scale * FRangeLEDSettings.OffsetY + gaugeCenY - FLEDActiveBitmap.height / 2);
  1010. // Set up the LED, if user sets Active state will keep led on even if
  1011. // the out of range state is set.
  1012. if FRangeLEDSettings.Active then
  1013. FGaugeBitmap.BlendImage(offsetX, offsetY, FLEDActiveBitmap, boLinearBlend)
  1014. else
  1015. FGaugeBitmap.BlendImage(offsetX, offsetY, FLEDInActiveBitmap, boLinearBlend);
  1016. // Draw Markers BEFORE the pointer(s)
  1017. DrawMarkers;
  1018. FGaugeBitmap.BlendImage(0, 0, FMarkerBitmap,boLinearBlend);
  1019. // Draw cap over or under the Main pointer. Note that the pointer is a special
  1020. // case when drawing since it's almost always dirty.
  1021. // Always draw aux point always under everything both the regular pointer and cap
  1022. DrawPointer(FAuxPointerSettings, FAuxValue);
  1023. if PointerCapSettings.CapStyle <> csNone then
  1024. begin
  1025. DrawPointerCap;
  1026. offsetX := gaugeCenX - FPointerCapBitmap.Width div 2;
  1027. offsetY := gaugeCenY - FPointerCapBitmap.Height div 2;
  1028. if PointerCapSettings.CapPosition = cpOver then
  1029. begin
  1030. DrawPointer(FPointerSettings, FValue);
  1031. FGaugeBitmap.BlendImage(offsetX, offsetY, FPointerCapBitmap, boLinearBlend); // Cap on top
  1032. end
  1033. else
  1034. begin
  1035. FGaugeBitmap.BlendImage(offsetX, offsetY, FPointerCapBitmap, boLinearBlend); // Cap on Bottom
  1036. DrawPointer(FPointerSettings, FValue);
  1037. end;
  1038. end
  1039. else
  1040. begin
  1041. DrawPointer(FPointerSettings, FValue);
  1042. end;
  1043. // make it all visable to the user!
  1044. FGaugeBitmap.Draw(Canvas, 0, 0, False);
  1045. end;
  1046. procedure TSGCustomSuperGauge.DrawMulti;
  1047. begin
  1048. // The strategy here is that these typically only change infrequently
  1049. // so just draw as a bundle and saves some blendimages calls. Each of the
  1050. // drawXXX still handles it's own dirty flag. The bitmap will be set up
  1051. // as on instantiation so all of the others have their dirty flag set True, so no
  1052. // need to do any initialization. Makes painting much faster even
  1053. // with the individual dirty flags!
  1054. if FFrameSettings.Dirty or FFaceSettings.Dirty
  1055. or FScaleSettings.Dirty or FAuxScaleSettings.Dirty
  1056. or IsAnyBandDirty then
  1057. begin
  1058. Initializebitmap(FMultiBitmap, Width, Height);
  1059. DrawFrame;
  1060. FMultiBitmap.BlendImage(0, 0, FFrameBitmap, boLinearBlend);
  1061. DrawFace;
  1062. FMultiBitmap.BlendImage(0, 0, FFaceBitmap, boLinearBlend);
  1063. DrawBands; // will handle the enable/disable and draw of each band
  1064. FMultiBitmap.BlendImage(0, 0, FBandBitmap, boLinearBlend);
  1065. DrawScales;
  1066. FMultiBitmap.BlendImage(0, 0, FScaleBitmap, boLinearBlend);
  1067. end;
  1068. end;
  1069. procedure TSGCustomSuperGauge.DrawFrame;
  1070. var
  1071. Origin: TSGOrigin;
  1072. begin
  1073. if not FrameSettings.Dirty then
  1074. Exit;
  1075. // Frame is 3 parts, inner, middle, outer, each with
  1076. // a different color, possibly shading if someone wants to add it.
  1077. //
  1078. // The Frame will scale with the component size, so this is a bit
  1079. // different then other parts of the gauge.
  1080. FrameSettings.Dirty := False;
  1081. // Dirty the Face, it's going to also need a redraw if we are here
  1082. // Also implies face is draw AFTER this
  1083. FaceSettings.Dirty := True; // need to dirty the FACE, cascade.
  1084. Origin := Initializebitmap(FFrameBitmap, Width, Height);
  1085. //Outer
  1086. FFrameBitmap.EllipseAntialias(Origin.CenterPoint.x, Origin.CenterPoint.y,
  1087. FResolvedSizes.OuterFrameInsideRadius,
  1088. FResolvedSizes.OuterFrameInsideRadius,
  1089. FFrameSettings.OuterFrameColor, FResolvedSizes.OuterFrameThickness + 0.5);
  1090. // Middle
  1091. FFrameBitmap.EllipseAntialias(Origin.CenterPoint.x, Origin.CenterPoint.y,
  1092. FResolvedSizes.MiddleFrameInsideRadius,
  1093. FResolvedSizes.MiddleFrameInsideRadius,
  1094. FFrameSettings.MiddleFrameColor, FResolvedSizes.MiddleFrameThickness + 0.5);
  1095. // Innermost
  1096. FFrameBitmap.EllipseAntialias(Origin.CenterPoint.x, Origin.CenterPoint.y,
  1097. FResolvedSizes.InnerFrameInsideRadius,
  1098. FResolvedSizes.InnerFrameInsideRadius,
  1099. FFrameSettings.InnerFrameColor, FResolvedSizes.InnerFrameThickness + 0.5);
  1100. end;
  1101. procedure TSGCustomSuperGauge.DrawFace;
  1102. var
  1103. OriginFace: TSGOrigin;
  1104. r: single;
  1105. d: integer;
  1106. xb, yb: integer;
  1107. d2, h: single;
  1108. Center: TPointF;
  1109. v: TPointF;
  1110. p: PBGRAPixel;
  1111. Image: TBGRABitmap;
  1112. Mask: TBGRABitmap;
  1113. Map: TBGRABitmap;
  1114. begin
  1115. if not FaceSettings.Dirty then
  1116. Exit;
  1117. FFaceSettings.Dirty := False;
  1118. OriginFace := Initializebitmap(FFaceBitmap, Width, Height);
  1119. r := FResolvedSizes.FaceRadiusStart; // this is the inner size from the last drawn Frame ring
  1120. // Fill types : fsNone, fsGradient, fsFlat, fsPhong
  1121. case FFaceSettings.FillStyle of
  1122. fsGradient:
  1123. begin
  1124. FFaceBitmap.FillEllipseLinearColorAntialias(OriginFace.CenterPoint.x,
  1125. OriginFace.CenterPoint.y, r, r, FFaceSettings.OuterColor,
  1126. FFaceSettings.InnerColor);
  1127. end;
  1128. fsFlat:
  1129. begin
  1130. FFaceBitmap.FillEllipseAntialias(OriginFace.CenterPoint.x, OriginFace.CenterPoint.y,
  1131. r, r, FFaceSettings.InnerColor);
  1132. end;
  1133. fsPhong:
  1134. begin
  1135. d := Round(r * 2);
  1136. Center := PointF((d - 1) / 2, (d - 1) / 2);
  1137. Map := TBGRABitmap.Create(d, d);
  1138. for yb := 0 to d - 1 do
  1139. begin
  1140. p := Map.ScanLine[yb];
  1141. for xb := 0 to d - 1 do
  1142. begin
  1143. // compute vector between center and current pixel
  1144. v := PointF(xb, yb) - Center;
  1145. // scale down to unit circle (with 1 pixel margin for soft border)
  1146. v.x := v.x / (r + 1);
  1147. v.y := v.y / (r + 1);
  1148. // compute squared distance with scalar product
  1149. d2 := v {$if FPC_FULLVERSION < 30203}*{$ELSE}**{$ENDIF} v;
  1150. // interpolate as quadratic curve and apply power function
  1151. if d2 > 1 then
  1152. h := 0
  1153. else
  1154. h := power(1 - d2, FFaceSettings.CurveExponent);
  1155. p^ := MapHeightToBGRA(h, 255);
  1156. Inc(p);
  1157. end;
  1158. end;
  1159. // mask image round with and antialiased border
  1160. Mask := TBGRABitmap.Create(d, d, BGRABlack);
  1161. Mask.FillEllipseAntialias(Center.x, Center.y, r, r, BGRAWhite);
  1162. Map.ApplyMask(Mask);
  1163. Mask.Free;
  1164. // now draw
  1165. FFaceSettings.FPhong.Draw(FFaceBitmap, Map, 30,
  1166. Round(OriginFace.CenterPoint.x - r), Round(OriginFace.CenterPoint.y - r),
  1167. FFaceSettings.InnerColor);
  1168. Map.Free;
  1169. end;
  1170. end;
  1171. // see if valid size and enabled, draw if so!
  1172. if ((FaceSettings.Picture.Width > 0) or (FaceSettings.Picture.Height > 0)) and (FFaceSettings.PictureEnabled) then
  1173. begin
  1174. Image := TBGRABitmap.Create(FaceSettings.Picture.Bitmap);
  1175. // Resize the image if we are in AutoScale Mode
  1176. if FAutoScale then
  1177. BGRAReplace(Image, Image.Resample(Round(Image.Width * FResolvedSizes.Scale), Round(Image.Height * FResolvedSizes.Scale), rmFineResample));
  1178. FFaceBitmap.BlendImage(
  1179. OriginFace.CenterPoint.X + Round(FFaceSettings.PictureOffsetX * FResolvedSizes.Scale),
  1180. OriginFace.CenterPoint.y + Round(FFaceSettings.PictureOffsetY * FResolvedSizes.Scale),
  1181. image,
  1182. boLinearBlend);
  1183. Image.Free; // needed!
  1184. end;
  1185. end;
  1186. procedure TSGCustomSuperGauge.DrawBands;
  1187. var
  1188. i: integer;
  1189. begin
  1190. // Draw multiple bands on the same bitmap. we can do this since
  1191. // we are drawing over the entire fullsized bitmap. Since
  1192. // this is the case, you can draw all of the bands here in one shot
  1193. // and on one bitmap. Init bitmap here!
  1194. // Only change if something dirty
  1195. // nothing dirty, no init, no draw, just bounce!
  1196. if not IsAnyBandDirty then
  1197. exit;
  1198. Initializebitmap(FBandBitmap, Width, Height); // clear it before we draw anything
  1199. for i := low(FBandsSettings) to high(FBandsSettings) do
  1200. begin
  1201. FBandsSettings[i].Dirty := True; // force draw, if any band is dirty they are all dirty
  1202. DrawBand(FBandsSettings[i], FResolvedSizes.Scale); // will clear any dirty for specific band
  1203. end;
  1204. end;
  1205. procedure TSGCustomSuperGauge.DrawBand(const BandSettings: TSGBandSettings; BandScale: single);
  1206. var
  1207. BandRadius, TextRadius: single;
  1208. TextSize: integer;
  1209. baseAngle, startAngle, endAngle: single;
  1210. cenX, cenY: integer;
  1211. fontRenderer: TBGRAVectorizedFontRenderer;
  1212. TextPath: TBGRAPath;
  1213. begin
  1214. if not BandSettings.Dirty then
  1215. Exit;
  1216. BandSettings.Dirty := False;
  1217. // Now, if not enabled we can leave if flag reset!
  1218. if not BandSettings.Enabled then
  1219. exit;
  1220. TextSize := Round(BandSettings.TextSize * 15 * BandScale);
  1221. // Needs to be the components sizes here
  1222. cenX := Width div 2;
  1223. cenY := Height div 2;
  1224. BandRadius := Round((BandSettings.BandRadius - BandSettings.BandThickness / 2) * BandScale); // may need to adjust for band thickness
  1225. TextRadius := Round((BandSettings.TextRadius - BandSettings.BandThickness) * BandScale) ; // offset to center
  1226. // Start = 225 degree is 0 on gague scale (Not the angle), and -45 degree is 100 on scale
  1227. // 270, down (gauge angle 0)180 flat, increase moves towards 0 decrease towards 100
  1228. // 0 is flat line, right most end. Increase goes backwards towards 0, -45 is 100 percent on scale
  1229. baseAngle := 225 * PI / 180;
  1230. startAngle := baseAngle - ((BandSettings.StartValue * 270 / 100) * PI / 180);
  1231. endAngle := baseAngle - ((BandSettings.EndValue * 270 / 100) * PI / 180);
  1232. FBandBitmap.LineCap := pecFlat; // caps should be flat
  1233. FBandBitmap.Arc(
  1234. cenX, cenY,
  1235. BandRadius + 0.5, BandRadius + 0.5, // push down a bit
  1236. // (360-135) 225, -45
  1237. // 3.92699,-0.785398, // must use start and end angle, internally Point calcs won't work due to arcsin2() limits
  1238. startAngle, endAngle,
  1239. BandSettings.BandColor,
  1240. BandSettings.BandThickness * BandScale,
  1241. false,
  1242. BGRA(0,0,0,0) // last param is alpha, so no interior color, inner routings ONLY draw the arc, no fill
  1243. );
  1244. if BandSettings.EnableText then
  1245. begin
  1246. FontRenderer := TBGRAVectorizedFontRenderer.Create;
  1247. FBandBitmap.FontRenderer := fontRenderer; // assign text vectorial font renderer
  1248. FBandBitmap.FontHeight := round(TextSize * 0.09);
  1249. FBandBitmap.FontQuality := fqFineAntialiasing;
  1250. FBandBitmap.FontName := BandSettings.TextFont;
  1251. FBandBitmap.FontStyle := BandSettings.TextStyle;
  1252. FontRenderer.OutlineColor := BGRABlack;
  1253. FontRenderer.OutlineWidth := TextSize / 600;
  1254. FontRenderer.OutlineVisible := true;
  1255. FBandBitmap.FontVerticalAnchor := fvaBaseline;
  1256. TextPath := TBGRAPath.Create;
  1257. // drawing is backwards on textpath
  1258. TextPath.Arc(cenX, cenY, TextRadius, -startAngle, -endAngle, False);
  1259. FBandBitmap.TextOutCurved(TextPath, BandSettings.Text, BandSettings.TextColor, taCenter, 0);
  1260. end;
  1261. end;
  1262. procedure TSGCustomSuperGauge.DrawText(TextBitmap: TBGRABitmap; const TextSettings: TSGTextSettings);
  1263. var
  1264. TextBoxWidth, TextBoxHeight: integer;
  1265. TextRect: TRect;
  1266. SaveFont: TBCFont;
  1267. begin
  1268. if not TextSettings.Dirty then
  1269. Exit;
  1270. TextSettings.Dirty := False;
  1271. // Need to save off the current Fonts size, as we may need to update it
  1272. // if the Gauge is scaled. We can't just pass the TextsSettings.FontEX because
  1273. // we need to change the size of the font, and in doing so will cause the
  1274. // Gauge to be dirty and refresh, and refresh, and refresh... you get the idea.
  1275. SaveFont := TBCFont.Create(nil);
  1276. SaveFont.Assign(TextSettings.FontEx); // assign will not copy the OnChange
  1277. // If scaling of other elements (Shadow, Offsets, etc are needed just use
  1278. // the Scale() functionality. For now we only need the Height scaled
  1279. // so can save some cycles and just scale it.
  1280. // SaveFont.Scale(TextScale, True); // Awesome, takes care of all elements (height, shadows, etc)
  1281. SaveFont.Height := round(SaveFont.Height * FResolvedSizes.Scale); // only using this from the font for now
  1282. // get the bounding box so we can create a SMALLER bitmap. This will be referenced
  1283. // to the Center of the text and gauge
  1284. CalculateTextSize(TextSettings.Text, SaveFont, TextBoxWidth, TextBoxHeight, TextSettings.FontEx.Shadow);
  1285. Initializebitmap(TextBitmap, TextBoxWidth, TextBoxHeight);
  1286. // Set up text bounding box,
  1287. TextRect.Left := 0;
  1288. TextRect.Top := 0;
  1289. TextRect.Height := TextBoxHeight;
  1290. TextRect.Width := TextBoxWidth;
  1291. // Draw into the TextBitmap for later use
  1292. RenderText(TextRect, SaveFont, TextSettings.Text, TextBitmap, Enabled);
  1293. // Clean it up
  1294. SaveFont.Free;
  1295. end;
  1296. procedure TSGCustomSuperGauge.DrawScales;
  1297. begin
  1298. if FScaleSettings.Dirty or FAuxScaleSettings.Dirty then
  1299. begin
  1300. Initializebitmap(FScaleBitmap, Width, Height);
  1301. FScaleSettings.Dirty := True;
  1302. FAuxScaleSettings.Dirty := True;
  1303. end;
  1304. // Draw AuxScale first so it's on the bottom and under the MainScale
  1305. DrawScale(FAuxScaleSettings, FResolvedSizes.Scale);
  1306. DrawScale(FScaleSettings, FResolvedSizes.Scale);
  1307. end;
  1308. procedure TSGCustomSuperGauge.DrawScale(const Settings: TSGScaleSettings; Scale: single);
  1309. var
  1310. cenX, cenY: integer;
  1311. i, n: integer;
  1312. x, y, xt, yt:single;
  1313. pStart, pEnd: TPointF;
  1314. startAngle, endAngle: single;
  1315. innerTicRadius: single;
  1316. scaleStartValue, scaleBump: integer;
  1317. scaleRadius, scaleTextRadius: single;
  1318. scaleTextSize: integer;
  1319. scaleMainTickLength, scaleSubTickLength: single;
  1320. scaleMainTickThickness, scaleSubTickThickness: single;
  1321. scaleInnerTickArcThickness, scaleOuterTickArcThickness: single;
  1322. begin
  1323. // if nothing dirty then skip it, we have a bitmap with
  1324. // the scale already drawn. This is slow so saves a lot of time
  1325. // as scales are slow to draw
  1326. if not Settings.Dirty then
  1327. Exit;
  1328. Settings.Dirty := False; // mark as clean, so next run will not need a rebuild!
  1329. if not Settings.Enabled then
  1330. Exit;
  1331. cenX := Width div 2;
  1332. cenY := Height div 2;
  1333. scaleRadius := Settings.ScaleRadius * FResolvedSizes.Scale;
  1334. scaleTextRadius := Settings.TextRadius * FResolvedSizes.Scale;
  1335. scaleTextSize := Round(Settings.TextSize * FResolvedSizes.Scale);
  1336. scaleMainTickLength := Settings.MainTickLength * Scale;
  1337. scaleSubTickLength := Settings.SubTickLength * Scale;
  1338. scaleMainTickThickness := Settings.MainTickThickness * Scale;
  1339. scaleSubTickThickness := Settings.SubTickThickness * Scale;
  1340. scaleInnerTickArcThickness := Settings.InnerTickArcThickness * Scale;
  1341. scaleOuterTickArcThickness := Settings.OuterTickArcThickness * Scale;
  1342. // Needs to be flat caps for scale and related
  1343. FScaleBitmap.LineCap := pecFlat;
  1344. // Draw SubTicks
  1345. if Settings.EnableSubTicks then
  1346. begin
  1347. n := Settings.MainTickCount * Settings.SubTickCount;
  1348. for i := 0 to n do
  1349. begin
  1350. // Calculate draw from point
  1351. X := cenX - (scaleRadius * cos((-45 + i * 270 / n) * Pi / 180));
  1352. Y := cenY - (scaleRadius * sin((-45 + i * 270 / n) * Pi / 180));
  1353. // Calculate draw to point
  1354. Xt := cenX - (scaleRadius - scaleSubTickLength) *
  1355. cos((-45 + i * 270 / n) * Pi / 180);
  1356. Yt := cenY - (scaleRadius - scaleSubTickLength) *
  1357. sin((-45 + i * 270 / n) * Pi / 180);
  1358. if scaleSubTickThickness > 0 then
  1359. begin
  1360. if Settings.SubTickUseDots then
  1361. begin
  1362. // Can draw the center at the outer (fixed) or the Inner
  1363. // which is based on the LENGTH of the tick
  1364. FScaleBitmap.FillEllipseAntialias(xt, yt,
  1365. scaleSubTickThickness, scaleSubTickThickness,
  1366. Settings.TickColor)
  1367. end
  1368. else
  1369. FScaleBitmap.DrawLineAntialias(x, y, xt, yt, Settings.TickColor, scaleSubTickThickness);
  1370. end;
  1371. // if dot mode, might not be needed, but likely don't use taboth and dots
  1372. if (Settings.TickArcStyle = taboth) and (not Settings.EnableMainTicks) then
  1373. begin
  1374. // need caps on the ends so the gauge doesn't look stupid if both inner and outer
  1375. // tic arcs are visiable
  1376. if (i = 0) or (i = n) then
  1377. begin
  1378. if not Settings.EnableMainTicks then
  1379. innerTicRadius := scaleSubTickLength
  1380. else
  1381. innerTicRadius := scaleMainTickLength;
  1382. // draw end pieces in the MainTick thickness to match
  1383. x := cenX - (scaleRadius + scaleOuterTickArcThickness / 2) * cos((-45 + i * 270 / n) * Pi / 180);
  1384. y := ceny - (scaleRadius + scaleOuterTickArcThickness / 2) * sin((-45 + i * 270 / n) * Pi / 180);
  1385. Xt := cenX - (scaleRadius - innerTicRadius - scaleOuterTickArcThickness / 2) *
  1386. cos((-45 + i * 270 / n) * Pi / 180);
  1387. Yt := ceny - (scaleRadius - innerTicRadius - scaleOuterTickArcThickness / 2) *
  1388. sin((-45 + i * 270 / n) * Pi / 180);
  1389. FScaleBitmap.DrawLineAntialias(x, y, xt, yt, Settings.TickColor,
  1390. scaleMainTickThickness);
  1391. end;
  1392. end;
  1393. end;
  1394. end;
  1395. // Draw after the sub-ticks so main ticks are on top
  1396. if Settings.EnableMainTicks then
  1397. begin
  1398. n := Settings.MainTickCount;
  1399. for i := 0 to n do
  1400. begin
  1401. // Draw main ticks
  1402. // Calculate draw from point bottom, to compensate for the width of the first and last
  1403. // Main Ticks we adust the starting point so no gap. This is easier then trying
  1404. // to extend the arc start and end (Have at it if you want)
  1405. x := cenX - (scaleRadius + scaleOuterTickArcThickness / 2) * cos((-45 + i * 270 / n) * Pi / 180);
  1406. y := cenY - (scaleRadius + scaleOuterTickArcThickness / 2) * sin((-45 + i * 270 / n) * Pi / 180);
  1407. // Calculate draw to point top, again trying to compensate for the widths of things for the endpoints
  1408. // if widths of the ticks get too big the edges will bleed out of the TickArc.
  1409. xt := cenX - (scaleRadius - scaleMainTickLength + 1) *
  1410. cos((-45 + i * 270 / n) * Pi / 180);
  1411. yt := cenY - (scaleRadius - scaleMainTickLength + 1) *
  1412. sin((-45 + i * 270 / n) * Pi / 180);
  1413. if scaleMainTickThickness > 0 then
  1414. begin
  1415. if Settings.MainTickUseDots then
  1416. begin
  1417. // Can draw the center at the outer (fixed) or the Inner
  1418. // which is based on the LENGTH of the tick
  1419. FScaleBitmap.FillEllipseAntialias(xt, yt,
  1420. scaleMainTickThickness, scaleMainTickThickness,
  1421. Settings.TickColor)
  1422. end
  1423. else
  1424. FScaleBitmap.DrawLineAntialias(x, y, xt, yt, Settings.TickColor, scaleMainTickThickness);
  1425. end;
  1426. end;
  1427. end;
  1428. // Draw text, these are only for the Main Ticks
  1429. if Settings.EnableScaleText then
  1430. begin
  1431. FScaleBitmap.FontName := Settings.TextFont;
  1432. FScaleBitmap.FontHeight := scaleTextSize;
  1433. FScaleBitmap.FontQuality := fqFineAntialiasing;
  1434. FScaleBitmap.FontStyle := Settings.TextStyle;
  1435. n := Settings.MainTickCount;
  1436. // if draw the scale reversed, do some tricky stuff so we can
  1437. // count up or down. Start is swapped with the actual end value here
  1438. if Settings.ReverseScale then
  1439. begin
  1440. scaleBump := -1;
  1441. scaleStartValue := n * Settings.Step + Settings.Start;
  1442. end
  1443. else
  1444. begin
  1445. scaleBump := 1;
  1446. scaleStartValue := Settings.Start;
  1447. end;
  1448. // Draw text for main ticks
  1449. for i := 0 to n do
  1450. begin
  1451. xt := cenX - scaleTextRadius * cos((-45 + i * 270 / n) * Pi / 180);
  1452. yt := cenY - scaleTextRadius * sin((-45 + i * 270 / n) * Pi / 180);
  1453. FScaleBitmap.TextOut(xt, yt - (FScaleBitmap.FontHeight / 1.7),
  1454. IntToStr(scaleStartValue + i * Settings.Step * scaleBump),
  1455. Settings.TextColor, taCenter);
  1456. end;
  1457. end;
  1458. // draw outer arcs
  1459. if (Settings.TickArcStyle = taOuter) or (Settings.TickArcStyle = taboth) then
  1460. begin
  1461. // draw arc OUSIDE on the tics, doesn't matter main or sub, all at the top
  1462. // inner of tic
  1463. pStart.x := cenX - ScaleRadius * cos(-45 * Pi / 180);
  1464. pStart.y := cenY - ScaleRadius * sin(-45 * Pi / 180);
  1465. // Start angle will compensate for the width of the MAJOR tick
  1466. startAngle := arctan2((cenY - pStart.y),
  1467. (cenX - pStart.x)) + 4.71239; // add 270 0.0174533 1 deg in radians
  1468. // Calculate draw to point outer
  1469. pEnd.x := cenX - (ScaleRadius - scaleMainTickLength) * cos(225 * Pi / 180);
  1470. pEnd.y := cenY - (ScaleRadius - scaleMainTickLength) * sin(225 * Pi / 180);
  1471. // Same for End Angle
  1472. endAngle := -arctan2((pEnd.y - cenY),(pEnd.x - cenX));
  1473. FScaleBitmap.Arc(cenX, cenY,
  1474. ScaleRadius + 0.5, ScaleRadius + 0.5, // push down a bit
  1475. startAngle, endAngle,
  1476. Settings.TickArcColor,
  1477. scaleOuterTickArcthickness,
  1478. false,
  1479. BGRA(0,0,0,0) // last param is alpha, so no interior color, inner routings ONLY draw the arc, no fill
  1480. );
  1481. end;
  1482. if (Settings.TickArcStyle = taInner) or (Settings.TickArcStyle = taBoth) then
  1483. begin
  1484. // Inner will chose main tics (for now) if both main and sub tics on)
  1485. // will need to find out the radius for what selected... or do something
  1486. // like use what ever tic is LONGER (logic here will need a change)
  1487. // draw arc OUSIDE on the tics, doesn't matter main or sub, all at the top
  1488. // inner of tick
  1489. pStart.x := cenX - ScaleRadius * cos(-45 * Pi / 180);
  1490. pStart.y := cenY - ScaleRadius * sin(-45 * Pi / 180);
  1491. startAngle := arctan2((cenY - pStart.y),(cenX - pStart.x)) + 4.71239; // add 270
  1492. // Calculate draw to point outer
  1493. pEnd.x := cenX - (ScaleRadius - scaleMainTickLength) * cos(225 * Pi / 180);
  1494. pEnd.y := cenY - (ScaleRadius - scaleMainTickLength) * sin(225 * Pi / 180);
  1495. endAngle := -arctan2((pEnd.y - cenY),(pEnd.x - cenX));
  1496. // be nice and if not displaying main tics, use the sub tic length to bottom
  1497. // up against them
  1498. if not Settings.EnableMainTicks then
  1499. innerTicRadius := scaleSubTickLength
  1500. else
  1501. innerTicRadius := scaleMainTickLength;
  1502. FScaleBitmap.Arc(
  1503. cenX, cenY,
  1504. ScaleRadius - innerTicRadius+2, ScaleRadius - innerTicRadius+2,
  1505. startAngle, endAngle,
  1506. Settings.TickArcColor,
  1507. scaleInnerTickArcThickness,
  1508. false,
  1509. BGRA(0,0,0,0) // last param is alpha, so no interior color, inner routings ONLY draw the arc, no fill
  1510. );
  1511. end;
  1512. end;
  1513. procedure TSGCustomSuperGauge.DrawPointer(const Settings: TSGPointerSettings; Value: single);
  1514. var
  1515. Origin: TSGOrigin;
  1516. x, y, x1, y1: single;
  1517. subEx: single;
  1518. PointerLength, PointerExtensionLength, PointerWidth, HighlightThickness: single;
  1519. PointerCapRadius: single;
  1520. startAngle, endAngle: single;
  1521. bandRadius: single;
  1522. vecLen: single;
  1523. A, B, U, V: TPointF;
  1524. begin
  1525. // Pointers are ALWAYS DRAWN since we don't create a specific pointer
  1526. // bitmap to blend (and save) we need to always draw, this is faster
  1527. // then creating another full size bitmap
  1528. //
  1529. // if not Settings.Dirty then
  1530. // Exit;
  1531. // Settings.Dirty := False;
  1532. if not Settings.Enabled then
  1533. Exit;
  1534. Origin.CenterPoint.X:= FGaugeBitmap.Width div 2;
  1535. Origin.CenterPoint.Y:= FGaugeBitmap.Height div 2;
  1536. // radius is smaller of the 2 dimensions
  1537. Origin.Radius := FResolvedSizes.MinRadius;
  1538. // Set the pointer length, does not apply to arc
  1539. PointerLength := Settings.Length * FResolvedSizes.Scale;
  1540. PointerExtensionLength := Settings.ExtensionLength * FResolvedSizes.Scale;
  1541. PointerWidth := Settings.Width * FResolvedSizes.Scale;
  1542. PointerCapRadius := FPointerCapSettings.Radius * FResolvedSizes.Scale;
  1543. HighlightThickness := Settings.HighlightThickness * FResolvedSizes.Scale;
  1544. // draw the arc style of pointer
  1545. if (Settings.Style = psLine) or (Settings.Style = psLineExt) then
  1546. begin
  1547. // if we are need to draw the extension behind the cap, we can
  1548. // recalc the ending point to just do one line draw instead of
  1549. // 2 discrete lines from the center. That is easier, but slower.
  1550. // If extension len is 0, skip as will show a partial pixel
  1551. FGaugeBitMap.LineCap := pecRound; // caps should be round for line type pointers
  1552. if (Settings.Style = psLineExt) and (PointerExtensionLength > 0) then
  1553. begin
  1554. // The extension is always pixels visable from the center or edge of the
  1555. // cap, fix as needed. Makes nice for the user.
  1556. if PointerCapSettings.CapStyle <> csNone then
  1557. PointerExtensionLength := PointerExtensionLength + PointerCapRadius;
  1558. // compute end point of pointer if an extension
  1559. subEx := (-225 + Value) * Pi / 180;
  1560. x1 := Origin.CenterPoint.x - PointerExtensionLength * cos(subEx);
  1561. y1 := Origin.CenterPoint.y - PointerExtensionLength * sin(subEx);
  1562. end
  1563. else
  1564. begin
  1565. // no extension or extension length is 0, just draw to center
  1566. x1 := Origin.CenterPoint.x;
  1567. y1 := Origin.CenterPoint.y;
  1568. end;
  1569. // computer start point of pointer
  1570. subEx := (-45 + Value) * Pi / 180;
  1571. x := Origin.CenterPoint.x - PointerLength * cos(subEx);
  1572. y := Origin.CenterPoint.y - PointerLength * sin(subEx);
  1573. // finally draw it
  1574. FGaugeBitMap.DrawLineAntialias(x, y, x1, y1, Settings.Color, PointerWidth);
  1575. // Highlight in the case of line pointers is a CENTER line
  1576. if Settings.HighlightLine then
  1577. FGaugeBitMap.DrawLineAntialias(x, y, x1, y1, Settings.HighlightColor, HighlightThickness)
  1578. end
  1579. else
  1580. if Settings.Style = psTriangle then
  1581. begin
  1582. // Draw a Triangle style pointer
  1583. // Draw from center point out
  1584. subEx := (-45 + Value) * Pi / 180;
  1585. x := Origin.CenterPoint.x;
  1586. y := Origin.CenterPoint.y;
  1587. A := PointF(x, y);
  1588. // Calculate draw to point top
  1589. x1 := Origin.CenterPoint.x - PointerLength * cos(subEx);
  1590. y1 := Origin.CenterPoint.y - PointerLength * sin(subEx);
  1591. B := PointF(x1, y1);
  1592. // set line cap just in case
  1593. FMarkerBitmap.LineCap := pecRound; // Ensure Round Cap
  1594. // This is the vector that runs from outer to inner
  1595. U := B - A;
  1596. // build the perpendicular vector
  1597. // (clockwise in screen coordinates while the opposite would be counter clockwise)
  1598. V := PointF(-U.y, U.x);
  1599. // scale it to set the new segment length
  1600. vecLen := VectLen(V);
  1601. // catch odd case of zero len vector, do nothing
  1602. if vecLen = 0.0 then
  1603. Exit;
  1604. V := V * (PointerWidth / vecLen);
  1605. // draw a full triangle pointer
  1606. FGaugeBitMap.FillPolyAntialias([B, A + V, A - V], Settings.Color);
  1607. // Triangle mode is to draw a highlight AROUND it
  1608. if Settings.HighlightLine then
  1609. FGaugeBitMap.DrawPolygonAntialias([B, A + V, A - V], Settings.HighlightColor, Settings.HighlightThickness);
  1610. end
  1611. else
  1612. if Settings.Style = psArc then
  1613. begin
  1614. // drawn arc pointer, ensure not negative or crash
  1615. if Value < 0.0 then
  1616. Exit;
  1617. // hack to show a marker for start at 0, may not scale
  1618. // well but fixes the issue of nothing drawing at 0 values
  1619. if Value = 0.0 then
  1620. Value := -1;
  1621. BandRadius := PointerLength - PointerWidth / 2; // adjust for band thickness so end of pointer is top
  1622. // Start = 225 degree is 0 on gague scale (Not the angle), and -45 degree is 100 on scale
  1623. // 270, down (gauge angle 0)180 flat, increase moves towards 0 decrease towards 100
  1624. // 0 is flat line, right most end. Increase goes backwards towards 0, -45 is 100 percent on scale
  1625. startAngle := 225 * PI / 180; // start at 0 on the gauge
  1626. endAngle := startAngle - Value * PI / 180;
  1627. FGaugeBitMap.LineCap := pecFlat; // caps should be flat, rounded does not align to scales well
  1628. FGaugeBitMap.Arc(
  1629. Origin.CenterPoint.x, Origin.CenterPoint.y,
  1630. BandRadius, BandRadius,
  1631. startAngle, endAngle,
  1632. Settings.Color,
  1633. PointerWidth,
  1634. False,
  1635. BGRA(0,0,0,0) // last param is alpha, so no interior color, inner routings ONLY draw the arc, no fill
  1636. );
  1637. end;
  1638. end;
  1639. procedure TSGCustomSuperGauge.DrawPointerCap;
  1640. var
  1641. Origin: TSGOrigin;
  1642. sizeWH : integer;
  1643. pCapEdge : single;
  1644. PointerCapRadius, PointerCapEdgeWidth: single;
  1645. tx, ty: integer;
  1646. h: single;
  1647. d2: single;
  1648. v: TPointF;
  1649. p: PBGRAPixel;
  1650. Center: TPointF;
  1651. yb: integer;
  1652. xb: integer;
  1653. mask: TBGRABitmap;
  1654. Map: TBGRABitmap;
  1655. begin
  1656. // skip drawing if nothing changed
  1657. if not PointerCapSettings.Dirty then
  1658. Exit;
  1659. PointerCapSettings.Dirty := False;
  1660. // drawing is the size of the cap, not of the entire gauge!
  1661. PointerCapRadius := FResolvedSizes.Scale * FPointerCapSettings.Radius;
  1662. PointerCapEdgeWidth := FResolvedSizes.Scale * FPointerCapSettings.EdgeWidth;
  1663. sizeWH := Round((PointerCapRadius + PointerCapEdgeWidth) * 2 + 2);
  1664. Origin := Initializebitmap(FPointerCapBitmap, SizeWH, SizeWH);
  1665. pCapEdge := PointerCapRadius + PointerCapEdgeWidth / 2;
  1666. if PointerCapSettings.CapStyle = csFlat then
  1667. begin
  1668. // Draw the flat cap, but make sure size is similar to the shaded below or will be odd
  1669. FPointerCapBitmap.EllipseAntialias(Origin.CenterPoint.x, Origin.CenterPoint.y,
  1670. pCapEdge,
  1671. pCapEdge,
  1672. PointerCapSettings.EdgeColor,
  1673. PointerCapEdgeWidth,
  1674. PointerCapSettings.FillColor);
  1675. end
  1676. else
  1677. if PointerCapSettings.CapStyle = csShaded then
  1678. begin
  1679. // Regular shading
  1680. FPointerCapBitmap.FillEllipseLinearColorAntialias(origin.CenterPoint.x, origin.CenterPoint.y,
  1681. pCapEdge,
  1682. pCapEdge,
  1683. PointerCapSettings.FillColor,
  1684. PointerCapSettings.EdgeColor
  1685. );
  1686. // draw edge since the shading is backwards ending on fill color not Edge
  1687. FPointerCapBitmap.EllipseAntialias(origin.CenterPoint.x, origin.CenterPoint.y,
  1688. pCapEdge,
  1689. pCapEdge,
  1690. PointerCapSettings.EdgeColor,
  1691. PointerCapEdgeWidth, BGRA(0,0,0,0)
  1692. );
  1693. end
  1694. else
  1695. if PointerCapSettings.CapStyle = csPhong then
  1696. begin
  1697. // Phong it is
  1698. tx := Round(PointerCapRadius * 2); // keeps size consistent with flat cap
  1699. ty := tx;
  1700. if (tx = 0) or (ty = 0) then
  1701. Exit;
  1702. if PointerCapSettings.CapStyle = csPhong then
  1703. begin
  1704. //compute knob height map
  1705. Center := PointF((tx - 1) / 2, (ty - 1) / 2);
  1706. Map := TBGRABitmap.Create(tx, ty);
  1707. for yb := 0 to ty - 1 do
  1708. begin
  1709. p := map.ScanLine[yb];
  1710. for xb := 0 to tx - 1 do
  1711. begin
  1712. //compute vector between center and current pixel
  1713. v := PointF(xb, yb) - Center;
  1714. //scale down to unit circle (with 1 pixel margin for soft border)
  1715. v.x := v.x / (tx / 2 + 1);
  1716. v.y := v.y / (ty / 2 + 1);
  1717. //compute squared distance with scalar product
  1718. d2 := v {$if FPC_FULLVERSION < 30203}*{$ELSE}**{$ENDIF} v;
  1719. //interpolate as quadratic curve and apply power function
  1720. if d2 > 1 then
  1721. h := 0
  1722. else
  1723. h := power(1 - d2, PointerCapSettings.CurveExponent);
  1724. p^ := MapHeightToBGRA(h, 255);
  1725. Inc(p);
  1726. end;
  1727. end;
  1728. // mask image round with and antialiased border
  1729. mask := TBGRABitmap.Create(tx, ty, BGRABlack);
  1730. Mask.FillEllipseAntialias(Center.x, Center.y, tx / 2, ty / 2, BGRAWhite);
  1731. map.ApplyMask(mask);
  1732. Mask.Free;
  1733. // now draw
  1734. PointerCapSettings.FPhong.Draw(FPointerCapBitmap, Map, 30,
  1735. origin.CenterPoint.x - tx div 2, origin.CenterPoint.y - ty div 2,
  1736. PointerCapSettings.FillColor);
  1737. Map.Free;
  1738. // Draw a flat radius around the cap if set, must be alpha 0 or will not
  1739. // be an outline
  1740. if PointerCapEdgeWidth > 0 then
  1741. FPointerCapBitmap.EllipseAntialias(origin.CenterPoint.x, origin.CenterPoint.y,
  1742. pCapEdge,
  1743. pCapEdge,
  1744. PointerCapSettings.EdgeColor,
  1745. PointerCapEdgeWidth, BGRA(0,0,0,0));
  1746. end;
  1747. end;
  1748. end;
  1749. procedure TSGCustomSuperGauge.DrawLED;
  1750. var
  1751. Origin: TSGOrigin;
  1752. sizeWH : integer;
  1753. RangeLedSize: single;
  1754. mask: TBGRABitmap;
  1755. begin
  1756. // skip drawing if nothing changed or not drawn
  1757. if not FRangeLEDSettings.Dirty then
  1758. Exit;
  1759. FRangeLEDSettings.Dirty := False;
  1760. // compute the size needed NOT a full gauge bitmap
  1761. RangeLEDSize := FResolvedSizes.Scale * FRangeLEDSettings.Size;
  1762. sizeWH := Round(RangeLEDSize * 2 + 2); // square size at lease LED radius and a bit more
  1763. Origin := Initializebitmap(FLEDActiveBitmap, sizeWH, sizeWH);
  1764. Initializebitmap(FLEDInActiveBitmap, sizeWH, sizeWH);
  1765. // offset must be done later in the Paint proc to
  1766. // keep bitmap small so the center point is the centerpoint of the bitmap
  1767. // The caller MUST move to the correct offset
  1768. // draw both active and inactive so we never need to do either unless props changed
  1769. // need to find/get x, y to place the LED
  1770. if RangeLEDSettings.Shape = lshRound then
  1771. begin
  1772. if FRangeLEDSettings.Style = lsFlat then
  1773. begin
  1774. FLEDActiveBitmap.EllipseAntialias(
  1775. Origin.CenterPoint.x, Origin.CenterPoint.y,
  1776. RangeLEDSize, RangeLEDSize,
  1777. FRangeLEDSettings.BorderColor,
  1778. 1,
  1779. FRangeLEDSettings.ActiveColor);
  1780. end
  1781. else
  1782. if FRangeLEDSettings.Style = lsShaded then
  1783. begin
  1784. // draw shaded, could do better here but good for starts
  1785. FLEDActiveBitmap.FillEllipseLinearColorAntialias(
  1786. Origin.CenterPoint.x, Origin.CenterPoint.y,
  1787. RangeLEDSize, RangeLEDSize,
  1788. FRangeLEDSettings.InactiveColor,
  1789. FRangeLEDSettings.ActiveColor);
  1790. // draw border
  1791. FLEDActiveBitmap.EllipseAntialias(
  1792. Origin.CenterPoint.x, Origin.CenterPoint.y,
  1793. RangeLEDSize, RangeLEDSize,
  1794. FRangeLEDSettings.BorderColor,
  1795. 1,
  1796. BGRA(0,0,0,0)); // fill transparent
  1797. end;
  1798. // Simple flat round for inactive always
  1799. if RangeLedSettings.Style <> lsNone then
  1800. begin
  1801. FLEDInactiveBitmap.EllipseAntialias(
  1802. Origin.CenterPoint.x, Origin.CenterPoint.y,
  1803. RangeLEDSize, RangeLEDSize,
  1804. FRangeLEDSettings.BorderColor,
  1805. 1,
  1806. FRangeLEDSettings.InActiveColor);
  1807. end;
  1808. end // Round
  1809. else
  1810. if RangeLEDSettings.Shape = lshSquare then
  1811. begin
  1812. // draw a Square LED
  1813. if FRangeLEDSettings.Style = lsFlat then
  1814. begin
  1815. // Flat
  1816. FLEDActiveBitmap.FillRoundRectAntialias(
  1817. 0, 0,
  1818. FLEDActiveBitmap.Width,
  1819. FLEDActiveBitmap.Height,
  1820. Origin.Radius / 2, Origin.Radius / 2,
  1821. FRangeLEDSettings.ActiveColor);
  1822. // draw border for Flat
  1823. FLEDActiveBitmap.RoundRectAntialias(
  1824. 0,0,
  1825. FLEDActiveBitmap.Width - 1,
  1826. FLEDActiveBitmap.Height - 1,
  1827. Origin.Radius / 2,
  1828. Origin.Radius / 2,
  1829. FRangeLEDSettings.BorderColor,
  1830. 1);
  1831. end
  1832. else
  1833. if FRangeLEDSettings.Style = lsShaded then
  1834. begin
  1835. // draw shaded
  1836. FLEDActiveBitmap.GradientFill(
  1837. 0, 0,
  1838. FLEDActiveBitmap.Width,
  1839. FLEDActiveBitmap.Height,
  1840. FRangeLEDSettings.ActiveColor,
  1841. BGRA(0,0,0),
  1842. gtRadial,
  1843. PointF(FLEDActiveBitmap.Width / 2, FLEDActiveBitmap.Height / 2),
  1844. PointF(FLEDActiveBitmap.Width * 1.5,FLEDActiveBitmap.Height * 1.5),
  1845. dmSet);
  1846. mask := TBGRABitmap.Create(FLEDActiveBitmap.Width, FLEDActiveBitmap.Height, BGRABlack);
  1847. mask.FillRoundRectAntialias(
  1848. 0, 0,
  1849. FLEDActiveBitmap.Width,
  1850. FLEDActiveBitmap.Height,
  1851. Origin.Radius / 2,
  1852. Origin.Radius / 2,
  1853. BGRAWhite);
  1854. FLEDActiveBitmap.ApplyMask(mask);
  1855. mask.Free;
  1856. // draw border for shaded
  1857. FLEDActiveBitmap.RoundRectAntialias(
  1858. 0, 0,
  1859. FLEDActiveBitmap.Width - 1,
  1860. FLEDActiveBitmap.Height - 1,
  1861. Origin.Radius / 2,
  1862. Origin.Radius / 2,
  1863. FRangeLEDSettings.BorderColor,
  1864. 1);
  1865. end;
  1866. // Simple flat square for inactive always
  1867. if RangeLEDSettings.Style <> lsNone then
  1868. begin
  1869. // Need to draw the filled
  1870. FLEDInactiveBitmap.FillRoundRectAntialias(
  1871. 0, 0,
  1872. FLEDActiveBitmap.Width,
  1873. FLEDActiveBitmap.Height,
  1874. Origin.Radius / 2,
  1875. Origin.Radius / 2,
  1876. FRangeLEDSettings.InactiveColor);
  1877. // Now the border
  1878. FLEDInactiveBitmap.RoundRectAntialias(
  1879. 0, 0,
  1880. FLEDActiveBitmap.Width - 1,
  1881. FLEDActiveBitmap.Height - 1,
  1882. Origin.Radius / 2,
  1883. Origin.Radius / 2,
  1884. FRangeLEDSettings.BorderColor,
  1885. 1);
  1886. end;
  1887. end // square
  1888. else
  1889. if RangeLEDSettings.Shape = lshTriangle then
  1890. begin
  1891. // draw a triangle and border
  1892. if FRangeLEDSettings.Style = lsFlat then
  1893. begin
  1894. FLEDActiveBitmap.DrawPolyLineAntialias(
  1895. [ PointF(FLEDActiveBitmap.Width / 2, 1),
  1896. PointF(FLEDActiveBitmap.Width - 1, FLEDActiveBitmap.Height - 1),
  1897. PointF(1, FLEDActiveBitmap.Height - 1),
  1898. PointF(FLEDActiveBitmap.Width / 2, 1) // close it for border
  1899. ],
  1900. FRangeLEDSettings.BorderColor,
  1901. 1,
  1902. FRangeLEDSettings.ActiveColor);
  1903. end
  1904. else
  1905. if FRangeLEDSettings.Style = lsShaded then
  1906. begin
  1907. // draw shaded
  1908. FLEDActiveBitmap.FillPolyLinearColor(
  1909. [ PointF(FLEDActiveBitmap.Width / 2, 1),
  1910. PointF(FLEDActiveBitmap.Width - 1, FLEDActiveBitmap.Height - 1),
  1911. PointF(1, FLEDActiveBitmap.Height - 1)],
  1912. [FRangeLEDSettings.InactiveColor,
  1913. FRangeLEDSettings.ActiveColor,
  1914. FRangeLEDSettings.ActiveColor]);
  1915. // draw border
  1916. FLEDActiveBitmap.DrawPolyLineAntialias(
  1917. [ PointF(FLEDActiveBitmap.Width / 2, 1),
  1918. PointF(FLEDActiveBitmap.Width - 1, FLEDActiveBitmap.Height - 1),
  1919. PointF(1, FLEDActiveBitmap.Height - 1),
  1920. PointF(FLEDActiveBitmap.Width / 2, 1) // close it for border
  1921. ],
  1922. FRangeLEDSettings.BorderColor,
  1923. 1,
  1924. BGRA(0,0,0,0));
  1925. end;
  1926. if RangeLEDSettings.Style <> lsNone then
  1927. begin
  1928. FLEDInactiveBitmap.DrawPolyLineAntialias(
  1929. [ PointF(FLEDActiveBitmap.Width / 2, 1),
  1930. PointF(FLEDActiveBitmap.Width - 1, FLEDActiveBitmap.Height - 1),
  1931. PointF(1, FLEDActiveBitmap.Height - 1),
  1932. PointF(FLEDActiveBitmap.Width / 2, 1) // close it for border
  1933. ],
  1934. FRangeLEDSettings.BorderColor,
  1935. 1,
  1936. FRangeLEDSettings.InactiveColor);
  1937. end;
  1938. end // triangle
  1939. else
  1940. if RangeLEDSettings.Shape = lshDownTriangle then
  1941. begin
  1942. // draw a downward pointing triangle and border
  1943. if FRangeLEDSettings.Style = lsFlat then
  1944. begin
  1945. FLEDActiveBitmap.DrawPolyLineAntialias(
  1946. [ PointF(1,1),
  1947. PointF(FLEDActiveBitmap.Width / 2, FLEDActiveBitmap.Height - 1),
  1948. PointF(FLEDActiveBitmap.Width - 1, 1),
  1949. PointF(1,1)
  1950. ],
  1951. FRangeLEDSettings.BorderColor,
  1952. 1,
  1953. FRangeLEDSettings.ActiveColor);
  1954. end
  1955. else
  1956. if FRangeLEDSettings.Style = lsShaded then
  1957. begin
  1958. // draw shaded
  1959. FLEDActiveBitmap.FillPolyLinearColor(
  1960. [ PointF(1,1),
  1961. PointF(FLEDActiveBitmap.Width / 2, FLEDActiveBitmap.Height - 1),
  1962. PointF(FLEDActiveBitmap.Width - 1, 1)
  1963. ],
  1964. [FRangeLEDSettings.InactiveColor,
  1965. FRangeLEDSettings.ActiveColor,
  1966. FRangeLEDSettings.ActiveColor]);
  1967. // draw border
  1968. FLEDActiveBitmap.DrawPolyLineAntialias(
  1969. [ PointF(1,1),
  1970. PointF(FLEDActiveBitmap.Width / 2, FLEDActiveBitmap.Height - 1),
  1971. PointF(FLEDActiveBitmap.Width - 1, 1),
  1972. PointF(1,1)
  1973. ],
  1974. FRangeLEDSettings.BorderColor,
  1975. 1,
  1976. BGRA(0,0,0,0));
  1977. end;
  1978. // Draw Inactive DownTri
  1979. if RangeLEDSettings.Style <> lsNone then
  1980. begin
  1981. FLEDInactiveBitmap.DrawPolyLineAntialias(
  1982. [ PointF(1,1),
  1983. PointF(FLEDActiveBitmap.Width / 2, FLEDActiveBitmap.Height - 1),
  1984. PointF(FLEDActiveBitmap.Width - 1, 1),
  1985. PointF(1,1)
  1986. ],
  1987. FRangeLEDSettings.BorderColor,
  1988. 1,
  1989. FRangeLEDSettings.InactiveColor);
  1990. end;
  1991. end;
  1992. end;
  1993. procedure TSGCustomSuperGauge.DrawMarkers;
  1994. var
  1995. i: integer;
  1996. begin
  1997. if not IsAnyMarkerDirty then
  1998. exit;
  1999. // draws the fill sized bitmap to draw ALL markers onto one bitmap
  2000. Initializebitmap(FMarkerBitmap, Width, Height); // clear it before we draw anything
  2001. // Draws low to high, so if overlapping, last will be visible
  2002. for i := low(FMarkersSettings) to high(FMArkersSettings) do
  2003. begin
  2004. FMarkersSettings[i].Dirty := True; // need to dirty them all
  2005. DrawMarker(FMarkerBitmap, FMarkersSettings[i]); // will clear any dirty
  2006. end;
  2007. end;
  2008. procedure TSGCustomSuperGauge.DrawMarker(MarkerBitmap: TBGRABitmap; const MarkerSettings: TSGMarkerSettings);
  2009. var
  2010. markerValue: single;
  2011. x1, y1, x2, y2: single;
  2012. cenX, cenY: single;
  2013. vecLen: single;
  2014. subx, cosSubx, sinSubX: single;
  2015. A, B, U, V: TPointF;
  2016. begin
  2017. // skip drawing if nothing changed or not drawn
  2018. if not MarkerSettings.Dirty then
  2019. Exit;
  2020. MarkerSettings.Dirty := False;
  2021. if not MarkerSettings.Enabled then
  2022. Exit;
  2023. // Center of bitmap
  2024. cenX := MarkerBitmap.Width / 2;
  2025. cenY := MarkerBitmap.Height / 2;
  2026. // Need to translate the marker value. Since the value coming in is the USER
  2027. // value in terms of Min/Max settings we need to also translate the markers
  2028. // to match
  2029. markerValue := UserToGauge(MarkerSettings.Value, FMinValue, FMaxValue);
  2030. // We need to do a quick range check here for anything set like the pointer
  2031. // but need to do it here since we can catch all markers being drawn. Range
  2032. // check will force min or max gauge value if out of range respectivly
  2033. if markerValue < INTERNAL_GAUGE_MIN_VALUE then
  2034. markerValue := INTERNAL_GAUGE_MIN_VALUE
  2035. else
  2036. if markerValue > INTERNAL_GAUGE_MAX_VALUE then
  2037. markerValue := INTERNAL_GAUGE_MAX_VALUE;
  2038. subx := (-45 + markerValue) * Pi / 180; // just like the pointer
  2039. cosSubX := cos(subX);
  2040. sinSubX := sin(subX);
  2041. x1 := cenX - (MarkerSettings.Radius * FResolvedSizes.scale) * cosSubX;
  2042. y1 := cenY - (MarkerSettings.Radius * FResolvedSizes.scale) * sinSubX;
  2043. A := PointF(x1,y1);
  2044. // Calculate draw to point top
  2045. x2 := cenX - (MarkerSettings.Radius * FResolvedSizes.scale - MarkerSettings.Height * FResolvedSizes.scale) * cosSubX;
  2046. y2 := cenY - (MarkerSettings.Radius * FResolvedSizes.scale - MarkerSettings.Height * FResolvedSizes.scale) * sinSubX;
  2047. B := PointF(X2, y2);
  2048. // set line cap just in case
  2049. FMarkerBitmap.LineCap := pecRound; // Ensure Round Cap
  2050. // This is the vector that runs from outer to inner
  2051. U := B - A;
  2052. // build the perpendicular vector
  2053. // (clockwise in screen coordinates while the opposite would be counter clockwise)
  2054. V := PointF(-U.y, U.x);
  2055. vecLen := VectLen(V);
  2056. // catch odd case of zero len vector, do nothing
  2057. if vecLen = 0.0 then
  2058. Exit;
  2059. // Set length
  2060. V := V * (MarkerSettings.Width / vecLen);
  2061. case MarkerSettings.Style of
  2062. msCenter: // triangle centered on the value
  2063. begin
  2064. MarkerBitmap.FillPolyAntialias([B, A + V, A - V], MarkerSettings.Color);
  2065. end;
  2066. msLeft: // triangle left side only (if looking at it at half way on the gauge)
  2067. begin
  2068. MarkerBitmap.FillPolyAntialias([B, A + V, A], MarkerSettings.Color);
  2069. end;
  2070. msRight:
  2071. begin // triangle right side only
  2072. MarkerBitmap.FillPolyAntialias([B, A, A - V], MarkerSettings.Color);
  2073. end;
  2074. end;
  2075. end;
  2076. function TSGCustomSuperGauge.CheckRangeLED(AValue: single): boolean;
  2077. begin
  2078. // Manually setting the .Active prop will ONLY
  2079. // work if rcNone is set, otherwise the range checks will prevail as the
  2080. // way the Active state is set and overide the manual setting.
  2081. //
  2082. // Current List
  2083. // TSGRangeCheckType = (rcNone, rcBetween, rcBothInclusive, rcStartInclusive,
  2084. // rcEndInclusive, rcBothBetweenOutside,
  2085. // rcBothInclusiveOutside, rcGreaterStart, RangeEndValue,
  2086. // rcGreaterStartInclusive, rcLessEndInclusive);
  2087. //
  2088. // NOTE - rcGreaterStart, RangeEndValue, rcGreaterStartInclusive, rcLessEndInclusive
  2089. // ignore RangeEnd and RangeStart as indicated
  2090. if FRangeLEDSettings.RangeType = rcNone then
  2091. begin
  2092. Result := FRangeLEDSettings.Active; // need to always return the current state here, Will never trigger RangeLED Events
  2093. end
  2094. else
  2095. if FRangeLEDSettings.Rangetype = rcGaugeOverload then // Special case to ONLY look at the gauge state, ignores the start/end
  2096. Result := FOverloadTriggeredState // Will NOT trigger any events for RangeLED, this is handled elsewhere
  2097. else
  2098. if FRangeLEDSettings.RangeType = rcGreaterStart then
  2099. Result := (AValue > FRangeLEDSettings.RangeStartValue) // ignore range end, common case
  2100. else
  2101. if FRangeLEDSettings.RangeType = rcGreaterStartInclusive then
  2102. Result := (AValue >= FRangeLEDSettings.RangeStartValue) // ignore range end, common case
  2103. else
  2104. if FRangeLEDSettings.RangeType = rcLessEnd then
  2105. Result := (AValue < FRangeLEDSettings.RangeEndValue) // ignor range start
  2106. else
  2107. if FRangeLEDSettings.RangeType = rcLessEndInclusive then
  2108. Result := (AValue <= FRangeLEDSettings.RangeEndValue) // ignor range start
  2109. else
  2110. if FRangeLEDSettings.RangeType = rcBetween then
  2111. Result := (AValue > FRangeLEDSettings.RangeStartValue) and (AValue < FRangeLEDSettings.RangeEndValue)
  2112. else
  2113. if FRangeLEDSettings.Rangetype = rcBothInclusive then
  2114. Result := (AValue >= FRangeLEDSettings.RangeStartValue) and (AValue <= FRangeLEDSettings.RangeEndValue)
  2115. else
  2116. if FRangeLEDSettings.Rangetype = rcBothBetweenOutside then
  2117. Result := (AValue < FRangeLEDSettings.RangeStartValue) or (AValue > FRangeLEDSettings.RangeEndValue)
  2118. else
  2119. if FRangeLEDSettings.Rangetype = rcStartInclusive then
  2120. Result := (AValue >= FRangeLEDSettings.RangeStartValue) and (AValue < FRangeLEDSettings.RangeEndValue)
  2121. else
  2122. if FRangeLEDSettings.Rangetype = rcEndInclusive then
  2123. Result := (AValue > FRangeLEDSettings.RangeStartValue) and (AValue <= FRangeLEDSettings.RangeEndValue)
  2124. else
  2125. if FRangeLEDSettings.Rangetype = rcBothInclusiveOutside then
  2126. Result := (AValue <= FRangeLEDSettings.RangeStartValue) or (AValue >= FRangeLEDSettings.RangeEndValue);
  2127. // Now set the flag we have changed so others SetValue() can update as needed
  2128. FRangeLEDStateChanged := FRangeLEDStateChanged or (Result <> FRangeLEDSettings.Active);
  2129. // Try the callbacks now, should hit one or the other depending on Active state
  2130. // if they are assigned! Rember some will NEVER cause a call back, rcNone and
  2131. // rcGaugeOverload
  2132. if FRangeLEDStateChanged and (FRangeLEDSettings.RangeType <> rcNone)
  2133. and (FRangeLEDSettings.RangeType <> rcGaugeOverload) then
  2134. begin
  2135. if Assigned(FRangeLedActive) and Result then
  2136. FRangeLEDActive(Self, AValue)
  2137. else
  2138. if Assigned(FRangeLedActive) and (not Result) then
  2139. FRangeLEDInactive(Self, AValue);
  2140. FRangeLEDStateChanged := False; // clear the state
  2141. end;
  2142. FRangeLEDSettings.ActiveNoDoChange := Result;
  2143. end;
  2144. end.