{********************************************************************}
{                                                                    }
{ written by TMS Software                                            }
{            copyright © 2013                                        }
{            Email : info@tmssoftware.com                            }
{            Web : http://www.tmssoftware.com                        }
{                                                                    }
{ The source code is given as is. The author is not responsible      }
{ for any possible damage done due to the use of this code.          }
{ The complete source code remains property of the author and may    }
{ not be distributed, published, given or sold in any form as such.  }
{ No parts of the source code can be included in any other component }
{ or application without written authorization of the author.        }
{********************************************************************}

unit FMX.TMSGauge;

interface

{$I TMSDEFS.INC}

uses
  FMX.Types, FMX.Objects, FMX.Effects, Classes, DateUtils,
  Types, SysUtils, Math, UITypes, FMX.TMSBaseControl, UIConsts, FMX.Styles,
  FMX.Controls
  {$IFDEF DELPHIXE5_LVL}
  , FMX.Graphics
  {$ENDIF}
  ;

const
  MAJ_VER = 1; // Major version nr.
  MIN_VER = 5; // Minor version nr.
  REL_VER = 0; // Release nr.
  BLD_VER = 2; // Build nr.

  // version history
  // v1.0.0.0 : first release
  // v1.0.4.2 : Reorganized packages
  // v1.0.5.0 : New : support for update 4
  // v1.0.5.1 : Improved : TMSIOS define
  // v1.0.5.2 : New : support for update 4 hotfix 1
  // v1.0.5.3 : Fixed : Issue with OnClick / OnDblClick
  // v1.5.0.0 : New : support for XE4
  // v1.5.0.1 : Fixed : Issue with calculation setpoints, needles and sections with minimumvalue > 0
  // v1.5.0.2 : Fixed : Issue with measure text calculation in XE6

type
  TTMSFMXApplyDivisionLineStyle = procedure(Sender: TObject; AOriginalLine, ALine: TLine; DivisionUnit: Integer; Value: Single) of object;
  TTMSFMXApplySubDivisionLineStyle = procedure(Sender: TObject; AOriginalLine, ALine: TLine; DivisionUnit, SubDivisionUnit: Integer; Value: Single) of object;
  TTMSFMXApplyDivisionTextStyle = procedure(Sender: TObject; AOriginalText, AText: TText; DivisionUnit: Integer; Value: Single) of object;
  TTMSFMXApplySubDivisionTextStyle = procedure(Sender: TObject; AOriginalText, AText: TText; DivisionUnit, SubDivisionUnit: Integer; Value: Single) of object;
  TTMSFMXValueChanged = procedure(Sender: TObject; Value: Single) of object;

  TSetPointShape = (spsTriangle, spsRectangle, spsDiamond, spsEllipse, spsNone);

  TGaugeValue = record
    State: Boolean;
    Value: Single;
  end;

  [ComponentPlatformsAttribute(TMSPlatforms)]
  TTMSFMXSetPointShape = class(TTMSFMXBaseShape)
  private
    FKind: TSetPointShape;
    procedure SetKind(const Value: TSetPointShape);
  public
    procedure Painting; override;
    constructor Create(AOwner: TComponent); override;
  published
    property Kind: TSetPointShape read FKind write SetKind default spsTriangle;
    property Fill;
    property Stroke;
    property StrokeThickness;
    property StrokeCap;
    property StrokeDash;
    property StrokeJoin;
  end;

  TTMSFMXNeedleShapeKind = (nskNeedle, nskRectangle, nskEllipse, nskTriangle, nskDiamond, nskNone);

  [ComponentPlatformsAttribute(TMSPlatforms)]
  TTMSFMXNeedleShape = class(TTMSFMXBaseShape)
  private
    FKind: TTMSFMXNeedleShapeKind;
    procedure SetKind(const Value: TTMSFMXNeedleShapeKind);
  public
    procedure Painting; override;
    constructor Create(AOwner: TComponent); override;
  published
    property Kind: TTMSFMXNeedleShapeKind read FKind write SetKind default nskNeedle;
    property Fill;
    property Stroke;
    property StrokeThickness;
    property StrokeCap;
    property StrokeDash;
    property StrokeJoin;
  end;

  TSectionShapeKind = (sskEllipse, sskRectangle);

  [ComponentPlatformsAttribute(TMSPlatforms)]
  TTMSFMXSectionShape = class(TTMSFMXBaseShape)
  private
    FStopAngle: Integer;
    FStartAngle: Integer;
    FInnerMargin: Integer;
    FKind: TSectionShapeKind;
    FOuterMargin: Integer;
    procedure SetStartAngle(const Value: Integer);
    procedure SetStopAngle(const Value: Integer);
    procedure SetInnerMargin(const Value: Integer);
    procedure SetKind(const Value: TSectionShapeKind);
    procedure SetOuterMargin(const Value: Integer);
  public
    procedure Painting; override;
    constructor Create(AOwner: TComponent); override;
  published
    property Fill;
    property Stroke;
    property StrokeThickness;
    property StrokeCap;
    property StrokeDash;
    property StrokeJoin;
    property StartAngle: Integer read FStartAngle write SetStartAngle default 0;
    property StopAngle: Integer read FStopAngle write SetStopAngle default 360;
    property InnerMargin: Integer read FInnerMargin write SetInnerMargin default 20;
    property OuterMargin: Integer read FOuterMargin write SetOuterMargin default 0;
    property Kind: TSectionShapeKind read FKind write SetKind default sskEllipse;
  end;

  [ComponentPlatformsAttribute(TMSPlatforms)]
  TTMSFMXPieShape = class(TTMSFMXBaseShape)
  private
    FStopAngle: Integer;
    FStartAngle: Integer;
    procedure SetStartAngle(const Value: Integer);
    procedure SetStopAngle(const Value: Integer);
  public
    procedure Painting; override;
    constructor Create(AOwner: TComponent); override;
  published
    property Fill;
    property Stroke;
    property StrokeThickness;
    property StrokeCap;
    property StrokeDash;
    property StrokeJoin;
    property StartAngle: Integer read FStartAngle write SetStartAngle default 0;
    property StopAngle: Integer read FStopAngle write SetStopAngle default 360;
  end;

  TTMSFMXGauge = class;

  TTMSFMXSection = class(TCollectionItem)
  private
    FOwner: TTMSFMXGauge;
    FStartValue: Single;
    FEndValue: Single;
    procedure SetEndValue(const Value: Single);
    procedure SetStartValue(const Value: Single);
  protected
    procedure InitValues; virtual;
  public
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
    procedure UpdateSection;
    function Shape: TTMSFMXSectionShape;
  published
    property StartValue: Single read FStartValue write SetStartValue;
    property EndValue: Single read FEndValue write SetEndValue;
  end;

  TTMSFMXGaugeSection = class(TTMSFMXSection)
  private
    FInnerMargin: Integer;
    FOuterMargin: Integer;
    procedure SetInnerMargin(const Value: Integer);
    procedure SetOuterMargin(const Value: Integer);
  protected
    procedure InitValues; override;
  public
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
  published
    property InnerMargin: Integer read FInnerMargin write SetInnerMargin default 20;
    property OuterMargin: Integer read FOuterMargin write SetOuterMargin default 0;
  end;

  TTMSFMXSections = class(TTMSFMXBaseCollection)
  private
    FOwner: TTMSFMXGauge;
    function GetItem(Index: Integer): TTMSFMXSection;
    procedure SetItem(Index: Integer; const Value: TTMSFMXSection);
  protected
    function GetOwner: TPersistent; override;
  public
    constructor Create(AClass: TCollectionItemClass; AOwner: TTMSFMXGauge);
    function Add: TTMSFMXSection;
    function Insert(Index: Integer): TTMSFMXSection;
    property Items[Index: Integer]: TTMSFMXSection read GetItem write SetItem; default;
  end;

  TTMSFMXSetPoint = class(TCollectionItem)
  private
    FKind: TSetPointShape;
    FdoAnim: Boolean;
    FCurrentValue, FCurrentValueTo: Single;
    FOwner: TTMSFMXGauge;
    procedure SetValue(const Value: Single);
    function GetValue: Single;
    function GetKind: TSetPointShape;
    procedure SetKind(const Value: TSetPointShape);
  public
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
    procedure UpdateSetPoint;
    function Shape: TTMSFMXSetPointShape;
  published
    property Value: Single read GetValue write SetValue;
    property Kind: TSetPointShape read GetKind write SetKind default spsTriangle;
  end;

  TTMSFMXSetPoints = class(TTMSFMXBaseCollection)
  private
    FOwner: TTMSFMXGauge;
    FOnChange: TNotifyEvent;
    function GetItem(Index: Integer): TTMSFMXSetPoint;
    procedure SetItem(Index: Integer; const Value: TTMSFMXSetPoint);
  protected
    function GetOwner: TPersistent; override;
  public
    constructor Create(AOwner: TTMSFMXGauge);
    function Add: TTMSFMXSetPoint;
    function Insert(Index: Integer): TTMSFMXSetPoint;
    property Items[Index: Integer]: TTMSFMXSetPoint read GetItem write SetItem; default;
  published
    property OnChange: TNotifyEvent read FOnChange write FOnChange;
  end;

  TTMSFMXExtraNeedle = class(TCollectionItem)
  private
    FdoAnim: Boolean;
    FCurrentValue, FCurrentValueTo: Single;
    FOwner: TTMSFMXGauge;
    procedure SetValue(const Value: Single);
    function GetValue: Single;
  public
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
    procedure UpdateExtraNeedle;
    function Shape: TTMSFMXNeedleShape;
  published
    property Value: Single read GetValue write SetValue;
  end;

  TTMSFMXExtraNeedles = class(TTMSFMXBaseCollection)
  private
    FOwner: TTMSFMXGauge;
    FOnChange: TNotifyEvent;
    function GetItem(Index: Integer): TTMSFMXExtraNeedle;
    procedure SetItem(Index: Integer; const Value: TTMSFMXExtraNeedle);
  protected
    function GetOwner: TPersistent; override;
  public
    constructor Create(AOwner: TTMSFMXGauge);
    function Add: TTMSFMXExtraNeedle;
    function Insert(Index: Integer): TTMSFMXExtraNeedle;
    property Items[Index: Integer]: TTMSFMXExtraNeedle read GetItem write SetItem; default;
  published
    property OnChange: TNotifyEvent read FOnChange write FOnChange;
  end;

  TTMSFMXGaugeSetPointClickEvent = procedure(Sender: TObject; SetPoint: TTMSFMXSetPoint) of object;

  TTMSFMXGaugeNeedleClickEvent = procedure(Sender: TObject; Needle: TTMSFMXExtraNeedle) of object;

  TTMSFMXGaugeSectionClickEvent = procedure(Sender: TObject; Section: TTMSFMXSection) of object;

  TTMSFMXValueClickEvent = procedure(Sender: TObject; Value: Single) of object;

  [ComponentPlatformsAttribute(TMSPlatforms)]
  TTMSFMXGauge = class(TTMSFMXBaseControl)
  private
    FAnimTimer, FExtraTimer: TTimer;
    FCurrentValue, FCurrentValueTo: Single;
    doanimation, doExtraAnimation: Boolean;
    FSubDivisions: Integer;
    FDivisions: Integer;
    FMaximumValue: Single;
    FMinimumValue: Single;
    FShowSubDivisions: Boolean;
    FShowSubDivisionText: Boolean;
    FShowDivisions: Boolean;
    FShowDivisionText: Boolean;
    FDivisionFormat: String;
    FSubDivisionFormat: String;
    FDisplayText: String;
    FSections: TTMSFMXSections;
    FAnimation: Boolean;
    FOnApplySubDivisionLineStyle: TTMSFMXApplySubDivisionLineStyle;
    FOnApplySubDivisionTextStyle: TTMSFMXApplySubDivisionTextStyle;
    FOnApplyDivisionLineStyle: TTMSFMXApplyDivisionLineStyle;
    FOnApplyDivisionTextStyle: TTMSFMXApplyDivisionTextStyle;
    FOnValueChanged: TTMSFMXValueChanged;
    FOnValueChanging: TTMSFMXValueChanged;
    FExtraNeedles: TTMSFMXExtraNeedles;
    FSetPoints: TTMSFMXSetPoints;
    FAnimationFactor: Single;
    FOnSetPointClick: TTMSFMXGaugeSetPointClickEvent;
    FOnValueClick: TTMSFMXValueClickEvent;
    FOnSectionClick: TTMSFMXGaugeSectionClickEvent;
    FOnExtraNeedleClick: TTMSFMXGaugeNeedleClickEvent;
    procedure SetDivisions(const Value: Integer);
    procedure SetSubDivisions(const Value: Integer);
    function GetValue: Single;
    procedure SetMaxValue(const Value: Single);
    procedure SetMinValue(const Value: Single);
    procedure SetValue(const Value: Single); virtual;
    procedure SetShowDivisions(const Value: Boolean);
    procedure SetShowDivisionText(const Value: Boolean);
    procedure SetShowSubDivisions(const Value: Boolean);
    procedure SetShowSubDivisionText(const Value: Boolean);
    procedure SetDivisionFormat(const Value: String);
    procedure SetSubDivisionFormat(const Value: String);
    procedure SetDisplayText(const Value: String);
    procedure SetSections(const Value: TTMSFMXSections);
    procedure SetAnimation(const Value: Boolean);
    procedure SetExtraNeedles(const Value: TTMSFMXExtraNeedles);
    procedure SetSetPoints(const Value: TTMSFMXSetPoints);
    function GetVersion: String;
    function GetVersionNr: Integer;
  protected
    function GetInstance: NativeUInt; override;
    procedure AssignEvents; virtual;
    function XYToValue(X, Y: Single): TGaugeValue; virtual;
    function FindStartAngle: Single; virtual;
    function FindStopAngle: Single; virtual;
    procedure InnerElementMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
    procedure ValueClick(Sender: TObject); virtual;
    function GetSectionForShape(AShape: TTMSFMXSectionShape): TTMSFMXSection;
    function GetExtraNeedleForShape(AShape: TTMSFMXNeedleShape): TTMSFMXExtraNeedle;
    function GetSetPointForShape(AShape: TTMSFMXSetPointShape): TTMSFMXSetPoint;
    procedure SetPointClick(Sender: TObject);
    procedure SectionClick(Sender: TObject);
    procedure SectionMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
    procedure ExtraNeedleClick(Sender: TObject);
    procedure AnimateUpdateNeedle; virtual;
    procedure AnimateUpdateDivisions; virtual;
    procedure AnimateUpdateValues; virtual;
    property AnimationFactor: Single read FAnimationFactor write FAnimationFactor;
    property OnApplyDivisionLineStyle: TTMSFMXApplyDivisionLineStyle read FOnApplyDivisionLineStyle write FOnApplyDivisionLineStyle;
    property OnApplySubDivisionLineStyle: TTMSFMXApplySubDivisionLineStyle read FOnApplySubDivisionLineStyle write FOnApplySubDivisionLineStyle;
    property OnApplyDivisionTextStyle: TTMSFMXApplyDivisionTextStyle read FOnApplyDivisionTextStyle write FOnApplyDivisionTextStyle;
    property OnApplySubDivisionTextStyle: TTMSFMXApplySubDivisionTextStyle read FOnApplySubDivisionTextStyle write FOnApplySubDivisionTextStyle;

    property OnValueChanged: TTMSFMXValueChanged read FOnValueChanged write FOnValueChanged;
    property OnValueChanging: TTMSFMXValueChanged read FOnValueChanging write FOnValueChanging;

    property Animation: Boolean read FAnimation write SetAnimation default True;

    property Divisions: Integer read FDivisions write SetDivisions default 10;
    property SubDivisions: Integer read FSubDivisions write SetSubDivisions default 2;
    property Value: Single read GetValue write SetValue;
    property ShowSubDivisions: Boolean read FShowSubDivisions write SetShowSubDivisions default True;
    property ShowDivisions: Boolean read FShowDivisions write SetShowDivisions default True;
    property ShowSubDivisionText: Boolean read FShowSubDivisionText write SetShowSubDivisionText default False;
    property ShowDivisionText: Boolean read FShowDivisionText write SetShowDivisionText default True;
    property DivisionFormat: String read FDivisionFormat write SetDivisionFormat;
    property SubDivisionFormat: String read FSubDivisionFormat write SetSubDivisionFormat;

    procedure DoApplyDivisionLineStyle(Sender: TObject; AOriginalLine, ALine: TLine; DivisionUnit: Integer; Value: Single); virtual;
    procedure DoApplySubDivisionLineStyle(Sender: TObject; AOriginalLine, ALine: TLine; DivisionUnit, SubDivisionUnit: Integer; Value: Single); virtual;
    procedure DoApplyDivisionTextStyle(Sender: TObject; AOriginalText, AText: TText; DivisionUnit: Integer; Value: Single); virtual;
    procedure DoApplySubDivisionTextStyle(Sender: TObject; AOriginalText, AText: TText; DivisionUnit, SubDivisionUnit: Integer; Value: Single); virtual;

    function GetStyleObject: TFMXObject; override;
    procedure AnimateGauge(Sender: TObject);
    procedure AnimateExtraGauge(Sender: TObject);
    function DoesDivisionLineExist(LineName: String): TLine;
    function DoesDivisionTextExist(TextName: String): TText;
    function DoesSectionExist(AControl: TFMXObject; SectionName: String): TTMSFMXSectionShape;
    function DoesSetPointExist(AControl: TFMXObject; SectionName: String): TTMSFMXSetPointShape;
    function DoesExtraNeedleExist(AControl: TFMXObject; ExtraNeedleName: String): TTMSFMXNeedleShape;
    procedure AlignNeedle;
    procedure CleanUpLines; virtual;
    procedure CleanUpValues; virtual;
    procedure UpdateNeedle; virtual;
    procedure UpdateLayout; virtual;
    procedure UpdateDivisions; virtual;
    procedure UpdateValues; virtual;
    procedure ApplySubLineDivision(CenterX, CenterY: Single; iDiv, iSubDiv: Integer; AValue: Single; lDiv, LineDiv: TLine); virtual;
    procedure ApplySubTextDivision(CenterX, CenterY: Single; iDiv, iSubDiv: Integer; AValue: Single; lDiv: TText; LineDiv: TLine); virtual;
    procedure ApplyLineDivision(CenterX, CenterY: Single; iDiv: Integer; AValue: Single; lDiv, LineDiv: TLine); virtual;
    procedure ApplyTextDivision(CenterX, CenterY: Single; iDiv: Integer; AValue: Single; lDiv: TText; LineDiv: TLine); virtual;
    procedure UpdateDisplayText;
    procedure UpdateSections(AControl: TFMXObject);
    procedure UpdateSetPoints(AControl: TFMXObject);
    function GetNeedlePlaceHolder(AControl: TFMXObject): TFMXObject; virtual;
    procedure UpdateExtraNeedles(AControl: TFMXObject);
    procedure ApplySetPoint(InnerElement: TControl; ASetPoint: TTMSFMXSetPoint; ASetPointShape: TTMSFMXSetPointShape; LineDiv: TLine); virtual;
    procedure ApplySection(InnerElement: TControl; ASection: TTMSFMXSection; ASectionShape: TTMSFMXSectionShape; LineDiv: TLine); virtual;
    procedure ApplyExtraNeedle(InnerElement: TControl; AExtraNeedle: TTMSFMXExtraNeedle; ANeedleShape: TTMSFMXNeedleShape; LineDiv: TLine); virtual;
    procedure CleanUpSections(AControl: TFMXObject);
    procedure CleanUpSetPoints(AControl: TFMXObject);
    procedure CleanUpExtraNeedles(AControl: TFMXObject);
    property MinimumValue: Single read FMinimumValue write SetMinValue;
    property MaximumValue: Single read FMaximumValue write SetMaxValue;
    property Sections: TTMSFMXSections read FSections write SetSections;
    property SetPoints: TTMSFMXSetPoints read FSetPoints write SetSetPoints;
    property ExtraNeedles: TTMSFMXExtraNeedles read FExtraNeedles write SetExtraNeedles;
    property DisplayText: String read FDisplayText write SetDisplayText;
    property OnSetPointClick: TTMSFMXGaugeSetPointClickEvent read FOnSetPointClick write FOnSetPointClick;
    property OnExtraNeedleClick: TTMSFMXGaugeNeedleClickEvent read FOnExtraNeedleClick write FOnExtraNeedleClick;
    property OnSectionClick: TTMSFMXGaugeSectionClickEvent read FOnSectionClick write FOnSectionClick;
    property OnValueClick: TTMSFMXValueClickEvent read FOnValueClick write FOnValueClick;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Resize; override;
    procedure Update; override;
    function GetDivision: TLine;
    function GetSubDivision: TLine;
    function GetDivisionText: TText;
    function GetSubDivisionText: TText;
    function GetNeedle: TTMSFMXNeedleShape;
    function GetInnerElement: TControl;
    function GetOuterElement: TControl;
    function GetOuterCenterElement: TControl;
    function GetInnerCenterElement: TControl;
    function GetDisplayText: TText;
    property Version: String read GetVersion;
  end;

  TGaugeDirection = (gdHorizontal, gdVertical);

  TTMSFMXValuePosition = (vpDown, vpUp);

  [ComponentPlatformsAttribute(TMSPlatforms)]
  TTMSFMXCustomLinearGauge = class(TTMSFMXGauge)
  private
    FValuePosition: TTMSFMXValuePosition;
    procedure SetValuePosition(const Value: TTMSFMXValuePosition);
  protected
    procedure AnimateUpdateDivisions; override;
    procedure AnimateUpdateValues; override;
    procedure ApplyStyle; override;
  public
    procedure AssignEvents; override;
    function XYToValue(X, Y: Single): TGaugeValue; override;
    constructor Create(AOwner: TComponent); override;
    procedure UpdateNeedle; override;
    function GetNeedlePlaceHolder(AControl: TFMXObject): TFMXObject; override;
    procedure ApplySetPoint(InnerElement: TControl; ASetPoint: TTMSFMXSetPoint; ASetPointShape: TTMSFMXSetPointShape; LineDiv: TLine); override;
    procedure ApplySection(InnerElement: TControl; ASection: TTMSFMXSection; ASectionShape: TTMSFMXSectionShape; LineDiv: TLine); override;
    procedure ApplyExtraNeedle(InnerElement: TControl; AExtraNeedle: TTMSFMXExtraNeedle; ANeedleShape: TTMSFMXNeedleShape; LineDiv: TLine); override;
    procedure ApplySubLineDivision(CenterX, CenterY: Single; iDiv, iSubDiv: Integer; AValue: Single; lDiv, LineDiv: TLine); override;
    procedure ApplySubTextDivision(CenterX, CenterY: Single; iDiv, iSubDiv: Integer; AValue: Single; lDiv: TText; LineDiv: TLine); override;
    procedure ApplyLineDivision(CenterX, CenterY: Single; iDiv: Integer; AValue: Single; lDiv, LineDiv: TLine); override;
    procedure ApplyTextDivision(CenterX, CenterY: Single; iDiv: Integer; AValue: Single; lDiv: TText; LineDiv: TLine); override;
  published
    property Version;
    property ValuePosition: TTMSFMXValuePosition read FValuePosition write SetValuePosition default vpDown;
    property AnimationFactor;
    property OnApplyDivisionLineStyle;
    property OnApplySubDivisionLineStyle;
    property OnApplyDivisionTextStyle;
    property OnApplySubDivisionTextStyle;
    property OnValueChanged;
    property OnValueChanging;
    property Animation;
    property Divisions;
    property SubDivisions;
    property Value;
    property ShowSubDivisions;
    property ShowDivisions;
    property ShowSubDivisionText;
    property ShowDivisionText;
    property DivisionFormat;
    property SubDivisionFormat;
  end;

  [ComponentPlatformsAttribute(TMSPlatforms)]
  TTMSFMXLinearGauge = class(TTMSFMXCustomLinearGauge)
  published
    property OnSetPointClick;
    property OnExtraNeedleClick;
    property OnSectionClick;
    property OnValueClick;
    property MinimumValue;
    property MaximumValue;
    property Sections;
    property SetPoints;
    property ExtraNeedles;
  end;

  [ComponentPlatformsAttribute(TMSPlatforms)]
  TTMSFMXJogMeter = class(TTMSFMXCustomLinearGauge)
  private
    FAperture: Single;
    FStep: Single;
    procedure SetAperture(const Value: Single);
    procedure SetValue(const Value: Single); override;
    procedure SetStep(const Value: Single);
  public
    function XYToValue(X, Y: Single): TGaugeValue; override;
    procedure ApplyAdditionalStyle(AControl: TFmxObject); override;
    procedure UpdateNeedle; override;
    procedure UpdateDivisions; override;
    procedure UpdateValues; override;
    procedure CleanUpLines; override;
    procedure CleanUpValues; override;
    procedure ApplySubLineDivision(CenterX, CenterY: Single; iDiv, iSubDiv: Integer; AValue: Single; lDiv, LineDiv: TLine); override;
    procedure ApplySubTextDivision(CenterX, CenterY: Single; iDiv, iSubDiv: Integer; AValue: Single; lDiv: TText; LineDiv: TLine); override;
    procedure ApplyLineDivision(CenterX, CenterY: Single; iDiv: Integer; AValue: Single; lDiv, LineDiv: TLine); override;
    procedure ApplyTextDivision(CenterX, CenterY: Single; iDiv: Integer; AValue: Single; lDiv: TText; LineDiv: TLine); override;
    constructor Create(AOwner: TComponent); override;
  published
    property Aperture: Single read FAperture write SetAperture;
    property OnValueClick;
    property OnSetPointClick;
    property OnExtraNeedleClick;
    property OnSectionClick;
    property Step: Single read FStep write SetStep;
  end;

  [ComponentPlatformsAttribute(TMSPlatforms)]
  TTMSFMXCustomGauge = class(TTMSFMXGauge)
  private
    FLayoutStartAngle: Integer;
    FLayoutStopAngle: Integer;
    FDivisionsStartAngle: Integer;
    FDivisionsStopAngle: Integer;
    function GetDivisionsStopAngle: integer;
    procedure SetDivisionsStartAngle(const Value: integer);
    procedure SetDivisionsStopAngle(const Value: integer);
    procedure SetLayoutStartAngle(const Value: integer);
    procedure SetLayoutStopAngle(const Value: integer);
  protected
    function FindStartAngle: Single; override;
    function FindStopAngle: Single; override;
    procedure UpdateNeedle; override;
    function GetNeedlePlaceHolder(AControl: TFMXObject): TFMXObject; override;
    procedure ApplySection(InnerElement: TControl; ASection: TTMSFMXSection; ASectionShape: TTMSFMXSectionShape; LineDiv: TLine); override;
    procedure ApplyExtraNeedle(InnerElement: TControl; AExtraNeedle: TTMSFMXExtraNeedle; ANeedleShape: TTMSFMXNeedleShape; LineDiv: TLine); override;
    procedure ApplySubLineDivision(CenterX, CenterY: Single; iDiv, iSubDiv: Integer; AValue: Single; lDiv, LineDiv: TLine); override;
    procedure ApplySubTextDivision(CenterX, CenterY: Single; iDiv, iSubDiv: Integer; AValue: Single; lDiv: TText; LineDiv: TLine); override;
    procedure ApplyLineDivision(CenterX, CenterY: Single; iDiv: Integer; AValue: Single; lDiv, LineDiv: TLine); override;
    procedure ApplyTextDivision(CenterX, CenterY: Single; iDiv: Integer; AValue: Single; lDiv: TText; LineDiv: TLine); override;
    procedure ApplySetPoint(InnerElement: TControl; ASetPoint: TTMSFMXSetPoint; ASetPointShape: TTMSFMXSetPointShape; LineDiv: TLine); override;
    function GetAngle(AValue: Single): Single;
    property DivisionsStartAngle: integer read FDivisionsStartAngle write SetDivisionsStartAngle default 135;
    property DivisionsStopAngle: integer read FDivisionsStopAngle write SetDivisionsStopAngle default 45;
    property LayoutStartAngle: integer read FLayoutStartAngle write SetLayoutStartAngle default 0;
    property LayoutStopAngle: integer read FLayoutStopAngle write SetLayoutStopAngle default 360;
  public
    constructor Create(AOwner: TComponent); override;
    procedure UpdateLayout; override;
  published
    property Version;
  end;

  [ComponentPlatformsAttribute(TMSPlatforms)]
  TTMSFMXCircularGauge = class(TTMSFMXCustomGauge)
  published
    property OnValueClick;
    property OnSetPointClick;
    property OnExtraNeedleClick;
    property OnSectionClick;
    property MinimumValue;
    property MaximumValue;
    property Sections;
    property SetPoints;
    property ExtraNeedles;
    property DisplayText;
    property AnimationFactor;
    property OnApplyDivisionLineStyle;
    property OnApplySubDivisionLineStyle;
    property OnApplyDivisionTextStyle;
    property OnApplySubDivisionTextStyle;
    property OnValueChanged;
    property OnValueChanging;
    property Animation;
    property Divisions;
    property SubDivisions;
    property Value;
    property ShowSubDivisions;
    property ShowDivisions;
    property ShowSubDivisionText;
    property ShowDivisionText;
    property DivisionFormat;
    property SubDivisionFormat;
    property DivisionsStartAngle;
    property DivisionsStopAngle;
    property LayoutStartAngle;
    property LayoutStopAngle;
  end;

  [ComponentPlatformsAttribute(TMSPlatforms)]
  TTMSFMXCompass = class(TTMSFMXCustomGauge)
  protected
    procedure DoApplyDivisionTextStyle(Sender: TObject; AOriginalText, AText: TText; DivisionUnit: Integer; Value: Single); override;
    procedure DoApplyDivisionLineStyle(Sender: TObject; AOriginalLine, ALine: TLine; DivisionUnit: Integer; Value: Single); override;
  public
    constructor Create(AOwner: TComponent); override;
  end;

  [ComponentPlatformsAttribute(TMSPlatforms)]
  TTMSFMXClock = class(TTMSFMXCustomGauge)
  private
    FTimer: TTimer;
    FClockTime: TTime;
    FOnTimeChanged: TNotifyEvent;
    FOffset: Integer;
    FShowSeconds: Boolean;
    FInterval: Integer;
    function GetActive: Boolean;
    procedure SetActive(const Value: Boolean);
    procedure SetInterval(const Value: Integer);
    procedure SetTime(const Value: TTime);
    function GetTime: TTime;
    procedure SetOffset(const Value: Integer);
    procedure SetShowSeconds(const Value: Boolean);
  protected
    procedure ApplyAdditionalStyle(AControl: TFmxObject); override;
    procedure ApplyStyle; override;
    procedure UpdateTime;
    procedure DoTime(Sender: TObject);
    procedure DoApplyDivisionTextStyle(Sender: TObject; AOriginalText, AText: TText; DivisionUnit: Integer; Value: Single); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property OnTimeChanged: TNotifyEvent read FOnTimeChanged write FOnTimeChanged;
    property Active: Boolean read GetActive write SetActive;
    property Interval: Integer read FInterval write SetInterval default 1000;
    property ClockTime: TTime read GetTime write SetTime;
    property Offset: Integer read FOffset write SetOffset default 0;
    property ShowSeconds: Boolean read FShowSeconds write SetShowSeconds default True;

    property DisplayText;
    property ShowDivisions;
    property ShowSubDivisions;
    property ShowDivisionText;
  end;

  TTMSFMXRotarySwitch = class;

  TTMSFMXSwitchPosition = class(TCollectionItem)
  private
    FOwner: TTMSFMXRotarySwitch;
    FTag: Integer;
    FText: string;
    procedure SetText(const Value: string);
  public
    function TextShape: TText;
    function EllipseShape: TEllipse;
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
  published
    property Text: string read FText write SetText;
    property Tag: Integer read FTag write FTag;
  end;

  TTMSFMXSwitchPositions = class(TTMSFMXBaseCollection)
  private
    FOwner: TTMSFMXRotarySwitch;
    function GetItem(Index: Integer): TTMSFMXSwitchPosition;
    procedure SetItem(Index: Integer; const Value: TTMSFMXSwitchPosition);
  protected
    function GetOwner: TPersistent; override;
  public
    constructor Create(AOwner: TTMSFMXRotarySwitch);
    function Add: TTMSFMXSwitchPosition;
    function Insert(Index: Integer): TTMSFMXSwitchPosition;
    property Items[Index: Integer]: TTMSFMXSwitchPosition read GetItem write SetItem; default;
  end;

  [ComponentPlatformsAttribute(TMSPlatforms)]
  TTMSFMXRotarySwitch = class(TTMSFMXCustomGauge)
  private
    FPositions: TTMSFMXSwitchPositions;
    procedure SetPositions(const Value: TTMSFMXSwitchPositions);
  protected
    function GetPositionForShape(AShape: TFMXObject): TTMSFMXSwitchPosition;
    procedure ValueClick(Sender: TObject); override;
    function XYToValue(X, Y: Single): TGaugeValue; override;
    procedure Changed; virtual;
    procedure ApplyLineDivision(CenterX, CenterY: Single; iDiv: Integer; AValue: Single; lDiv, LineDiv: TLine); override;
    procedure ApplyTextDivision(CenterX, CenterY: Single; iDiv: Integer; AValue: Single; lDiv: TText; LineDiv: TLine); override;
    procedure DoApplyDivisionTextStyle(Sender: TObject; AOriginalText, AText: TText; DivisionUnit: Integer; Value: Single); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property Positions: TTMSFMXSwitchPositions read FPositions write SetPositions;
    property AnimationFactor;
    property OnApplyDivisionLineStyle;
    property OnApplyDivisionTextStyle;
    property OnValueChanged;
    property OnValueChanging;
    property OnValueClick;
    property Animation;
    property DivisionsStartAngle;
    property DivisionsStopAngle;
    property LayoutStartAngle;
    property LayoutStopAngle;
    property Value;
  end;

  [ComponentPlatformsAttribute(TMSPlatforms)]
  TTMSFMXKnobSwitch = class(TTMSFMXRotarySwitch)
  private
    FEnableGlow: Boolean;
    procedure SetValue(const Value: Single); override;
  protected
    procedure ApplyAdditionalStyle(AControl: TFMXObject); override;
  public
    procedure UpdateLayout; override;
    procedure EnableDisableGlow(Enable: Boolean);
    procedure DoApplyDivisionLineStyle(Sender: TObject; AOriginalLine, ALine: TLine; DivisionUnit: Integer; Value: Single); override;
    constructor Create(AOwner: TComponent); override;
  end;

implementation

{$R FMX.TMSGauge.res}

{ TTMSFMXGauge }

function TTMSFMXGauge.XYToValue(X, Y: Single): TGaugeValue;
var
  cx, cy: Single;
  innerelement: TControl;
  st: Single;
  val, val1, val2: Single;
  stStart, stStop: Single;
begin
  Result.State := False;
  innerelement := GetInnerElement;
  if Assigned(innerelement) then
  begin
    cx := innerElement.Width / 2;
    cy := innerElement.Height / 2;
    val1 := X - cx;
    val2 := Y - cy;
    if val1 <> 0 then
    begin
      val := val2 / val1;
      st := ArcTan(val);
      st := RadToDeg(st);
      if (val1 < 0) and (val2 > 0) then
        st := st + 180
      else if (val1 < 0) and (val2 < 0) then
        st := st + 180
      else
        st := st + 360;

      stStart := FindStartAngle;
      stStop := FindStopAngle;
      if (st >= stStart) and (st <= stStop) then
      begin
        Result.State := True;
        val := MaximumValue - MinimumValue;
        Result.Value := ((val / (stStop - stStart)) * (st - stStart)) + MinimumValue;
      end;
    end;
  end;
end;

procedure TTMSFMXGauge.InnerElementMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
var
  val: TGaugeValue;
begin
  val := XYToValue(X, Y);
  if val.State then
  begin
    if Assigned(OnValueClick) then
      OnValueClick(Self, val.Value);
  end;
end;

procedure TTMSFMXGauge.AlignNeedle;
var
  outerElement: TControl;
begin
  outerElement := GetOuterCenterElement;
  if Assigned(outerElement) then
    outerElement.BringToFront;
end;

function AnimateDouble(var Start, Stop: Single; Delta: Single; Margin: Double): Boolean;
begin
  Result := true;
  if (Start > Stop - Margin) and (Start < Stop + Margin) then
  begin
    Start := Stop;
    Result := false;
  end
  else
  begin
    Delta := Max(Margin, Delta);
    if Start < Stop then
      Start := Start + Delta
    else
      Start := Start - Delta;
  end;
end;

procedure TTMSFMXGauge.AnimateExtraGauge(Sender: TObject);
var
  d, pos: Single;
  res: Boolean;
  I: Integer;
  iCount: Integer;
begin
  if Animation and not (csLoading in ComponentState) and not (csDesigning in ComponentState) and doExtraAnimation then
  begin
    for I := 0 to ExtraNeedles.Count - 1 do
    begin
      if ExtraNeedles[I].FdoAnim then
      begin
        d := Abs(ExtraNeedles[I].FcurrentValueTo - ExtraNeedles[I].fcurrentValue) / AnimationFactor;
        pos := ExtraNeedles[I].fcurrentValue;
        res := AnimateDouble(pos, ExtraNeedles[I].FcurrentValueTo, d, 0.001);
        if res then
        begin
          ExtraNeedles[I].fcurrentValue := pos;
          ExtraNeedles[I].UpdateExtraNeedle;
        end
        else
        begin
          ExtraNeedles[I].FdoAnim := False;
          ExtraNeedles[I].FcurrentValue := ExtraNeedles[I].FcurrentValueTo;
          ExtraNeedles[I].UpdateExtraNeedle;
        end;
      end;
    end;

    for I := 0 to SetPoints.Count - 1 do
    begin
      if SetPoints[I].FdoAnim then
      begin
        d := Abs(SetPoints[I].FcurrentValueTo - SetPoints[I].fcurrentValue) / AnimationFactor;
        pos := SetPoints[I].fcurrentValue;
        res := AnimateDouble(pos, SetPoints[I].FcurrentValueTo, d, 0.001);
        if res then
        begin
          SetPoints[I].fcurrentValue := pos;
          SetPoints[I].UpdateSetPoint;
        end
        else
        begin
          SetPoints[I].FdoAnim := False;
          SetPoints[I].FcurrentValue := SetPoints[I].FcurrentValueTo;
          SetPoints[I].UpdateSetPoint;
        end;
      end;
    end;

    iCount := 0;
    for I := 0 to ExtraNeedles.Count - 1 do
    begin
      if ExtraNeedles[I].FdoAnim then
        Inc(iCount);
    end;
    for I := 0 to SetPoints.Count - 1 do
    begin
      if SetPoints[I].FdoAnim then
        Inc(iCount);
    end;

    if iCount = 0 then
    begin
      doanimation := False;
      FExtraTimer.Enabled := False;
    end;
  end;
end;

procedure TTMSFMXGauge.AnimateGauge(Sender: TObject);
var
  d, pos: Single;
  res: Boolean;
begin
  if Animation and not (csLoading in ComponentState) and not (csDesigning in ComponentState) and doAnimation then
  begin
    d := Abs(FcurrentValueTo - fcurrentValue) / AnimationFactor;
    pos := fcurrentValue;
    res := AnimateDouble(pos, FcurrentValueTo, d, 0.001);
    if res then
    begin
      fcurrentValue := pos;
      AnimateUpdateDivisions;
      AnimateUpdateValues;
      AnimateUpdateNeedle;
      if Assigned(OnValueChanging) then
        OnValueChanging(Self, FCurrentValue);
    end
    else
    begin
      doAnimation := False;
      FcurrentValue := FcurrentValueTo;
      AnimateUpdateDivisions;
      AnimateUpdateValues;
      AnimateUpdateNeedle;
      FAnimTimer.Enabled := False;

      if Assigned(OnValueChanged) then
        OnValueChanged(Self, FCurrentValue);
    end;
  end;
end;

procedure TTMSFMXGauge.AnimateUpdateDivisions;
begin

end;

procedure TTMSFMXGauge.AnimateUpdateNeedle;
begin
  UpdateNeedle;
end;

procedure TTMSFMXGauge.AnimateUpdateValues;
begin

end;

procedure TTMSFMXGauge.ApplyExtraNeedle(InnerElement: TControl; AExtraNeedle: TTMSFMXExtraNeedle; ANeedleShape: TTMSFMXNeedleShape; LineDiv: TLine);
begin
//
end;

procedure TTMSFMXGauge.ApplyLineDivision(CenterX, CenterY: Single; iDiv: Integer; AValue: Single; lDiv, LineDiv: TLine);
begin
//
end;

procedure TTMSFMXGauge.ApplySection(InnerElement: TControl; ASection: TTMSFMXSection; ASectionShape: TTMSFMXSectionShape; LineDiv: TLine);
begin
  //
end;

procedure TTMSFMXGauge.ApplySetPoint(InnerElement: TControl;
  ASetPoint: TTMSFMXSetPoint; ASetPointShape: TTMSFMXSetPointShape; LineDiv: TLine);
begin
//
end;

procedure TTMSFMXGauge.ApplySubLineDivision(CenterX, CenterY: Single; iDiv,
  iSubDiv: Integer; AValue: Single; lDiv, LineDiv: TLine);
begin
//
end;

procedure TTMSFMXGauge.ApplySubTextDivision(CenterX, CenterY: Single; iDiv,
  iSubDiv: Integer; AValue: Single; lDiv: TText; LineDiv: TLine);
begin
//
end;

procedure TTMSFMXGauge.ApplyTextDivision(CenterX, CenterY: Single;
  iDiv: Integer; AValue: Single; lDiv: TText; LineDiv: TLine);
begin
//
end;

procedure TTMSFMXGauge.AssignEvents;
var
  innerelement: TControl;
begin
  innerelement := GetInnerElement;
  if Assigned(innerelement) then
  begin
    innerelement.OnMouseUp := InnerElementMouseUp;
    innerelement.OnClick := OnClick;
    innerelement.OnDblClick := OnDblClick;
  end;
end;

procedure TTMSFMXGauge.Update;
begin
  inherited;
  UpdateDivisions;
  UpdateValues;
  UpdateNeedle;
  UpdateLayout;
  UpdateDisplayText;
  AssignEvents;
end;

procedure TTMSFMXGauge.UpdateDisplayText;
var
  dtext: TText;
begin
  dtext := GetDisplayText;
  if Assigned(dtext) then
    dtext.Text := DisplayText;
end;

procedure TTMSFMXGauge.UpdateDivisions;
var
  innerElement: TControl;
  tdiv, tsubdiv, l, ldiv: TLine;
  iDiv, iSubdiv: Integer;
  cx, cy: Single;
  lName, ldivName: String;
  lExists, lsubexists: TLine;
  fOrigVal, fVal, oVal: Single;
begin
  inherited;

  innerElement := GetInnerElement;
  tdiv := GetDivision;
  tsubdiv := GetSubDivision;

  if Assigned(innerElement) and Assigned(tdiv) then
  begin
    cx := innerElement.Width / 2;
    cy := innerElement.Height / 2;

    for iDiv := 0 to Divisions do
    begin
      if ShowDivisions then
      begin
        lName := 'divisionline' + inttostr(iDiv);
        lExists := DoesDivisionLineExist(lName);

        if not Assigned(lExists) then
          l := TLine.Create(innerElement)
        else
          l := lExists;

        l.BeginUpdate;
        l.HitTest := False;
        {$IFDEF DELPHIXE6_LVL}
        l.LineType := TLineType.Left;
        {$ELSE}
        l.LineType := TLineType.ltLeft;
        {$ENDIF}
        l.Width := tdiv.Stroke.Thickness;
        l.Height := tdiv.Height;
        l.Stroke.Assign(tdiv.Stroke);
        l.StrokeThickness := tdiv.Stroke.Thickness;
        l.Fill.Assign(tdiv.Fill);
        l.Name := lName;
        l.Locked := True;

        innerElement.AddObject(l);

        l.BringToFront;

        oVal := MinimumValue + ((MaximumValue - MinimumValue) / Divisions * iDiv);

        DoApplyDivisionLineStyle(Self, tdiv, l, iDiv, oVal);

        ApplyLineDivision(cx, cy, iDiv, oVal, l, tdiv);

        l.EndUpdate;
      end;

      if iDiv = Divisions then
        break;

      if Assigned(tsubdiv) then
      begin
        for iSubdiv := 1 to SubDivisions - 1 do
        begin
          if ShowSubDivisions then
          begin
            lDivName := 'subdivisionline'+inttostr(iDiv)+inttostr(iSubdiv);
            lsubexists := DoesDivisionLineExist(lDivName);
            if not Assigned(lsubexists) then
              ldiv := TLine.Create(innerElement)
            else
              ldiv := lsubexists;

            ldiv.BeginUpdate;
            ldiv.HitTest := False;
            {$IFDEF DELPHIXE6_LVL}
            ldiv.LineType := TLineType.Left;
            {$ELSE}
            ldiv.LineType := TLineType.ltLeft;
            {$ENDIF}
            ldiv.Width := tsubdiv.Stroke.Thickness;
            ldiv.Height := tsubdiv.Height;
            ldiv.Stroke.Assign(tsubdiv.Stroke);
            ldiv.StrokeThickness := tsubdiv.Stroke.Thickness;
            ldiv.Fill.Assign(tsubdiv.Fill);
            ldiv.Locked := True;

            ldiv.Name := lDivName;

            innerElement.AddObject(ldiv);

            ldiv.BringToFront;

            fOrigVal := (((MaximumValue - MinimumValue) / Divisions) * iDiv);
            fval := (((MaximumValue - MinimumValue) / Divisions) * (iDiv + 1)) - fOrigVal;
            fval := fOrigVal + ((fval / SubDivisions) * iSubdiv);
            oVal := MinimumValue + fVal;

            ApplySubLineDivision(cx, cy, iDiv, iSubDiv, oVal, ldiv, tsubdiv);

            DoApplySubDivisionLineStyle(Self, tsubdiv, ldiv, iDiv, iSubdiv, oVal);

            ldiv.EndUpdate;
          end;
        end;
        tsubdiv.Visible := False;
      end;
    end;

    tdiv.Visible := False;
  end;

  CleanUpLines;
  AlignNeedle;
end;

procedure TTMSFMXGauge.UpdateExtraNeedles(AControl: TFMXObject);
var
  I: Integer;
  sn: String;
  newsec, sec: TTMSFMXNeedleShape;
  origneedle: TTMSFMXNeedleShape;
  innerElement: TFMXObject;
  ldiv: TFMXObject;
begin
  innerElement := GetNeedlePlaceHolder(AControl);

  if Assigned(AControl) then
  begin
    origneedle := AControl.FindStyleResource('needle') as TTMSFMXNeedleShape;
    ldiv := AControl.FindStyleResource('division');
  end
  else
  begin
    origneedle := GetNeedle;
    ldiv := GetDivision;
  end;


  if Assigned(innerElement) then
  begin
    for I := 0 to ExtraNeedles.Count - 1 do
    begin
      sn := 'FMXExtraNeedle'+inttostr(I);
      sec := DoesExtraNeedleExist(AControl, sn);
      if Assigned(sec) then
        newsec := sec
      else
      begin
        newsec := TTMSFMXNeedleShape.Create(innerElement);
        newsec.BeginUpdate;
        newsec.OnClick := ExtraNeedleClick;
        {$IFDEF DELPHIXE6_LVL}
        newsec.Align := TAlignLayout.Scale;
        {$ELSE}
        newsec.Align := TAlignLayout.alScale;
        {$ENDIF}
        newsec.Position.X := origneedle.Position.X;
        newsec.Position.Y := origneedle.Position.Y;
        newsec.RotationCenter.X := origneedle.RotationCenter.X;
        newsec.RotationCenter.Y := origneedle.RotationCenter.Y;
        newsec.Height := origneedle.Height;
        newsec.Width := origneedle.Width;
        newsec.RotationAngle := origneedle.RotationAngle;
        newsec.Locked := True;
        newsec.StyleName := sn;
        innerElement.AddObject(newsec);
        newsec.EndUpdate;
      end;

      newsec.Name := sn + 'Name';
      newsec.SendToBack;

      ApplyExtraNeedle(innerElement as TControl, ExtraNeedles[I], newsec, ldiv as TLine);
    end;
  end;

  CleanUpExtraNeedles(AControl);
end;

procedure TTMSFMXGauge.UpdateLayout;
begin
//
end;

procedure TTMSFMXGauge.CleanUpSections(AControl: TFMXObject);
var
  str: TStringList;
  iSection: Integer;
  lName: String;
  innerElement: TFMXObject;
  i: integer;
  obj: TFMXObject;
  l: TTMSFMXSectionShape;
begin
  innerElement := AControl.FindStyleResource('innerelement');

  str := TStringList.Create;
  for iSection := 0 to Sections.Count - 1 do
  begin
    lName := 'FMXSection'+inttostr(iSection);
    str.Add(UpperCase(lName+'Name'));
  end;

  if Assigned(innerElement) then
  begin
    for I := innerElement.ChildrenCount - 1 downto 0 do
    begin
      obj := innerElement.Children[i];
      if (obj is TTMSFMXSectionShape) then
      begin
        l := obj as TTMSFMXSectionShape;
        if str.IndexOf(UpperCase(l.Name)) = -1 then
        begin
        {$IFDEF DELPHI_LLVM}
        l.Release;
        {$ELSE}
        l.Free;
        {$ENDIF}
        end;
      end;
    end;
  end;

  str.Free;
end;

procedure TTMSFMXGauge.CleanUpSetPoints(AControl: TFMXObject);
var
  str: TStringList;
  iSection: Integer;
  lName: String;
  innerElement: TFMXObject;
  i: integer;
  obj: TFMXObject;
  l: TTMSFMXSetPointShape;
begin
  innerElement := AControl.FindStyleResource('innerelement');

  str := TStringList.Create;
  for iSection := 0 to SetPoints.Count - 1 do
  begin
    lName := 'FMXSetPoint'+inttostr(iSection);
    str.Add(UpperCase(lName+'Name'));
  end;

  if Assigned(innerElement) then
  begin
    for I := innerElement.ChildrenCount - 1 downto 0 do
    begin
      obj := innerElement.Children[i];
      if (obj is TTMSFMXSetPointShape) then
      begin
        l := obj as TTMSFMXSetPointShape;
        if str.IndexOf(UpperCase(l.Name)) = -1 then
        begin
        {$IFDEF DELPHI_LLVM}
        l.Release;
        {$ELSE}
        l.Free;
        {$ENDIF}
        end;
      end;
    end;
  end;

  str.Free;
end;

procedure TTMSFMXGauge.CleanUpExtraNeedles(AControl: TFMXObject);
var
  str: TStringList;
  iExtraNeedle: Integer;
  lName: String;
  innerElement: TFMXObject;
  i: integer;
  obj: TFMXObject;
  l: TTMSFMXNeedleShape;
  needle: TFmxObject;
begin
  innerElement := GetNeedlePlaceHolder(AControl);

  if Assigned(AControl) then
    needle := AControl.FindStyleResource('needle') as TTMSFMXNeedleShape
  else
    needle := GetNeedle;

  str := TStringList.Create;
  for iExtraNeedle := 0 to ExtraNeedles.Count - 1 do
  begin
    lName := 'FMXExtraNeedle'+inttostr(iExtraNeedle);
    str.Add(UpperCase(lName+'Name'));
  end;

  if Assigned(innerElement) then
  begin
    for I := innerElement.ChildrenCount - 1 downto 0 do
    begin
      obj := innerElement.Children[i];
      if (obj is TTMSFMXNeedleShape) then
      begin
        l := obj as TTMSFMXNeedleShape;
        if (l <> needle) and (str.IndexOf(UpperCase(l.Name)) = -1) then
        begin
          l.Free;
        end;
      end;
    end;
  end;

  str.Free;
end;

procedure TTMSFMXGauge.CleanUpLines;
var
  str: TStringList;
  iDiv, iSubDiv: Integer;
  lName, lDivName: String;
  innerElement: TControl;
  i: integer;
  obj: TFMXObject;
  l: TLine;
begin
  str := TStringList.Create;
  for iDiv := 0 to Divisions do
  begin
    if ShowDivisions then
    begin
      lName := 'divisionline' + inttostr(iDiv);
      str.Add(UpperCase(lName));
    end;

    if iDiv = Divisions then
      break;

    for iSubdiv := 1 to SubDivisions - 1 do
    begin
      if ShowSubDivisions then
      begin
        lDivName := 'subdivisionline'+inttostr(iDiv)+inttostr(iSubdiv);
        str.Add(UpperCase(lDivName));
      end;
    end;
  end;

  innerElement := GetInnerElement;
  if Assigned(innerElement) then
  begin
    for I := innerElement.ChildrenCount - 1 downto 0 do
    begin
      obj := innerElement.Children[i];
      if (obj is TLine) then
      begin
        l := obj as TLine;
        if str.IndexOf(UpperCase(l.Name)) = -1 then
        begin
        {$IFDEF DELPHI_LLVM}
        l.Release;
        {$ELSE}
        l.Free;
        {$ENDIF}
        end;
      end;
    end;
  end;

  str.Free;
end;

procedure TTMSFMXGauge.CleanUpValues;
var
  str: TStringList;
  iDiv, iSubDiv: Integer;
  lName, lDivName: String;
  innerElement: TControl;
  i: integer;
  obj: TFMXObject;
  l: TText;
begin
  str := TStringList.Create;
  for iDiv := 0 to Divisions do
  begin
    if ShowDivisionText then
    begin
      lName := 'divisiontext' + inttostr(iDiv);
      str.Add(UpperCase(lName));
    end;

    if iDiv = Divisions then
      break;

    for iSubdiv := 1 to SubDivisions - 1 do
    begin
      if ShowSubDivisionText then
      begin
        lDivName := 'subdivisiontext'+inttostr(iDiv)+inttostr(iSubdiv);
        str.Add(UpperCase(lDivName));
      end;
    end;
  end;

  innerElement := GetInnerElement;
  if Assigned(innerElement) then
  begin
    for I := innerElement.ChildrenCount - 1 downto 0 do
    begin
      obj := innerElement.Children[i];
      if (obj is TText) then
      begin
        l := obj as TText;
        if str.IndexOf(UpperCase(l.Name)) = -1 then
        begin
        {$IFDEF DELPHI_LLVM}
        l.Release;
        {$ELSE}
        l.Free;
        {$ENDIF}
        end;
      end;
    end;
  end;

  str.Free;
end;

constructor TTMSFMXGauge.Create(AOwner: TComponent);
begin
  inherited;
  FAnimation := True;
  FAnimationFactor := 4;
  FAnimTimer := TTimer.Create(Self);
  FAnimTimer.Interval := 1;
  FAnimTimer.Enabled := False;
  FAnimTimer.OnTimer := AnimateGauge;

  FExtraTimer := TTimer.Create(Self);
  FExtraTimer.Interval := 1;
  FExtraTimer.Enabled := False;
  FExtraTimer.OnTimer := AnimateExtraGauge;

  Width := 250;
  Height := 250;
  FDivisions := 10;
  FSubDivisions := 2;
  FMinimumValue := 0;
  FMaximumValue := 100;

  FDivisionFormat := '0';
  FSubDivisionFormat := '0.0';

  FShowSubDivisions := True;
  FShowDivisions := True;
  FShowSubDivisionText := False;
  FShowDivisionText := True;

  FSections := TTMSFMXSections.Create(TTMSFMXGaugeSection, Self);
  FExtraNeedles := TTMSFMXExtraNeedles.Create(Self);
  FSetPoints := TTMSFMXSetPoints.Create(Self);
end;

destructor TTMSFMXGauge.Destroy;
begin
  FAnimTimer.Free;
  FExtraTimer.Free;
  FSetPoints.Free;
  FSections.Free;
  FExtraNeedles.Free;
  inherited;
end;

function TTMSFMXGauge.GetDisplayText: TText;
var
  objtext: TFMXObject;
begin
  Result := nil;
  objtext := FindStyleResource('displaytext');
  if Assigned(objtext) and (objtext is TText) then
    Result := objtext as TText;
end;

function TTMSFMXGauge.GetDivision: TLine;
var
  objdiv: TFMXObject;
begin
  Result := nil;
  objdiv := FindStyleResource('division');
  if Assigned(objdiv) and (objdiv is TLine) then
    Result := objdiv as TLine;
end;

procedure TTMSFMXGauge.DoApplyDivisionLineStyle(Sender: TObject; AOriginalLine,
  ALine: TLine; DivisionUnit: Integer; Value: Single);
begin
  if Assigned(OnApplyDivisionLineStyle) then
    OnApplyDivisionLineStyle(Sender, AOriginalLine, ALine, DivisionUnit, Value);
end;

procedure TTMSFMXGauge.DoApplyDivisionTextStyle(Sender: TObject; AOriginalText,
  AText: TText; DivisionUnit: Integer; Value: Single);
begin
  if Assigned(OnApplyDivisionTextStyle) then
    OnApplyDivisionTextStyle(Sender, AOriginalText, AText, DivisionUnit, Value);
end;

procedure TTMSFMXGauge.DoApplySubDivisionLineStyle(Sender: TObject;
  AOriginalLine, ALine: TLine; DivisionUnit, SubDivisionUnit: Integer;
  Value: Single);
begin
  if Assigned(OnApplySubDivisionLineStyle) then
    OnApplySubDivisionLineStyle(Sender, AOriginalLine, ALine, DivisionUnit, SubDivisionUnit, Value);
end;

procedure TTMSFMXGauge.DoApplySubDivisionTextStyle(Sender: TObject;
  AOriginalText, AText: TText; DivisionUnit, SubDivisionUnit: Integer;
  Value: Single);
begin
  if Assigned(OnApplySubDivisionTextStyle) then
    OnApplySubDivisionTextStyle(Sender, AOriginalText, AText, DivisionUnit, SubDivisionUnit, Value);
end;

function TTMSFMXGauge.DoesDivisionLineExist(LineName: String): TLine;
var
  innerElement: TControl;
  i: integer;
  obj: TFMXObject;
  l: TLine;
begin
  Result := nil;
  innerElement := GetInnerElement;
  if Assigned(innerElement) then
  begin
    for I := 0 to innerElement.ChildrenCount - 1 do
    begin
      obj := innerElement.Children[i];
      if (obj is TLine) then
      begin
        l := obj as TLine;
        if UpperCase(l.Name) = UpperCase(LineName) then
        begin
          Result := l;
          Break;
        end;
      end;
    end;
  end;
end;

function TTMSFMXGauge.GetDivisionText: TText;
var
  objdiv: TFMXObject;
begin
  Result := nil;
  objdiv := FindStyleResource('divisiontext');
  if Assigned(objdiv) and (objdiv is TText) then
    Result := objdiv as TText;
end;

function TTMSFMXGauge.GetExtraNeedleForShape(
  AShape: TTMSFMXNeedleShape): TTMSFMXExtraNeedle;
var
  sn: string;
  I: Integer;
begin
  Result := nil;
  if Assigned(AShape) then
  begin
    for I := 0 to ExtraNeedles.Count - 1 do
    begin
      sn := 'FMXExtraNeedle'+inttostr(I)+'Name';
      if sn = AShape.Name then
      begin
        Result := ExtraNeedles[I];
        break;
      end;
    end;
  end;
end;

function TTMSFMXGauge.DoesDivisionTextExist(TextName: String): TText;
var
  innerElement: TControl;
  i: integer;
  tdiv, tsubdiv: TText;
  obj: TFMXObject;
  l: TText;
begin
  Result := nil;
  innerElement := GetInnerElement;
  tdiv := GetDivisionText;
  tsubdiv := GetSubDivisionText;
  if Assigned(innerElement) then
  begin
    for I := 0 to innerElement.ChildrenCount - 1 do
    begin
      obj := innerElement.Children[i];
      if (obj <> tdiv) and (obj <> tsubdiv) and (obj is TText) then
      begin
        l := obj as TText;
        if UpperCase(l.Name) = UpperCase(TextName) then
        begin
          Result := l;
          Break;
        end;
      end;
    end;
  end;
end;

function TTMSFMXGauge.DoesExtraNeedleExist(AControl: TFMXObject;
  ExtraNeedleName: String): TTMSFMXNeedleShape;
var
  innerElement: TFMXObject;
  i: integer;
  obj: TFMXObject;
  l: TTMSFMXNeedleShape;
begin
  Result := nil;

  innerElement := GetNeedlePlaceHolder(AControl);

  if Assigned(innerElement) then
  begin
    for I := 0 to innerElement.ChildrenCount - 1 do
    begin
      obj := innerElement.Children[i];
      if (obj is TTMSFMXNeedleShape) then
      begin
        l := obj as TTMSFMXNeedleShape;
        if UpperCase(l.StyleName) = UpperCase(ExtraNeedleName) then
        begin
          Result := l;
          Break;
        end;
      end;
    end;
  end;
end;

function TTMSFMXGauge.DoesSectionExist(AControl: TFMXObject; SectionName: String): TTMSFMXSectionShape;
var
  innerElement: TFMXObject;
  i: integer;
  obj: TFMXObject;
  l: TTMSFMXSectionShape;
begin
  Result := nil;
  innerElement := AControl.FindStyleResource('innerelement');
  if Assigned(innerElement) then
  begin
    for I := 0 to innerElement.ChildrenCount - 1 do
    begin
      obj := innerElement.Children[i];
      if (obj is TTMSFMXSectionShape) then
      begin
        l := obj as TTMSFMXSectionShape;
        if UpperCase(l.StyleName) = UpperCase(SectionName) then
        begin
          Result := l;
          Break;
        end;
      end;
    end;
  end;
end;

function TTMSFMXGauge.DoesSetPointExist(AControl: TFMXObject;
  SectionName: String): TTMSFMXSetPointShape;
var
  innerElement: TFMXObject;
  i: integer;
  obj: TFMXObject;
  l: TTMSFMXSetPointShape;
begin
  Result := nil;
  innerElement := AControl.FindStyleResource('innerelement');
  if Assigned(innerElement) then
  begin
    for I := 0 to innerElement.ChildrenCount - 1 do
    begin
      obj := innerElement.Children[i];
      if (obj is TTMSFMXSetPointShape) then
      begin
        l := obj as TTMSFMXSetPointShape;
        if UpperCase(l.StyleName) = UpperCase(SectionName) then
        begin
          Result := l;
          Break;
        end;
      end;
    end;
  end;
end;

procedure TTMSFMXGauge.ExtraNeedleClick(Sender: TObject);
var
  sps: TTMSFMXNeedleShape;
  stpoint: TTMSFMXExtraNeedle;
begin
  if Assigned(Sender) then
  begin
    sps := Sender as TTMSFMXNeedleShape;
    stpoint := GetExtraNeedleForShape(sps);
    if Assigned(OnExtraNeedleClick) and Assigned(stpoint) then
      OnExtraNeedleClick(Self, stpoint);
  end;
end;

function TTMSFMXGauge.FindStartAngle: Single;
begin
  Result := 0;
end;

function TTMSFMXGauge.FindStopAngle: Single;
begin
  Result := 0;
end;

function TTMSFMXGauge.GetInnerCenterElement: TControl;
begin
  Result := FindStyleResource('innercenterelement') as TControl;
end;

function TTMSFMXGauge.GetInnerElement: TControl;
begin
  Result := FindStyleResource('innerelement') as TControl;
end;

function TTMSFMXGauge.GetInstance: NativeUInt;
begin
  Result := hinstance;
end;

function TTMSFMXGauge.GetNeedle: TTMSFMXNeedleShape;
var
  objneedle: TFMXObject;
begin
  Result := nil;
  objneedle := FindStyleResource('needle');
  if Assigned(objneedle) and (objneedle is TTMSFMXNeedleShape) then
    Result := objneedle as TTMSFMXNeedleShape;
end;

function TTMSFMXGauge.GetNeedlePlaceHolder(AControl: TFMXObject): TFMXObject;
begin
  Result := nil;
end;

function TTMSFMXGauge.GetOuterCenterElement: TControl;
begin
  Result := FindStyleResource('outercenterelement') as TControl;
end;

function TTMSFMXGauge.GetOuterElement: TControl;
begin
  Result := FindStyleResource('outerelement') as TControl;
end;

function TTMSFMXGauge.GetSectionForShape(
  AShape: TTMSFMXSectionShape): TTMSFMXSection;
var
  sn: string;
  I: Integer;
begin
  Result := nil;
  if Assigned(AShape) then
  begin
    for I := 0 to Sections.Count - 1 do
    begin
      sn := 'FMXSection'+inttostr(I)+'Name';
      if sn = AShape.Name then
      begin
        Result := Sections[I];
        break;
      end;
    end;
  end;
end;

function TTMSFMXGauge.GetSetPointForShape(
  AShape: TTMSFMXSetPointShape): TTMSFMXSetPoint;
var
  sn: string;
  I: Integer;
begin
  Result := nil;
  if Assigned(AShape) then
  begin
    for I := 0 to SetPoints.Count - 1 do
    begin
      sn := 'FMXSetPoint'+inttostr(I)+'Name';
      if sn = AShape.Name then
      begin
        Result := SetPoints[I];
        break;
      end;
    end;
  end;
end;

function TTMSFMXGauge.GetStyleObject: TFMXObject;
begin
  Result := Inherited GetStyleObject;
  UpdateSections(Result);
  UpdateExtraNeedles(Result);
  UpdateSetPoints(Result);
end;

function TTMSFMXGauge.GetSubDivision: TLine;
var
  objdiv: TFMXObject;
begin
  Result := nil;
  objdiv := FindStyleResource('subdivision');
  if Assigned(objdiv) and (objdiv is TLine) then
    Result := objdiv as TLine;
end;

function TTMSFMXGauge.GetSubDivisionText: TText;
var
  objdiv: TFMXObject;
begin
  Result := nil;
  objdiv := FindStyleResource('subdivisiontext');
  if Assigned(objdiv) and (objdiv is TText) then
    Result := objdiv as TText;
end;

function TTMSFMXGauge.GetValue: Single;
begin
  if doanimation then
    Result := FCurrentValueTo
  else
    Result := FCurrentValue;
end;

function TTMSFMXGauge.GetVersion: String;
var
  vn: Integer;
begin
  vn := GetVersionNr;
  Result := IntToStr(Hi(Hiword(vn)))+'.'+IntToStr(Lo(Hiword(vn)))+'.'+IntToStr(Hi(Loword(vn)))+'.'+IntToStr(Lo(Loword(vn)));
end;

function TTMSFMXGauge.GetVersionNr: integer;
begin
  Result := MakeLong(MakeWord(BLD_VER,REL_VER),MakeWord(MIN_VER,MAJ_VER));
end;

procedure TTMSFMXGauge.Resize;
begin
  inherited;
  ApplyStyle;
end;

procedure TTMSFMXGauge.SectionClick(Sender: TObject);
var
  sps: TTMSFMXSectionShape;
  stpoint: TTMSFMXSection;
begin
  if Assigned(Sender) then
  begin
    sps := Sender as TTMSFMXSectionShape;
    stpoint := GetSectionForShape(sps);
    if Assigned(OnSectionClick) and Assigned(stpoint) then
      OnSectionClick(Self, stpoint);
  end;
end;

procedure TTMSFMXGauge.SectionMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin
  InnerElementMouseUp(Sender, Button, Shift, X, Y);
end;

procedure TTMSFMXGauge.SetAnimation(const Value: Boolean);
begin
  if FAnimation <> Value then
    FAnimation := Value;
end;

procedure TTMSFMXGauge.SetDisplayText(const Value: String);
begin
  if FDisplayText <> Value then
  begin
    FDisplayText := Value;
    UpdateDisplayText;
  end;
end;

procedure TTMSFMXGauge.SetDivisionFormat(const Value: String);
begin
  if FDivisionFormat <> Value then
  begin
    FDivisionFormat := Value;
    UpdateValues;
  end;
end;

procedure TTMSFMXGauge.SetDivisions(const Value: Integer);
begin
  if FDivisions <> Value then
  begin
    FDivisions := Value;
    UpdateDivisions;
    UpdateValues;
  end;
end;

procedure TTMSFMXGauge.SetExtraNeedles(const Value: TTMSFMXExtraNeedles);
begin
  if FExtraNeedles <> Value then
    FExtraNeedles.Assign(Value);
end;

procedure TTMSFMXGauge.SetMaxValue(const Value: Single);
begin
  if FMaximumValue <> Value then
  begin
    FMaximumValue := Value;
    self.Value := Max(FMinimumValue, Min(self.Value, FMaximumValue));
    UpdateNeedle;
    UpdateValues;
  end;
end;

procedure TTMSFMXGauge.SetMinValue(const Value: Single);
begin
  if FMinimumValue <> Value then
  begin
    FMinimumValue := Value;
    Self.Value := Max(FMinimumValue, Min(Self.Value, FMaximumValue));
    UpdateNeedle;
    UpdateValues;
  end;
end;

procedure TTMSFMXGauge.SetPointClick(Sender: TObject);
var
  sps: TTMSFMXSetPointShape;
  stpoint: TTMSFMXSetPoint;
begin
  if Assigned(Sender) then
  begin
    sps := Sender as TTMSFMXSetPointShape;
    stpoint := GetSetPointForShape(sps);
    if Assigned(OnSetPointClick) and Assigned(stpoint) then
      OnSetPointClick(Self, stpoint);
  end;
end;

procedure TTMSFMXGauge.SetSections(const Value: TTMSFMXSections);
begin
  if FSections <> Value then
    FSections.Assign(Value);
end;

procedure TTMSFMXGauge.SetSetPoints(const Value: TTMSFMXSetPoints);
begin
  if FSetPoints <> Value then
    FSetPoints.Assign(Value);
end;

procedure TTMSFMXGauge.SetShowDivisions(const Value: Boolean);
begin
  if FShowDivisions <> Value then
  begin
    FShowDivisions := Value;
    UpdateDivisions;
  end;
end;

procedure TTMSFMXGauge.SetShowDivisionText(const Value: Boolean);
begin
  if FShowDivisionText <> Value then
  begin
    FShowDivisionText := Value;
    UpdateValues;
  end;
end;

procedure TTMSFMXGauge.SetShowSubDivisions(const Value: Boolean);
begin
  if FShowSubDivisions <> Value then
  begin
    FShowSubDivisions := Value;
    UpdateDivisions;
  end;
end;

procedure TTMSFMXGauge.SetShowSubDivisionText(const Value: Boolean);
begin
  if FShowSubDivisionText <> Value then
  begin
    FShowSubDivisionText := Value;
    UpdateValues;
  end;
end;

procedure TTMSFMXGauge.SetSubDivisionFormat(const Value: String);
begin
  if FSubDivisionFormat <> Value then
  begin
    FSubDivisionFormat := Value;
    UpdateValues;
  end;
end;

procedure TTMSFMXGauge.SetSubDivisions(const Value: Integer);
begin
  if FSubDivisions <> Value then
  begin
    FSubDivisions := Value;
    UpdateDivisions;
    UpdateValues;
  end;
end;

procedure TTMSFMXGauge.SetValue(const Value: Single);
var
  v: Double;
begin
  v := Value;
  v := Max(Min(v, MaximumValue), MinimumValue);
  if Animation and not (csDesigning in ComponentState) and not (csLoading in ComponentState) then
  begin
    doAnimation := true;
    FcurrentValueTo := v;
    FAnimTimer.Enabled := True;
  end
  else
  begin
    fcurrentValue := v;
    FcurrentValueTo := v;
    UpdateDivisions;
    UpdateValues;
    UpdateNeedle;
    if Assigned(OnValueChanged) then
      OnValueChanged(Self, Value);
  end;
end;

procedure TTMSFMXGauge.UpdateNeedle;
begin
//
end;

procedure TTMSFMXGauge.UpdateSections(AControl: TFMXObject);
var
  I: Integer;
  sn: String;
  newsec, sec: TTMSFMXSectionShape;
  innerElement: TFMXObject;
  ldiv: TFMXObject;
begin
  if Assigned(AControl) then
  begin
    innerElement := AControl.FindStyleResource('innerelement');
    ldiv := AControl.FindStyleResource('division');
  end
  else
  begin
    innerElement := GetInnerElement;
    ldiv := GetDivision;
  end;

  if Assigned(innerElement) then
  begin
    for I := 0 to Sections.Count - 1 do
    begin
      sn := 'FMXSection'+inttostr(I);
      sec := DoesSectionExist(AControl, sn);
      if Assigned(sec) then
        newsec := sec
      else
      begin
        newsec := TTMSFMXSectionShape.Create(innerElement);
        newsec.BeginUpdate;
        newsec.OnClick := SectionClick;
        newsec.OnMouseUp := SectionMouseUp;
        newsec.Height := 230;
        newsec.Width := 230;
        newsec.Position.X := 0;
        newsec.Position.Y := 0;
        newsec.StyleName := sn;
        newsec.Locked := True;
        innerElement.AddObject(newsec);
        newsec.EndUpdate;
      end;

      newsec.Name := sn + 'Name';

      newsec.SendToBack;
      ApplySection(innerElement as TControl, Sections[I], newsec, ldiv as TLine);
    end;
  end;

  CleanUpSections(AControl);
end;

procedure TTMSFMXGauge.UpdateSetPoints(AControl: TFMXObject);
var
  I: Integer;
  sn: String;
  newsec, sec: TTMSFMXSetPointShape;
  innerElement: TFMXObject;
  LineDiv: TFmxObject;
begin
  if Assigned(AControl) then
  begin
    innerElement := AControl.FindStyleResource('innerelement');
    LineDiv := AControl.FindStyleResource('division');
  end
  else
  begin
    innerElement := GetInnerElement;
    LineDiv := GetDivision;
  end;

  if Assigned(innerElement) then
  begin
    for I := 0 to SetPoints.Count - 1 do
    begin
      sn := 'FMXSetPoint'+inttostr(I);
      sec := DoesSetPointExist(AControl, sn);
      if Assigned(sec) then
        newsec := sec
      else
      begin
        newsec := TTMSFMXSetPointShape.Create(innerElement);
        newsec.BeginUpdate;
        newsec.OnClick := SetPointClick;
        newsec.Width := 15;
        newsec.Height := 15;
        newsec.StyleName := sn;
        newsec.Locked := True;
        innerElement.AddObject(newsec);
        newsec.EndUpdate;
      end;

      {$IFDEF DELPHIXE6_LVL}
      newsec.Align := TAlignLayout.Scale;
      {$ELSE}
      newsec.Align := TAlignLayout.alScale;
      {$ENDIF}
      newsec.Kind := SetPoints[I].Kind;
      newsec.Name := sn + 'Name';

      ApplySetPoint(innerElement as TControl, SetPoints[I], newsec, LineDiv as TLine);
    end;
  end;

  CleanUpSetPoints(AControl);
end;

procedure TTMSFMXGauge.UpdateValues;
var
  innerElement: TControl;
  tdiv, tsubdiv, l, ldiv: TText;
  iDiv, iSubdiv: Integer;
  cx, cy: Single;
  lName, ldivName: String;
  lExists, lsubexists: TText;
  linediv, linesubdiv: TLine;
  rect: TRectF;
  fVal: Single;
  fOrigVal: Single;
  oVal: Single;
begin
  innerElement := GetInnerElement;
  tdiv := GetDivisionText;
  tsubdiv := GetSubDivisionText;
  linediv := GetDivision;
  linesubdiv := GetSubDivision;

  if Assigned(innerElement) and Assigned(tdiv) and Assigned(linediv) then
  begin
    tdiv.Visible := False;

    cx := innerElement.Width / 2;
    cy := innerElement.Height / 2;

    for iDiv := 0 to Divisions do
    begin
      if ShowDivisionText then
      begin

        lName := 'divisiontext' + inttostr(iDiv);
        lExists := DoesDivisionTextExist(lName);

        if not Assigned(lExists) then
          l := TText.Create(innerElement)
        else
          l := lExists;

        l.BeginUpdate;
        l.HitTest := True;
        l.OnClick := ValueClick;
        l.WordWrap := False;
        l.Color := tdiv.Color;
        l.Font.Assign(tdiv.Font);
        l.Name := lName;
        l.StyleName := lName;
        oVal := MinimumValue + ((MaximumValue - MinimumValue) / Divisions * iDiv);
        l.Text := FormatFloat(DivisionFormat, oVal);


        l.RotationAngle := tdiv.RotationAngle;
        l.RotationCenter.X := tdiv.RotationCenter.X;
        l.RotationCenter.Y := tdiv.RotationCenter.Y;


        DoApplyDivisionTextStyle(Self, tdiv, l, iDiv, oVal);

        Canvas.Font.Assign(l.Font);
        rect := BoundsRect;
        {$IFDEF DELPHIXE6_LVL}
        Canvas.MeasureText(rect, l.Text, False, [], TTextAlign.Leading, TTextAlign.Leading);
        {$ELSE}
        Canvas.MeasureText(rect, l.Text, False, [], TTextAlign.taLeading, TTextAlign.taLeading);
        {$ENDIF}

        l.Width := rect.Width;
        l.Height := rect.Height;


        l.Locked := True;
        innerElement.AddObject(l);
        l.BringToFront;
        ApplyTextDivision(cx, cy, iDiv, oVal, l, linediv);

        l.Visible := l.Text <> '';

        l.EndUpdate;
      end;

      if iDiv = Divisions then
        break;

      if Assigned(tsubdiv) and Assigned(linesubdiv) then
      begin
        for iSubdiv := 1 to SubDivisions - 1 do
        begin
          if ShowSubDivisionText then
          begin
            lDivName := 'subdivisiontext'+inttostr(iDiv)+inttostr(iSubdiv);
            lsubexists := DoesDivisionTextExist(lDivName);
            if not Assigned(lsubexists) then
              ldiv := TText.Create(innerElement)
            else
              ldiv := lsubexists;

            ldiv.BeginUpdate;
            ldiv.HitTest := True;
            ldiv.OnClick := ValueClick;
            ldiv.WordWrap := False;
            ldiv.AutoSize := True;
            ldiv.Color := tsubdiv.Color;
            ldiv.Font.Assign(tsubdiv.Font);
            ldiv.Name := ldivName;
            ldiv.StyleName := ldivName;
            fOrigVal := (((MaximumValue - MinimumValue) / Divisions) * iDiv);
            fval := (((MaximumValue - MinimumValue) / Divisions) * (iDiv + 1)) - fOrigVal;
            fval := fOrigVal + ((fval / SubDivisions) * iSubdiv);
            oVal := MinimumValue + fVal;
            ldiv.Text := FormatFloat(SubDivisionFormat, oVal);

            ldiv.RotationAngle := tsubdiv.RotationAngle;
            ldiv.RotationCenter.X := tsubdiv.RotationCenter.X;
            ldiv.RotationCenter.Y := tsubdiv.RotationCenter.Y;


            DoApplySubDivisionTextStyle(Self, tsubdiv, ldiv, iDiv, iSubdiv, oVal);

            Canvas.Font.Assign(ldiv.Font);
            rect := BoundsRect;
            {$IFDEF DELPHIXE6_LVL}
            Canvas.MeasureText(rect, ldiv.Text, False, [], TTextAlign.Leading, TTextAlign.Leading);
            {$ELSE}
            Canvas.MeasureText(rect, ldiv.Text, False, [], TTextAlign.taLeading, TTextAlign.taLeading);
            {$ENDIF}

            ldiv.Width := rect.Width;
            ldiv.Height := rect.Height;

            ldiv.Locked := True;
            innerElement.AddObject(ldiv);
            ldiv.BringToFront;
            ApplySubTextDivision(cx, cy, iDiv, iSubdiv, oVal, ldiv, linesubdiv);

            ldiv.Visible := ldiv.Text <> '';

            ldiv.EndUpdate;
          end;
        end;
        tsubdiv.Visible := False;
      end;
    end;
  end;

  CleanUpValues;

  AlignNeedle;
end;

procedure TTMSFMXGauge.ValueClick(Sender: TObject);
var
  t: TText;
begin
  if Assigned(Sender) then
  begin
    t := Sender as TText;
    if Assigned(OnValueClick) and Assigned(t) then
      OnValueClick(Self, StrToFloat(t.Text));
  end;
end;

{ TTMSFMXNeedleShape }

constructor TTMSFMXNeedleShape.Create(AOwner: TComponent);
begin
  inherited;
  Width := 50;
  Height := 100;
  FKind := nskNeedle;
end;

procedure TTMSFMXNeedleShape.Painting;
var
  pth: TPathData;
begin
  inherited;
  case Kind of
    nskTriangle:
    begin
      pth := TPathData.Create;
      pth.MoveTo(PointF(Width / 2, 0));
      pth.LineTo(PointF(Width, Height));
      pth.LineTo(PointF(0, Height));
      pth.ClosePath;
      Canvas.FillPath(pth, Opacity, Fill);
      Canvas.DrawPath(pth, Opacity, Stroke);
      pth.Free;
    end;
    nskRectangle:
    begin
      Canvas.FillRect(BoundsRect, 0, 0, AllCorners, Opacity, Fill);
      Canvas.DrawRect(BoundsRect, 0, 0, AllCorners, Opacity, Stroke);
    end;
    nskDiamond:
    begin
      pth := TPathData.Create;
      pth.MoveTo(PointF(Width / 2, 0));
      pth.LineTo(PointF(Width, Height / 2));
      pth.LineTo(PointF(Width / 2, Height));
      pth.LineTo(PointF(0, Height / 2));
      pth.ClosePath;
      Canvas.FillPath(pth, Opacity, Fill);
      Canvas.DrawPath(pth, Opacity, Stroke);
      pth.Free;
    end;
    nskNeedle:
    begin
      pth := TPathData.Create;
      pth.MoveTo(PointF(Width / 2, Height));
      pth.LineTo(PointF(0, Height - (Height / 5)));
      pth.LineTo(PointF((Width / 2), 0));
      pth.LineTo(PointF(Width, Height - (Height / 5)));
      pth.LineTo(PointF(Width / 2, Height));
      pth.ClosePath;
      Canvas.FillPath(pth, Opacity, Fill);
      Canvas.DrawPath(pth, Opacity, Stroke);
      pth.Free;
    end;
    nskEllipse:
    begin
      Canvas.FillEllipse(Boundsrect, Opacity, Fill);
      Canvas.DrawEllipse(Boundsrect, Opacity, Stroke);
    end;
  end;
end;

procedure TTMSFMXNeedleShape.SetKind(const Value: TTMSFMXNeedleShapeKind);
begin
  if FKind <> Value then
  begin
    FKind := Value;
    Repaint;
  end;
end;

{ TTMSFMXSectionShape }

constructor TTMSFMXSectionShape.Create(AOwner: TComponent);
begin
  inherited;
  Width := 100;
  Height := 100;
  FStartAngle := 0;
  FStopAngle := 360;
  FInnerMargin := 20;
  FOuterMargin := 0;
  FKind := sskEllipse;
end;

procedure TTMSFMXSectionShape.Painting;
var
  pth: TPathData;
  ct: TPointF;
  rdin, rdout: TPointF;
begin
  inherited;
  case Kind of
    sskEllipse:
    begin
      pth := TPathData.Create;
      ct.X := Width / 2;
      ct.Y := Height / 2;
      rdin.X := InnerMargin;
      rdin.Y := InnerMargin;
      rdout.X := (Width - OuterMargin) / 2;
      rdout.Y := (Height - OuterMargin) / 2;
      if InnerMargin > 0 then
        pth.AddArc(ct, rdin, StartAngle, StopAngle);

      pth.AddArc(ct, rdout, StartAngle + StopAngle, -StopAngle);
      pth.ClosePath;

      Canvas.FillPath(pth, Opacity, Fill);
      Canvas.DrawPath(pth, Opacity, Stroke);
      pth.Free;
    end;
    sskRectangle:
    begin
      Canvas.FillRect(BoundsRect, 0, 0, AllCorners, Opacity, Fill);
      Canvas.DrawRect(BoundsRect, 0, 0, AllCorners, Opacity, Stroke);
    end;
  end;
end;

procedure TTMSFMXSectionShape.SetInnerMargin(const Value: Integer);
begin
  if FInnerMargin <> Value then
  begin
    FInnerMargin := Value;
    Repaint;
  end;
end;

procedure TTMSFMXSectionShape.SetKind(const Value: TSectionShapeKind);
begin
  if FKind <> Value then
  begin
    FKind := Value;
    Repaint;
  end;
end;

procedure TTMSFMXSectionShape.SetOuterMargin(const Value: Integer);
begin
  if FOuterMargin <> Value then
  begin
    FOuterMargin := Value;
    Repaint;
  end;
end;

procedure TTMSFMXSectionShape.SetStartAngle(const Value: Integer);
begin
  if FStartAngle <> Value then
  begin
    FStartAngle := Value;
    Repaint;
  end;
end;

procedure TTMSFMXSectionShape.SetStopAngle(const Value: Integer);
begin
  if FStopAngle <> Value then
  begin
    FStopAngle := Value;
    Repaint;
  end;
end;

{ TTMSFMXGaugeSection }

constructor TTMSFMXSection.Create(Collection: TCollection);
begin
  inherited;
  FOwner := (Collection as TTMSFMXSections).FOwner;
  InitValues;
  UpdateSection;
end;

destructor TTMSFMXSection.Destroy;
begin
  inherited;
  UpdateSection;
end;

procedure TTMSFMXSection.InitValues;
begin
  FStartValue := 20;
  FEndValue := 60;
end;

procedure TTMSFMXSection.SetEndValue(const Value: Single);
begin
  if FEndValue <> Value then
  begin
    FEndValue := Value;
    UpdateSection;
  end;
end;

procedure TTMSFMXSection.SetStartValue(const Value: Single);
begin
  if FStartValue <> Value then
  begin
    FStartValue := Value;
    UpdateSection;
  end;
end;

function TTMSFMXSection.Shape: TTMSFMXSectionShape;
var
  innerelement: TControl;
begin
  Result := nil;
  innerelement := FOwner.GetInnerElement;
  if Assigned(innerelement) then
    Result := innerelement.FindStyleResource('FMXSection'+inttostr(Index)) as TTMSFMXSectionShape;
end;

procedure TTMSFMXSection.UpdateSection;
var
  obj: TControl;
begin
  if csDesigning in FOwner.ComponentState then
  begin
    obj := nil;
    if Assigned(FOwner.Scene) and Assigned(FOwner.Scene.GetStyleBook) and Assigned(FOwner.Scene.GetStyleBook.Root) then
    begin
      if FOwner.StyleLookup <> '' then
        obj := TControl(FOwner.Scene.GetStyleBook.FindStyleResource(FOwner.StyleLookup))
      else
        obj := TControl(FOwner.Scene.GetStyleBook.FindStyleResource(FOwner.GetClassStyleName));
    end;

    if Assigned(obj) then
      FOwner.UpdateSections(obj);

    FOwner.NeedStyleLookup;
    FOwner.Repaint;
  end
  else
    FOwner.UpdateSections(FOwner);
end;

{ TTMSFMXGaugeSections }

function TTMSFMXSections.Add: TTMSFMXSection;
begin
  result := TTMSFMXSection(inherited Add);
end;

constructor TTMSFMXSections.Create(AClass: TCollectionItemClass; AOwner: TTMSFMXGauge);
begin
  inherited Create(AClass);
  FOwner := AOwner;
end;




function TTMSFMXSections.GetItem(
  Index: Integer): TTMSFMXSection;
begin
  Result := TTMSFMXSection(inherited Items[Index]);
end;

function TTMSFMXSections.GetOwner: TPersistent;
begin
  Result := FOwner;
end;

function TTMSFMXSections.Insert(Index: Integer): TTMSFMXSection;
begin
  Result := TTMSFMXSection(inherited Insert(Index));
end;

procedure TTMSFMXSections.SetItem(Index: Integer;
  const Value: TTMSFMXSection);
begin
  inherited Items[Index] := Value;
end;

{ TTMSFMXGaugeSection }

constructor TTMSFMXGaugeSection.Create(Collection: TCollection);
begin
  inherited;
end;

destructor TTMSFMXGaugeSection.Destroy;
begin
  inherited;
end;

procedure TTMSFMXGaugeSection.InitValues;
begin
  inherited;
  FInnerMargin := 20;
  FOuterMargin := 0;
end;

procedure TTMSFMXGaugeSection.SetInnerMargin(const Value: Integer);
begin
  if FInnerMargin <> Value then
  begin
    FInnerMargin := Value;
    UpdateSection;
  end;
end;

procedure TTMSFMXGaugeSection.SetOuterMargin(const Value: Integer);
begin
  if FOuterMargin <> Value then
  begin
    FOuterMargin := Value;
    UpdateSection;
  end;
end;

{ TTMSFMXCustomGauge }

procedure TTMSFMXCustomGauge.UpdateLayout;
var
  el: TControl;
begin
  inherited;
  el := GetInnerElement;
  if Assigned(el) and (el is TTMSFMXPieShape) then
  begin
    (el as TTMSFMXPieShape).StartAngle := LayoutStartAngle;
    if LayoutStopAngle < LayoutStartAngle then
      (el as TTMSFMXPieShape).StopAngle := LayoutStopAngle + 360
    else
      (el as TTMSFMXPieShape).StopAngle := LayoutStopAngle;
  end;

  el := GetOuterElement;
  if Assigned(el) and (el is TTMSFMXPieShape) then
  begin
    (el as TTMSFMXPieShape).StartAngle := LayoutStartAngle;
    if LayoutStopAngle < LayoutStartAngle then
      (el as TTMSFMXPieShape).StopAngle := LayoutStopAngle + 360
    else
      (el as TTMSFMXPieShape).StopAngle := LayoutStopAngle
  end;
end;

procedure TTMSFMXCustomGauge.UpdateNeedle;
var
  needleobj: TFMXObject;
  needle: TTMSFMXNeedleShape;
  val: Single;
begin
  inherited;
  needleobj := FindStyleResource('needle');
  if Assigned(needleobj) and (needleobj is TTMSFMXNeedleShape) then
  begin
    needle := (needleobj as TTMSFMXNeedleShape);
    val := MaximumValue - MinimumValue;
    val := ((100 * (FCurrentValue - MinimumValue)) / val);
    val := ((GetDivisionsStopAngle - DivisionsStartAngle) * val) / 100;
    val := val + DivisionsStartAngle;
    needle.RotationAngle := -270 +  val;
  end;
end;

procedure TTMSFMXCustomGauge.ApplyExtraNeedle(InnerElement: TControl; AExtraNeedle: TTMSFMXExtraNeedle; ANeedleShape: TTMSFMXNeedleShape; LineDiv: TLine);
var
  val: Single;
begin
  inherited;
  if Assigned(AExtraNeedle) and Assigned(ANeedleShape) then
  begin
    val := MaximumValue - MinimumValue;
    val := ((100 * (AExtraNeedle.FCurrentValue - MinimumValue)) / val);
    val := ((GetDivisionsStopAngle - DivisionsStartAngle) * val) / 100;
    val := val + DivisionsStartAngle;
    ANeedleShape.RotationAngle := -270 +  val;
  end;
end;

procedure TTMSFMXCustomGauge.ApplyLineDivision(CenterX, CenterY: Single;
  iDiv: Integer; AValue: Single; lDiv, LineDiv: TLine);
var
  angle, st: Single;
  tx, ty: Single;
begin
  inherited;
  {$IFDEF DELPHIXE6_LVL}
  ldiv.Align := TAlignLayout.Scale;
  {$ELSE}
  ldiv.Align := TAlignLayout.alScale;
  {$ENDIF}
  angle := DivisionsStartAngle + ((GetDivisionsStopAngle - DivisionsStartAngle) / Divisions * iDiv);
  st := DegToRad(angle);

  tx := CenterX + (CenterX * Cos(st));
  ty := CenterY + (CenterY * Sin(st));

  ldiv.Position.X := tx;
  ldiv.Position.Y := ty;

  ldiv.RotationCenter.X := 0;
  ldiv.RotationCenter.Y := 0;
  ldiv.RotationAngle := -270 + angle;
end;

procedure TTMSFMXCustomGauge.ApplySection(InnerElement: TControl; ASection: TTMSFMXSection; ASectionShape: TTMSFMXSectionShape; LineDiv: TLine);
begin
  inherited;
  {$IFDEF DELPHIXE6_LVL}
  ASectionShape.Align := TAlignLayout.Scale;
  {$ELSE}
  ASectionShape.Align := TAlignLayout.alScale;
  {$ENDIF}
  ASectionShape.StartAngle := Round(Self.GetAngle(ASection.StartValue));
  ASectionShape.StopAngle := Round(Self.GetAngle(ASection.EndValue)) - ASectionShape.StartAngle;

  if (ASection is TTMSFMXGaugeSection) then
  begin
    ASectionShape.InnerMargin := (ASection as TTMSFMXGaugeSection).InnerMargin;
    ASectionShape.OuterMargin := (ASection as TTMSFMXGaugeSection).OuterMargin;
  end;
end;

procedure TTMSFMXCustomGauge.ApplySetPoint(InnerElement: TControl;
  ASetPoint: TTMSFMXSetPoint; ASetPointShape: TTMSFMXSetPointShape; LineDiv: TLine);
var
  st: Single;
  tx, ty: Single;
  cx, cy: Single;
  val: Single;
begin
  inherited;

  val := MaximumValue - MinimumValue;
  val := ((100 * (ASetPoint.FCurrentValue - MinimumValue)) / val);
  val := ((GetDivisionsStopAngle - DivisionsStartAngle) * val) / 100;
  val := val + DivisionsStartAngle;

  st := DegToRad(val);

  cx := InnerElement.Width / 2;
  cy := InnerElement.Height / 2;

  tx := cx + (cx * Cos(st));
  ty := cy + (cy * Sin(st));

  ASetPointShape.Position.X := tx - ASetPointShape.Width / 2;
  ASetPointShape.Position.Y := ty;

  ASetPointShape.RotationCenter.X := 0.5;
  ASetPointShape.RotationCenter.Y := 0;
  ASetPointShape.RotationAngle := -90 + val;
end;

procedure TTMSFMXCustomGauge.ApplySubLineDivision(CenterX, CenterY: Single;
  iDiv, iSubDiv: Integer; AValue: Single; lDiv, LineDiv: TLine);
var
  nextangle, stangle, angle, st: Single;
  tx, ty: Single;
begin
  inherited;
  {$IFDEF DELPHIXE6_LVL}
  ldiv.Align := TAlignLayout.Scale;
  {$ELSE}
  ldiv.Align := TAlignLayout.alScale;
  {$ENDIF}
  angle := DivisionsStartAngle + ((GetDivisionsStopAngle - DivisionsStartAngle) / Divisions * iDiv);
  nextangle := DivisionsStartAngle + ((GetDivisionsStopAngle - DivisionsStartAngle) / Divisions * (iDiv + 1));
  stangle := angle + ((nextangle - angle) / SubDivisions * iSubdiv);
  st := DegToRad(stangle);

  tx := CenterX + (CenterX * Cos(st));
  ty := CenterY + (CenterY * Sin(st));

  ldiv.RotationCenter.X := 0;
  ldiv.RotationCenter.Y := 0;
  ldiv.RotationAngle := -270 + stangle;
  ldiv.Position.X := tx;
  ldiv.Position.Y := ty;
end;

procedure TTMSFMXCustomGauge.ApplySubTextDivision(CenterX, CenterY: Single;
  iDiv, iSubDiv: Integer; AValue: Single; lDiv: TText; LineDiv: TLine);
var
  nextangle, stangle, st, angle: Single;
  tx, ty: Single;
begin
  inherited;
  {$IFDEF DELPHIXE6_LVL}
  ldiv.Align := TAlignLayout.Scale;
  {$ELSE}
  ldiv.Align := TAlignLayout.alScale;
  {$ENDIF}
  angle := DivisionsStartAngle + ((GetDivisionsStopAngle - DivisionsStartAngle) / Divisions * iDiv);
  nextangle := DivisionsStartAngle + ((GetDivisionsStopAngle - DivisionsStartAngle) / Divisions * (iDiv + 1));
  stangle := angle + ((nextangle - angle) / SubDivisions * iSubdiv);
  st := DegToRad(stangle);

  tx := CenterX + ((CenterX - linediv.Height - ldiv.Width / 2) * Cos(st));
  ty := CenterY + ((CenterY - linediv.Height - ldiv.Height / 2) * Sin(st));

  ldiv.Position.X := tx - ldiv.Width / 2;
  ldiv.Position.Y := ty - ldiv.Height / 2;
end;

procedure TTMSFMXCustomGauge.ApplyTextDivision(CenterX, CenterY: Single;
  iDiv: Integer; AValue: Single; lDiv: TText; LineDiv: TLine);
var
  angle, st: Single;
  tx, ty: Single;
begin
  inherited;
  {$IFDEF DELPHIXE6_LVL}
  ldiv.Align := TAlignLayout.Scale;
  {$ELSE}
  ldiv.Align := TAlignLayout.alScale;
  {$ENDIF}
  angle := DivisionsStartAngle + ((GetDivisionsStopAngle - DivisionsStartAngle) / Divisions * iDiv);
  st := DegToRad(angle);

  tx := CenterX + ((CenterX - linediv.Height - ldiv.Width / 2) * Cos(st));
  ty := CenterY + ((CenterY - linediv.Height - ldiv.Height / 2) * Sin(st));

  ldiv.Position.X := tx - ldiv.Width / 2;
  ldiv.Position.Y := ty - ldiv.Height / 2;
end;

constructor TTMSFMXCustomGauge.Create(AOwner: TComponent);
begin
  inherited;
  FDivisionsStartAngle := 135;
  FLayoutStopAngle := 360;
  FLayoutStartAngle := 0;
  FDivisionsStopAngle := 45;
end;

function TTMSFMXCustomGauge.FindStartAngle: Single;
begin
  Result := DivisionsStartAngle;
end;

function TTMSFMXCustomGauge.FindStopAngle: Single;
begin
  Result := GetDivisionsStopAngle;
end;

function TTMSFMXCustomGauge.GetAngle(AValue: Single): Single;
begin
  Result := ((GetDivisionsStopAngle - DivisionsStartAngle) / (MaximumValue - MinimumValue)) * (AValue - MinimumValue);
  Result := Result + DivisionsStartAngle;
end;

function TTMSFMXCustomGauge.GetDivisionsStopAngle: integer;
begin
  Result := FDivisionsStopAngle;
  if Result <= DivisionsStartAngle then
    Result := Result + 360;
end;

function TTMSFMXCustomGauge.GetNeedlePlaceHolder(
  AControl: TFMXObject): TFMXObject;
begin
  if Assigned(AControl) then
    Result := AControl.FindStyleResource('outercenterelement')
  else
    Result := GetOuterCenterElement;
end;

procedure TTMSFMXCustomGauge.SetDivisionsStartAngle(const Value: integer);
begin
  if FDivisionsStartAngle <> Value then
  begin
    FDivisionsStartAngle :=  Max(Value, LayoutStartAngle);
    FDivisionsStartAngle := Min(FDivisionsStartAngle, 360);
    UpdateDivisions;
    UpdateValues;
    UpdateNeedle;
  end;
end;

procedure TTMSFMXCustomGauge.SetDivisionsStopAngle(const Value: integer);
begin
  if FDivisionsStopAngle <> Value then
  begin
    FDivisionsStopAngle := Min(Value, LayoutStopAngle);
    FDivisionsStopAngle := Min(FDivisionsStopAngle, 360);
    UpdateDivisions;
    UpdateValues;
    UpdateNeedle;
  end;
end;

procedure TTMSFMXCustomGauge.SetLayoutStartAngle(const Value: integer);
begin
  if FLayoutStartAngle <> Value then
  begin
    FLayoutStartAngle := Value;
    FDivisionsStartAngle := Max(FDivisionsStartAngle, FLayoutStartAngle);
    UpdateDivisions;
    UpdateValues;
    UpdateLayout;
    UpdateNeedle;
  end;
end;

procedure TTMSFMXCustomGauge.SetLayoutStopAngle(const Value: integer);
begin
  if FLayoutStopAngle <> Value then
  begin
    FLayoutStopAngle := Value;
    if (FDivisionsStopAngle < FDivisionsStartAngle) and (LayoutStopAngle > LayoutStartAngle) then
      FDivisionsStopAngle := Min(FDivisionsStopAngle + 360, FLayoutStopAngle)
    else
      FDivisionsStopAngle := Min(FDivisionsStopAngle, FLayoutStopAngle);

    UpdateDivisions;
    UpdateValues;
    UpdateLayout;
    UpdateNeedle;
  end;
end;

{ TTMSFMXCustomLinearGauge }

procedure TTMSFMXCustomLinearGauge.AnimateUpdateDivisions;
begin
  inherited;
  UpdateDivisions;
end;

procedure TTMSFMXCustomLinearGauge.AnimateUpdateValues;
begin
  inherited;
  UpdateValues;
end;

procedure TTMSFMXCustomLinearGauge.ApplyExtraNeedle(InnerElement: TControl; AExtraNeedle: TTMSFMXExtraNeedle; ANeedleShape: TTMSFMXNeedleShape; LineDiv: TLine);
var
  val: Single;
  ew: Single;
begin
  inherited;
  ANeedleShape.BringToFront;
  if Assigned(innerelement) then
  begin
    ew := Width - InnerElement.Margins.Left - InnerElement.Margins.Right;

    val := ew / (MaximumValue - MinimumValue) * (AExtraNeedle.FCurrentValue - MinimumValue);
    ANeedleShape.Position.X := val - ANeedleShape.Width / 2;
  end;
end;

procedure TTMSFMXCustomLinearGauge.ApplyLineDivision(CenterX, CenterY: Single;
  iDiv: Integer; AValue: Single; lDiv, LineDiv: TLine);
var
  st, ew: Single;
  innerelement: TControl;
begin
  inherited;
  lDiv.SendToBack;
  innerelement := GetInnerElement;
  st := LineDiv.Stroke.Thickness;
  ew := Width - InnerElement.Margins.Left - InnerElement.Margins.Right - st;
  lDiv.Position.X := ew / Divisions * iDiv;
  case ValuePosition of
    vpDown: lDiv.Position.Y := innerelement.Height;
    vpUp: lDiv.Position.Y := - ldiv.Height;
  end;
end;

procedure TTMSFMXCustomLinearGauge.ApplySection(InnerElement: TControl; ASection: TTMSFMXSection;
  ASectionShape: TTMSFMXSectionShape; LineDiv: TLine);
var
  st: Single;
  startval: Single;
  endval: Single;
  ew: Single;
begin
  inherited;
  ASectionShape.Kind := sskRectangle;

  st := (Innerelement as TRectangle).Stroke.Thickness;

  ASectionShape.Height := InnerElement.Height - st * 2;
  ASectionShape.Position.Y := st;

  ew := Width - InnerElement.Margins.Left - InnerElement.Margins.Right - st * 2;

  startval := ew / (MaximumValue - MinimumValue) * (ASection.StartValue - MinimumValue);
  endval := ew / (MaximumValue - MinimumValue) * (ASection.EndValue - MinimumValue);

  ASectionShape.Position.X := Max(st, startval + st);
  ASectionShape.Width := Min(ew - ASectionShape.Position.X + st, endval - ASectionShape.Position.X + st);
end;

procedure TTMSFMXCustomLinearGauge.ApplySetPoint(InnerElement: TControl;
  ASetPoint: TTMSFMXSetPoint; ASetPointShape: TTMSFMXSetPointShape; LineDiv: TLine);
var
  val: Single;
  ew: Single;
begin
  inherited;
  ASetPointShape.BringToFront;
  if Assigned(innerelement) and assigned(linediv) then
  begin
    ew := Width - InnerElement.Margins.Left - InnerElement.Margins.Right;

    val := ew / (MaximumValue - MinimumValue) * (ASetPoint.FCurrentValue - MinimumValue);
    ASetPointShape.Position.X := val - ASetPointShape.Width / 2;
    case ValuePosition of
      vpDown:     ASetPointShape.Position.Y := InnerElement.Height;
      vpUp:     ASetPointShape.Position.Y := - ASetPointShape.Height;
    end;
  end;
end;

procedure TTMSFMXCustomLinearGauge.ApplyStyle;
var
  innerelement: TControl;
begin
  innerelement := GetInnerElement;
  if Assigned(innerelement) then
  begin
    {$IFDEF DELPHIXE6_LVL}
    case ValuePosition of
      vpDown: innerelement.Align := TAlignLayout.Top;
      vpUp: innerelement.Align := TAlignLayout.Bottom;
    end;
    {$ELSE}
    case ValuePosition of
      vpDown: innerelement.Align := TAlignLayout.alTop;
      vpUp: innerelement.Align := TAlignLayout.alBottom;
    end;
    {$ENDIF}
  end;

  inherited;
end;

procedure TTMSFMXCustomLinearGauge.ApplySubLineDivision(CenterX, CenterY: Single;
  iDiv, iSubDiv: Integer; AValue: Single; lDiv, LineDiv: TLine);
var
  st, ew: Single;
  innerelement: TControl;
  pos, posnext: Single;
begin
  inherited;
  lDiv.SendToBack;
  innerelement := GetInnerElement;

  st := LineDiv.Stroke.Thickness;

  ew := Width - InnerElement.Margins.Left - InnerElement.Margins.Right - st;
  pos := ew / Divisions * iDiv;
  posNext := ew / Divisions * (iDiv + 1);
  lDiv.Position.X := pos + (posNext - pos) / SubDivisions * iSubDiv;
  case ValuePosition of
    vpDown: lDiv.Position.Y := innerelement.Height;
    vpUp: lDiv.Position.Y := - ldiv.Height;
  end;
end;

procedure TTMSFMXCustomLinearGauge.ApplySubTextDivision(CenterX, CenterY: Single;
  iDiv, iSubDiv: Integer; AValue: Single; lDiv: TText; LineDiv: TLine);
var
  st, ew: Single;
  innerelement: TControl;
  pos, posnext: Single;
begin
  inherited;
  lDiv.SendToBack;
  innerelement := GetInnerElement;
  st := LineDiv.Stroke.Thickness;

  ew := Width - InnerElement.Margins.Left - InnerElement.Margins.Right - st;
  pos := ew / Divisions * iDiv;
  posNext := ew / Divisions * (iDiv + 1);
  lDiv.Position.X := (pos + (posNext - pos) / SubDivisions * iSubDiv) - lDiv.Width / 2;
  case ValuePosition of
    vpDown:   lDiv.Position.Y := innerelement.Height + LineDiv.Height;
    vpUp: lDiv.Position.Y := -LineDiv.Height - ldiv.Height;
  end;
end;

procedure TTMSFMXCustomLinearGauge.ApplyTextDivision(CenterX, CenterY: Single;
  iDiv: Integer; AValue: Single; lDiv: TText; LineDiv: TLine);
var
  st, ew: Single;
  innerelement: TControl;
begin
  inherited;
  lDiv.SendToBack;
  innerelement := GetInnerElement;

  st := LineDiv.Stroke.Thickness;
  ew := Width - InnerElement.Margins.Left - InnerElement.Margins.Right - st;
  lDiv.Position.X := (ew / Divisions * iDiv) - lDiv.Width / 2;
  case ValuePosition of
    vpDown:   lDiv.Position.Y := innerelement.Height + LineDiv.Height;
    vpUp: lDiv.Position.Y := -LineDiv.Height - ldiv.Height;
  end;
end;

procedure TTMSFMXCustomLinearGauge.AssignEvents;
var
  outerelement: TControl;
begin
  outerelement := GetOuterElement;
  if Assigned(outerelement) then
    outerelement.OnMouseUp := InnerElementMouseUp;
end;

constructor TTMSFMXCustomLinearGauge.Create(AOwner: TComponent);
begin
  inherited;
  Width := 310;
  Height := 60;
  FValuePosition := vpDown;
end;

function TTMSFMXCustomLinearGauge.GetNeedlePlaceHolder(
  AControl: TFMXObject): TFMXObject;
begin
  if Assigned(AControl) then
    Result := AControl.FindStyleResource('innerelement')
  else
    Result := GetOuterElement;
end;

procedure TTMSFMXCustomLinearGauge.UpdateNeedle;
var
  needleobj: TFMXObject;
  needle: TTMSFMXNeedleShape;
  val: Single;
  innerelement: TControl;
  ew: Single;
begin
  inherited;
  innerelement := GetInnerElement;

  if Assigned(innerelement) then
  begin
    ew := Width - InnerElement.Margins.Left - InnerElement.Margins.Right;

    needleobj := FindStyleResource('needle');
    if Assigned(needleobj) and (needleobj is TTMSFMXNeedleShape) then
    begin
      needle := (needleobj as TTMSFMXNeedleShape);
      val := ew / (MaximumValue - MinimumValue) * (FCurrentValue - MinimumValue);
      needle.Position.X := val - needle.Width / 2;
      needle.Position.Y := 2;
      needle.Height := Innerelement.Height - 4;
    end;
  end;
end;

function TTMSFMXCustomLinearGauge.XYToValue(X, Y: Single): TGaugeValue;
var
  innerelement: TControl;
  ew: Single;
  val: Single;
begin
  Result.State := False;
  innerelement := GetInnerElement;
  if Assigned(innerelement) then
  begin
    ew := Width - InnerElement.Margins.Left - InnerElement.Margins.Right;
    if (ew > 0) and (X >= innerelement.Position.X) and (X <= innerelement.Position.X + innerelement.Width) then
    begin
      X := X - innerelement.Position.X;
      val := MaximumValue - MinimumValue;
      Result.Value := val / ew * x;
      Result.State := True;
    end;
  end;
end;

{ TTMSFMXSetPoints }

function TTMSFMXSetPoints.Add: TTMSFMXSetPoint;
begin
  Result := TTMSFMXSetPoint(inherited Add);
end;

constructor TTMSFMXSetPoints.Create(AOwner: TTMSFMXGauge);
begin
  inherited Create(TTMSFMXSetPoint);
  FOwner := AOwner;
end;

function TTMSFMXSetPoints.GetItem(
  Index: Integer): TTMSFMXSetPoint;
begin
  Result := TTMSFMXSetPoint(inherited Items[Index]);
end;

function TTMSFMXSetPoints.GetOwner: TPersistent;
begin
  Result := FOwner;
end;

function TTMSFMXSetPoints.Insert(Index: Integer): TTMSFMXSetPoint;
begin
  Result := TTMSFMXSetPoint(inherited Insert(Index));
end;

procedure TTMSFMXSetPoints.SetItem(Index: Integer;
  const Value: TTMSFMXSetPoint);
begin
  inherited Items[Index] := Value;
end;

{ TTMSFMXSetPoint }

constructor TTMSFMXSetPoint.Create(Collection: TCollection);
begin
  inherited;
  FOwner := (Collection as TTMSFMXSetPoints).FOwner;
  FKind := spsTriangle;
  UpdateSetPoint;
end;

destructor TTMSFMXSetPoint.Destroy;
begin
  inherited;
  UpdateSetPoint;
end;

function TTMSFMXSetPoint.GetKind: TSetPointShape;
var
  s: TTMSFMXSetPointShape;
begin
  s := Shape;
  if Assigned(s) then
    Result := s.Kind
  else
    Result := FKind;
end;

function TTMSFMXSetPoint.GetValue: Single;
begin
  if FOwner.doanimation then
    Result := FCurrentValueTo
  else
    Result := FCurrentValue;
end;

procedure TTMSFMXSetPoint.SetKind(const Value: TSetPointShape);
var
  s: TTMSFMXSetPointShape;
begin
  FKind := Value;
  s := Shape;
  if Assigned(s) then
    Shape.Kind := Value;
end;

procedure TTMSFMXSetPoint.SetValue(const Value: Single);
var
  v: Double;
begin
  v := Value;
  v := Max(Min(v, FOwner.MaximumValue), FOwner.MinimumValue);
  if FOwner.Animation and not (csDesigning in FOwner.ComponentState) and not (csLoading in FOwner.ComponentState) then
  begin
    FOwner.doExtraAnimation := true;
    FdoAnim := True;
    FcurrentValueTo := v;
    FOwner.FExtraTimer.Enabled := True;
  end
  else
  begin
    fcurrentValue := v;
    FcurrentValueTo := v;
    UpdateSetPoint;
  end;
end;

function TTMSFMXSetPoint.Shape: TTMSFMXSetPointShape;
var
  innerelement: TControl;
begin
  Result := nil;
  innerelement := FOwner.GetInnerElement;
  if Assigned(innerelement) then
    Result := innerelement.FindStyleResource('FMXSetPoint'+inttostr(Index)) as TTMSFMXSetPointShape;
end;

procedure TTMSFMXSetPoint.UpdateSetPoint;
var
  obj: TControl;
begin
  if csDesigning in FOwner.ComponentState then
  begin
    obj := nil;
    if Assigned(FOwner.Scene) and Assigned(FOwner.Scene.GetStyleBook) and Assigned(FOwner.Scene.GetStyleBook.Root) then
    begin
      if FOwner.StyleLookup <> '' then
        obj := TControl(FOwner.Scene.GetStyleBook.FindStyleResource(FOwner.StyleLookup))
      else
        obj := TControl(FOwner.Scene.GetStyleBook.FindStyleResource(FOwner.GetClassStyleName));
    end;

    if Assigned(obj) then
      FOwner.UpdateSetPoints(obj);

    FOwner.NeedStyleLookup;
    FOwner.Repaint;
  end
  else
    FOwner.UpdateSetPoints(FOwner);
end;

{ TTMSFMXExtraNeedles }

function TTMSFMXExtraNeedles.Add: TTMSFMXExtraNeedle;
begin
  result := TTMSFMXExtraNeedle(inherited Add);
end;

constructor TTMSFMXExtraNeedles.Create(AOwner: TTMSFMXGauge);
begin
  inherited Create(TTMSFMXExtraNeedle);
  FOwner := AOwner;
end;

function TTMSFMXExtraNeedles.GetItem(
  Index: Integer): TTMSFMXExtraNeedle;
begin
  Result := TTMSFMXExtraNeedle(inherited Items[Index]);
end;

function TTMSFMXExtraNeedles.GetOwner: TPersistent;
begin
  Result := FOwner;
end;

function TTMSFMXExtraNeedles.Insert(Index: Integer): TTMSFMXExtraNeedle;
begin
  Result := TTMSFMXExtraNeedle(inherited Insert(Index));
end;

procedure TTMSFMXExtraNeedles.SetItem(Index: Integer;
  const Value: TTMSFMXExtraNeedle);
begin
  inherited Items[Index] := Value;
end;

{ TTMSFMXExtraNeedle }

constructor TTMSFMXExtraNeedle.Create(Collection: TCollection);
begin
  inherited;
  FOwner := (Collection as TTMSFMXExtraNeedles).FOwner;
  UpdateExtraNeedle;
end;

destructor TTMSFMXExtraNeedle.Destroy;
begin
  inherited;
  UpdateExtraNeedle;
end;

function TTMSFMXExtraNeedle.GetValue: Single;
begin
  if FOwner.doanimation then
    Result := FCurrentValueTo
  else
    Result := FCurrentValue;
end;

procedure TTMSFMXExtraNeedle.SetValue(const Value: Single);
var
  v: Double;
begin
  v := Value;
  v := Max(Min(v, FOwner.MaximumValue), FOwner.MinimumValue);
  if FOwner.Animation and not (csDesigning in FOwner.ComponentState) and not (csLoading in FOwner.ComponentState) then
  begin
    FOwner.doExtraAnimation := true;
    FdoAnim := True;
    FcurrentValueTo := v;
    FOwner.FExtraTimer.Enabled := True;
  end
  else
  begin
    fcurrentValue := v;
    FcurrentValueTo := v;
    UpdateExtraNeedle;
  end;
end;

function TTMSFMXExtraNeedle.Shape: TTMSFMXNeedleShape;
var
  innerelement: TFmxObject;
begin
  Result := nil;
  innerelement := FOwner.GetNeedlePlaceHolder(FOwner);
  if Assigned(innerelement) then
    Result := innerelement.FindStyleResource('FMXExtraNeedle'+inttostr(Index)) as TTMSFMXNeedleShape;
end;

procedure TTMSFMXExtraNeedle.UpdateExtraNeedle;
var
  obj: TControl;
begin
  if csDesigning in FOwner.ComponentState then
  begin
    obj := nil;
    if Assigned(FOwner.Scene) and Assigned(FOwner.Scene.GetStyleBook) and Assigned(FOwner.Scene.GetStyleBook.Root) then
    begin
      if FOwner.StyleLookup <> '' then
        obj := TControl(FOwner.Scene.GetStyleBook.FindStyleResource(FOwner.StyleLookup))
      else
        obj := TControl(FOwner.Scene.GetStyleBook.FindStyleResource(FOwner.GetClassStyleName));
    end;

    if Assigned(obj) then
      FOwner.UpdateExtraNeedles(obj);

    FOwner.NeedStyleLookup;
    FOwner.Repaint;
  end
  else
    FOwner.UpdateExtraNeedles(FOwner);
end;

{ TTMSFMXSetPointShape }

constructor TTMSFMXSetPointShape.Create(AOwner: TComponent);
begin
  inherited;
  Width := 15;
  Height := 15;
  FKind := spsTriangle;
end;

procedure TTMSFMXSetPointShape.Painting;
var
  pth: TPathData;
begin
  inherited;
  case Kind of
    spsTriangle:
    begin
      pth := TPathData.Create;
      pth.MoveTo(PointF(Width / 2, 0));
      pth.LineTo(PointF(Width, Height));
      pth.LineTo(PointF(0, Height));
      pth.ClosePath;
      Canvas.FillPath(pth, Opacity, Fill);
      Canvas.DrawPath(pth, Opacity, Stroke);
      pth.Free;
    end;
    spsRectangle:
    begin
      Canvas.FillRect(BoundsRect, 0, 0, AllCorners, Opacity, Fill);
      Canvas.DrawRect(BoundsRect, 0, 0, AllCorners, Opacity, Stroke);
    end;
    spsDiamond:
    begin
      pth := TPathData.Create;
      pth.MoveTo(PointF(Width / 2, 0));
      pth.LineTo(PointF(Width, Height / 2));
      pth.LineTo(PointF(Width / 2, Height));
      pth.LineTo(PointF(0, Height / 2));
      pth.ClosePath;
      Canvas.FillPath(pth, Opacity, Fill);
      Canvas.DrawPath(pth, Opacity, Stroke);
      pth.Free;
    end;
    spsEllipse:
    begin
      Canvas.FillEllipse(Boundsrect, Opacity, Fill);
      Canvas.DrawEllipse(Boundsrect, Opacity, Stroke);
    end;
  end;
end;

procedure TTMSFMXSetPointShape.SetKind(const Value: TSetPointShape);
begin
  if FKind <> Value then
  begin
    FKind := Value;
    Repaint;
  end;
end;

{ TTMSFMXJogMeter }

procedure TTMSFMXJogMeter.ApplyAdditionalStyle(AControl: TFMXObject);
var
  outerelement, innerelement: TShape;
  o: TFMXObject;
  t: TText;
  p: TGradientPoint;
begin
  inherited;
  if Assigned(AControl) then
  begin

    o := AControl.FindStyleResource('divisiontext');
    if Assigned(o) then
    begin
      t := o as TText;
      {$IFDEF DELPHIXE6_LVL}
      t.TextSettings.FontColor := TAlphaColorRec.White;
      t.TextSettings.Font.Style := [TFontStyle.fsBold];
      {$ELSE}
      t.Color := TAlphaColorRec.White;
      t.Font.Style := [TFontStyle.fsBold];
      {$ENDIF}
    end;

    o := AControl.FindStyleResource('subdivisiontext');
    if Assigned(o) then
    begin
      t := o as TText;
      {$IFDEF DELPHIXE6_LVL}
      t.TextSettings.FontColor := TAlphaColorRec.White;
      t.TextSettings.Font.Size := 9;
      {$ELSE}
      t.Color := TAlphaColorRec.White;
      t.Font.Size := 9;
      {$ENDIF}
    end;

    outerelement := AControl.FindStyleResource('outerelement') as TShape;
    innerelement := AControl.FindStyleResource('innerelement') as TShape;
    if Assigned(outerelement) and Assigned(innerelement) then
    begin
      {$IFDEF DELPHIXE6_LVL}
      outerelement.Fill.Kind := TBrushKind.Gradient;
      {$ELSE}
      outerelement.Fill.Kind := TBrushKind.bkGradient;
      {$ENDIF}
      outerelement.Fill.Gradient.StartPosition.X := 0;
      outerelement.Fill.Gradient.StartPosition.Y := 0.5;
      outerelement.Fill.Gradient.StopPosition.X := 1;
      outerelement.Fill.Gradient.StopPosition.Y := 0.5;

      outerelement.Fill.Gradient.Points.Clear;
      p := outerelement.Fill.Gradient.Points.Add as TGradientPoint;
      p.IntColor := MakeColor(claBlack, 200);
      p.Offset := 0;

      p := outerelement.Fill.Gradient.Points.Add as TGradientPoint;
      p.IntColor := MakeColor(claBlack, 0);
      p.Offset := 0.2;

      p := outerelement.Fill.Gradient.Points.Add as TGradientPoint;
      p.IntColor := MakeColor(claBlack, 0);
      p.Offset := 0.8;

      p := outerelement.Fill.Gradient.Points.Add as TGradientPoint;
      p.IntColor := MakeColor(claBlack, 200);
      p.Offset := 1;


      innerelement.Fill.Assign(outerelement.Fill);

    end;
  end;
end;

procedure TTMSFMXJogMeter.ApplyLineDivision(CenterX, CenterY: Single;
  iDiv: Integer; AValue: Single; lDiv, LineDiv: TLine);
var
  st, ew: Single;
  innerelement: TControl;
begin
  inherited;
  innerelement := GetInnerElement;

  st := LineDiv.Stroke.Thickness;

  ew := Width - InnerElement.Margins.Left - InnerElement.Margins.Right - st;
  lDiv.Position.X := (ew * AValue / Aperture);
end;

procedure TTMSFMXJogMeter.ApplySubLineDivision(CenterX, CenterY: Single; iDiv,
  iSubDiv: Integer; AValue: Single; lDiv, LineDiv: TLine);
var
  st, ew: Single;
  innerelement: TControl;
begin
  inherited;
  innerelement := GetInnerElement;
  st := LineDiv.Stroke.Thickness;

  ew := Width - InnerElement.Margins.Left - InnerElement.Margins.Right - st;
  lDiv.Position.X := (ew * AValue / Aperture);
end;

procedure TTMSFMXJogMeter.ApplySubTextDivision(CenterX, CenterY: Single; iDiv,
  iSubDiv: Integer; AValue: Single; lDiv: TText; LineDiv: TLine);
var
  st, ew: Single;
  innerelement: TControl;
begin
  inherited;
  innerelement := GetInnerElement;
  st := LineDiv.Stroke.Thickness;

  ew := Width - InnerElement.Margins.Left - InnerElement.Margins.Right - st;
  lDiv.Position.X := (ew * AValue / Aperture) - lDiv.Width / 2;
end;

procedure TTMSFMXJogMeter.ApplyTextDivision(CenterX, CenterY: Single;
  iDiv: Integer; AValue: Single; lDiv: TText; LineDiv: TLine);
var
  st, ew: Single;
  innerelement: TControl;
begin
  inherited;
  innerelement := GetInnerElement;
  st := LineDiv.Stroke.Thickness;

  ew := Width - InnerElement.Margins.Left - InnerElement.Margins.Right - st;
  lDiv.Position.X := (ew * AValue / Aperture) - lDiv.Width / 2;
end;

procedure TTMSFMXJogMeter.CleanUpLines;
var
  str: TStringList;
  iDiv, iSubDiv: Integer;
  lName, lDivName: String;
  innerElement: TControl;
  i: integer;
  obj: TFMXObject;
  l: TLine;
  bp, sp: integer;
begin
  sp := SubDivisions;
  bp := Trunc(Aperture / Divisions);

  str := TStringList.Create;
  for iDiv := 0 to bp do
  begin
    if ShowDivisions then
    begin
      lName := 'divisionline' + inttostr(iDiv);
      str.Add(UpperCase(lName));
    end;

    for iSubdiv := 1 to sp - 1 do
    begin
      if ShowSubDivisions then
      begin
        lDivName := 'subdivisionline'+inttostr(iDiv)+inttostr(iSubdiv);
        str.Add(UpperCase(lDivName));
      end;
    end;
  end;

  innerElement := GetInnerElement;
  if Assigned(innerElement) then
  begin
    for I := innerElement.ChildrenCount - 1 downto 0 do
    begin
      obj := innerElement.Children[i];
      if (obj is TLine) then
      begin
        l := obj as TLine;
        if str.IndexOf(UpperCase(l.Name)) = -1 then
        begin
        {$IFDEF DELPHI_LLVM}
        l.Release;
        {$ELSE}
        l.Free;
        {$ENDIF}
        end;
      end;
    end;
  end;

  str.Free;
end;

procedure TTMSFMXJogMeter.CleanUpValues;
var
  str: TStringList;
  iDiv, iSubDiv: Integer;
  lName, lDivName: String;
  innerElement: TControl;
  i: integer;
  obj: TFMXObject;
  l: TText;
  sp, bp: Integer;
begin
  sp := SubDivisions;
  bp := Trunc(Aperture / Divisions);

  str := TStringList.Create;
  for iDiv := 0 to bp do
  begin
    if ShowDivisionText then
    begin
      lName := 'divisiontext' + inttostr(iDiv);
      str.Add(UpperCase(lName));
    end;

    for iSubdiv := 1 to sp - 1 do
    begin
      if ShowSubDivisionText then
      begin
        lDivName := 'subdivisiontext'+inttostr(iDiv)+inttostr(iSubdiv);
        str.Add(UpperCase(lDivName));
      end;
    end;
  end;

  innerElement := GetInnerElement;
  if Assigned(innerElement) then
  begin
    for I := innerElement.ChildrenCount - 1 downto 0 do
    begin
      obj := innerElement.Children[i];
      if (obj is TText) then
      begin
        l := obj as TText;
        if str.IndexOf(UpperCase(l.Name)) = -1 then
        begin
        {$IFDEF DELPHI_LLVM}
        l.Release;
        {$ELSE}
        l.Free;
        {$ENDIF}
        end;
      end;
    end;
  end;

  str.Free;
end;

constructor TTMSFMXJogMeter.Create(AOwner: TComponent);
begin
  inherited;
  FAperture := 100;
  FValuePosition := vpDown;
  FStep := 1;
end;

procedure TTMSFMXJogMeter.SetAperture(const Value: Single);
begin
  if FAperture <> Value then
  begin
    FAperture := Value;
    UpdateDivisions;
    UpdateValues;
    UpdateNeedle;
  end;
end;

procedure TTMSFMXJogMeter.SetStep(const Value: Single);
begin
  if FStep <> Value then
  begin
    FStep := Value;
    UpdateDivisions;
    UpdateValues;
    UpdateNeedle;
  end;
end;

procedure TTMSFMXJogMeter.SetValue(const Value: Single);
var
  v: Double;
begin
  v := Value;
  if Animation and not (csDesigning in ComponentState) and not (csLoading in ComponentState) then
  begin
    doAnimation := true;
    FcurrentValueTo := v;
    FAnimTimer.Enabled := True;
  end
  else
  begin
    fcurrentValue := v;
    FcurrentValueTo := v;
    UpdateDivisions;
    UpdateValues;
    UpdateNeedle;
    if Assigned(OnValueChanged) then
      OnValueChanged(Self, Value);
  end;
end;

procedure TTMSFMXCustomLinearGauge.SetValuePosition(const Value: TTMSFMXValuePosition);
var
  innerelement: TControl;
begin
  if FValuePosition <> Value then
  begin
    FValuePosition := Value;
    innerelement := GetInnerElement;
    if Assigned(innerelement) then
    begin
      {$IFDEF DELPHIXE6_LVL}
      case ValuePosition of
        vpDown: innerelement.Align := TAlignLayout.Top;
        vpUp: innerelement.Align := TAlignLayout.Bottom;
      end;
      {$ELSE}
      case ValuePosition of
        vpDown: innerelement.Align := TAlignLayout.alTop;
        vpUp: innerelement.Align := TAlignLayout.alBottom;
      end;
      {$ENDIF}
    end;
    UpdateDivisions;
    UpdateValues;
  end;
end;

procedure TTMSFMXJogMeter.UpdateDivisions;
var
  innerElement: TControl;
  tdiv, tsubdiv, l, ldiv: TLine;
  iDiv, iSubdiv: Integer;
  cx, cy: Single;
  lName, ldivName: String;
  lExists, lsubexists: TLine;
  oVal: Single;
  sp, bp: Integer;
  minb: Single;
  oValNum: Single;
  oValFirst, oValNext: Single;
begin
  if Aperture = 0 then
    Exit;

  innerElement := GetInnerElement;
  tdiv := GetDivision;
  tsubdiv := GetSubDivision;

  sp := SubDivisions;
  bp := Trunc(Aperture / Divisions);
  minb := ((FCurrentValue / Step) - Aperture / 2) / Divisions;
  minb := -(((FCurrentValue / Step) - Aperture / 2) - Trunc(minb) * Divisions);

  if Assigned(innerElement) and Assigned(tdiv) and Assigned(tsubdiv) then
  begin
    cx := innerElement.Width / 2;
    cy := innerElement.Height / 2;

    for iDiv := 0 to bp do
    begin
      if ShowDivisions then
      begin
        lName := 'divisionline' + inttostr(iDiv);
        lExists := DoesDivisionLineExist(lName);

        if not Assigned(lExists) then
          l := TLine.Create(innerElement)
        else
          l := lExists;

        l.BeginUpdate;
        l.HitTest := False;
        {$IFDEF DELPHIXE6_LVL}
        l.LineType := TLineType.Left;
        {$ELSE}
        l.LineType := TLineType.ltLeft;
        {$ENDIF}
        l.Width := tdiv.Stroke.Thickness;
        l.Height := tdiv.Height;
        l.Stroke.Assign(tdiv.Stroke);

        l.StrokeThickness := tdiv.Stroke.Thickness;

        l.Fill.Assign(tdiv.Fill);
        l.Name := lName;

        l.Locked := True;
        innerElement.AddObject(l);

        oVal := Minb + (iDiv * Divisions);
        oValNum := ((FCurrentValue / Step) - Aperture / 2) + minb;
        oValNum := oValNum + iDiv * Divisions;
        oValNum := oValNum * Step;

        l.SendToBack;
        ApplyLineDivision(cx, cy, iDiv, oVal, l, tdiv);

        DoApplyDivisionLineStyle(Self, tdiv, l, iDiv, oValNum);

        l.EndUpdate;
      end;

      if iDiv = bp then
        Break;

      for iSubdiv := 1 to sp - 1 do
      begin
        if ShowSubDivisions then
        begin
          lDivName := 'subdivisionline'+inttostr(iDiv)+inttostr(iSubdiv);
          lsubexists := DoesDivisionLineExist(lDivName);
          if not Assigned(lsubexists) then
            ldiv := TLine.Create(innerElement)
          else
            ldiv := lsubexists;

          ldiv.BeginUpdate;
          ldiv.HitTest := False;
          {$IFDEF DELPHIXE6_LVL}
          ldiv.LineType := TLineType.Left;
          {$ELSE}
          ldiv.LineType := TLineType.ltLeft;
          {$ENDIF}
          ldiv.Width := tsubdiv.Stroke.Thickness;

          ldiv.Height := tsubdiv.Height;
          ldiv.Stroke.Assign(tsubdiv.Stroke);

          ldiv.StrokeThickness := tsubdiv.Stroke.Thickness;

          ldiv.Fill.Assign(tsubdiv.Fill);

          ldiv.Name := lDivName;

          ldiv.Locked := True;
          innerElement.AddObject(ldiv);

          oValFirst := Minb + (iDiv * Divisions);
          oValNext := Minb + ((iDiv + 1) * Divisions);

          oVal := Abs(oValNext) - Abs(oValFirst);
          oVal := oValFirst + (iSubDiv * oVal / SubDivisions);
          oValNum := oVal + ((FCurrentValue / Step) - Aperture / 2);

          ldiv.SendToBack;
          ApplySubLineDivision(cx, cy, iDiv, iSubDiv, oVal, ldiv, tsubdiv);

          DoApplySubDivisionLineStyle(Self, tsubdiv, ldiv, iDiv, iSubdiv, oValNum);

          ldiv.EndUpdate;
        end;
      end;
    end;

    tdiv.Visible := False;
    tsubdiv.Visible := False;
  end;

  CleanUpLines;
  AlignNeedle;
end;

procedure TTMSFMXJogMeter.UpdateNeedle;
var
  needleobj: TFMXObject;
  needle: TTMSFMXNeedleShape;
  innerelement: TControl;
  ew: Single;
begin
  innerelement := GetInnerElement;

  if Assigned(innerelement) then
  begin
    ew := Width - InnerElement.Margins.Left - InnerElement.Margins.Right;

    needleobj := FindStyleResource('needle');
    if Assigned(needleobj) and (needleobj is TTMSFMXNeedleShape) then
    begin
      needle := (needleobj as TTMSFMXNeedleShape);
      needle.Position.X := ew / 2 - needle.Width / 2;
    end;
  end;
end;

procedure TTMSFMXJogMeter.UpdateValues;
var
  innerElement: TControl;
  tdiv, tsubdiv, l, ldiv: TText;
  iDiv, iSubdiv: Integer;
  cx, cy: Single;
  lName, ldivName: String;
  lExists, lsubexists: TText;
  linediv, linesubdiv: TLine;
  rect: TRectF;
  oVal: Single;
  sp, bp: Integer;
  minb: Single;
  oValNum: Single;
  oValNext, oValFirst: Single;
begin
  if Aperture = 0 then
    Exit;

  innerElement := GetInnerElement;
  tdiv := GetDivisionText;
  tsubdiv := GetSubDivisionText;
  linediv := GetDivision;
  linesubdiv := GetSubDivision;

  sp := SubDivisions;
  bp := Trunc(Aperture / Divisions);
  minb := ((FCurrentValue / Step) - Aperture / 2) / Divisions;
  minb := -(((FCurrentValue / Step) - Aperture / 2) - Trunc(minb) * Divisions);

  if Assigned(innerElement) and Assigned(tdiv) and Assigned(tsubdiv) and Assigned(linediv) and Assigned(linesubdiv) then
  begin
    tdiv.Visible := False;
    tsubdiv.Visible := False;

    cx := innerElement.Width / 2;
    cy := innerElement.Height / 2;

    for iDiv := 0 to bp do
    begin
      if ShowDivisionText then
      begin

        lName := 'divisiontext' + inttostr(iDiv);
        lExists := DoesDivisionTextExist(lName);

        if not Assigned(lExists) then
          l := TText.Create(innerElement)
        else
          l := lExists;

        l.BeginUpdate;
        l.HitTest := True;
        l.OnClick := ValueClick;
        l.WordWrap := False;
        l.Color := tdiv.Color;
        l.Font.Assign(tdiv.Font);
        l.Name := lName;
        l.StyleName := lName;

        oVal := Minb + (iDiv * Divisions);
        oValNum := ((FCurrentValue / Step) - Aperture / 2) + minb;
        oValNum := oValNum + iDiv * Divisions;
        oValNum := oValNum * Step;
        l.Text := FormatFloat(DivisionFormat, oValNum);

        l.RotationAngle := tdiv.RotationAngle;
        l.RotationCenter.X := tdiv.RotationCenter.X;
        l.RotationCenter.Y := tdiv.RotationCenter.Y;

        DoApplyDivisionTextStyle(Self, tdiv, l, iDiv, oValNum);

        Canvas.Font.Assign(l.Font);
        rect := BoundsRect;
        {$IFDEF DELPHIXE6_LVL}
        Canvas.MeasureText(rect, l.Text, False, [], TTextAlign.Leading, TTextAlign.Leading);
        {$ELSE}
        Canvas.MeasureText(rect, l.Text, False, [], TTextAlign.taLeading, TTextAlign.taLeading);
        {$ENDIF}

        l.Width := rect.Width;
        l.Height := rect.Height;

        l.Locked := True;
        innerElement.AddObject(l);
        l.SendToBack;
        ApplyTextDivision(cx, cy, iDiv, oVal, l, linediv);

        l.Visible := l.Text <> '';

        l.EndUpdate;
      end;

      if iDiv = bp then
        Break;

      for iSubdiv := 1 to sp - 1 do
      begin
        if ShowSubDivisionText then
        begin
          lDivName := 'subdivisiontext'+inttostr(iDiv)+inttostr(iSubdiv);
          lsubexists := DoesDivisionTextExist(lDivName);
          if not Assigned(lsubexists) then
            ldiv := TText.Create(innerElement)
          else
            ldiv := lsubexists;

          ldiv.BeginUpdate;
          ldiv.HitTest := True;
          ldiv.OnClick := ValueClick;
          ldiv.WordWrap := False;
          ldiv.AutoSize := True;
          ldiv.Color := tsubdiv.Color;
          ldiv.Font.Assign(tsubdiv.Font);
          ldiv.Name := ldivName;
          ldiv.StyleName := ldivName;

          oValFirst := Minb + (iDiv * Divisions);
          oValNext := Minb + ((iDiv + 1) * Divisions);

          oVal := Abs(oValNext) - Abs(oValFirst);
          oVal := oValFirst + (iSubDiv * oVal / SubDivisions);
          oValNum := oVal + ((FCurrentValue / Step) - Aperture / 2);
          oValNum := oValNum * Step;
          ldiv.Text := FormatFloat(SubDivisionFormat, oValNum);

          ldiv.RotationAngle := tsubdiv.RotationAngle;
          ldiv.RotationCenter.X := tsubdiv.RotationCenter.X;
          ldiv.RotationCenter.Y := tsubdiv.RotationCenter.Y;

          DoApplySubDivisionTextStyle(Self, tsubdiv, ldiv, iDiv, iSubdiv, oValNum);

          Canvas.Font.Assign(ldiv.Font);
          rect := BoundsRect;
          {$IFDEF DELPHIXE6_LVL}
          Canvas.MeasureText(rect, ldiv.Text, False, [], TTextAlign.Leading, TTextAlign.Leading);
          {$ELSE}
          Canvas.MeasureText(rect, ldiv.Text, False, [], TTextAlign.taLeading, TTextAlign.taLeading);
          {$ENDIF}

          ldiv.Width := rect.Width;
          ldiv.Height := rect.Height;

          ldiv.Locked := True;
          innerElement.AddObject(ldiv);
          ldiv.SendToBack;
          ApplySubTextDivision(cx, cy, iDiv, iSubdiv, oVal, ldiv, linesubdiv);

          ldiv.Visible := ldiv.Text <> '';

          ldiv.EndUpdate;
        end;
      end;
    end;
  end;

  CleanUpValues;
  AlignNeedle;
end;

function TTMSFMXJogMeter.XYToValue(X, Y: Single): TGaugeValue;
var
  innerelement: TControl;
  ew: Single;
  val: Single;
begin
  Result.State := False;
  innerelement := GetInnerElement;
  if Assigned(innerelement) then
  begin
    ew := Width - InnerElement.Margins.Left - InnerElement.Margins.Right;
    if (ew > 0) and (X >= innerelement.Position.X) and (X <= innerelement.Position.X + innerelement.Width) then
    begin
      X := X - innerelement.Position.X - (ew / 2);
      val := X * Aperture / ew;
      Result.Value := (val + FCurrentValue) * Step;
      Result.State := True;
    end;
  end;
end;

{ TTMSFMXCompassGauge }

constructor TTMSFMXCompass.Create(AOwner: TComponent);
begin
  inherited;
  Divisions := 8;
  SubDivisions := 4;
  DivisionsStartAngle := 180;
  DivisionsStopAngle := 180;
  Width := 350;
  Height := 350;
  Animation := False;
  Value := 25;
end;

procedure TTMSFMXCompass.DoApplyDivisionLineStyle(Sender: TObject;
  AOriginalLine, ALine: TLine; DivisionUnit: Integer; Value: Single);
begin
  ALine.Height := 150;
  inherited;
end;

procedure TTMSFMXCompass.DoApplyDivisionTextStyle(Sender: TObject;
  AOriginalText, AText: TText; DivisionUnit: Integer; Value: Single);
begin
  if Value = 0 then
  begin
    AText.Text := 'W';
  end
  else if Value = 12.5 then
  begin
    AText.Text := 'NW'
  end
  else if Value = 37.5 then
  begin
    AText.Text := 'NO'
  end
  else if Value = 50 then
  begin
    AText.Text := 'O'
  end
  else if Value = 62.5 then
  begin
    AText.Text := 'ZO'
  end
  else if Value = 87.5 then
  begin
    AText.Text := 'ZW'
  end
  else if Value = 25 then
  begin
    AText.Text := 'N';
  end
  else if Value = 75 then
  begin
    AText.Text := 'Z'
  end
  else
    AText.Text := '';

  inherited;
end;

{ TTMSFMXClock }

constructor TTMSFMXClock.Create(AOwner: TComponent);
begin
  inherited;
  Divisions := 12;
  SubDivisions := 5;
  MaximumValue := 12;
  DivisionsStartAngle := 270;
  DivisionsStopAngle := 270;
  FShowSeconds := True;

  FTimer := TTimer.Create(Self);
  FTimer.OnTimer := DoTime;
end;

destructor TTMSFMXClock.Destroy;
begin
  FTimer.Free;
  inherited;
end;

procedure TTMSFMXClock.DoApplyDivisionTextStyle(Sender: TObject; AOriginalText,
  AText: TText; DivisionUnit: Integer; Value: Single);
begin
  if Value = 0 then
    AText.Text := ''
  else
    AText.Text := FloatToStr(Value);

  inherited;
end;

procedure TTMSFMXClock.DoTime(Sender: TObject);
begin
  UpdateTime;
end;

function TTMSFMXClock.GetActive: Boolean;
begin
  Result := FTimer.Enabled;
end;

function TTMSFMXClock.GetTime: TTime;
begin
  if Active then
    Result := Time
  else
    Result := FClockTime;
end;

procedure TTMSFMXClock.ApplyAdditionalStyle(AControl: TFmxObject);
var
  o: TFmxObject;
  t: TText;
begin
  inherited;
  o := AControl.FindStyleResource('divisiontext');
  if Assigned(o) then
  begin
    t := o as TText;
    {$IFDEF DELPHIXE6_LVL}
    t.TextSettings.FontColor := TAlphaColorRec.White;
    t.TextSettings.Font.Style := [TFontStyle.fsBold];
    t.TextSettings.Font.Size := 18;
    {$ELSE}
    t.Color := TAlphaColorRec.White;
    t.Font.Style := [TFontStyle.fsBold];
    t.Font.Size := 18;
    {$ENDIF}
  end;

  o := AControl.FindStyleResource('subdivisiontext');
  if Assigned(o) then
  begin
    t := o as TText;
    {$IFDEF DELPHIXE6_LVL}
    t.TextSettings.FontColor := TAlphaColorRec.White;
    t.TextSettings.Font.Size := 9;
    {$ELSE}
    t.Color := TAlphaColorRec.White;
    t.Font.Size := 9;
    {$ENDIF}
  end;
end;

procedure TTMSFMXClock.ApplyStyle;
begin
  inherited;
  UpdateTime;
end;

procedure TTMSFMXClock.SetActive(const Value: Boolean);
begin
  FTimer.Enabled := Value;
  if Value then
    UpdateTime;
end;

procedure TTMSFMXClock.SetInterval(const Value: Integer);
begin
  if FInterval <> VAlue then
  begin
    FInterval := Value;
    FTimer.Interval := Value;
  end;
end;

procedure TTMSFMXClock.SetOffset(const Value: Integer);
begin
  if FOffset <> Value then
  begin
    FOffset := Value;
    UpdateTime;
  end;
end;

procedure TTMSFMXClock.SetShowSeconds(const Value: Boolean);
var
  needlesecond: TRoundRect;
begin
  if FShowSeconds <> Value then
  begin
    FShowSeconds := Value;

    if FShowSeconds then
      FTimer.Interval := FInterval
    else
      FTimer.Interval := 60000;

    needlesecond := FindStyleResource('needlesecond') as TRoundRect;
    if Assigned(needlesecond) then
      needlesecond.Visible := FShowSeconds;
  end;
end;

procedure TTMSFMXClock.SetTime(const Value: TTime);
begin
  if FClockTime <> Value then
  begin
    FClockTime := Value;
    UpdateTime;
  end;
end;

procedure TTMSFMXClock.UpdateTime;
var
  needlehour, needleminute, needlesecond: TRoundRect;
  dt: TDateTime;
  ht, mt, st, mst: Word;
  h, m, s, ms: Single;
  sec: Single;
  valhour, val, valminute, valsecond: Single;
begin
  needlehour := FindStyleResource('needlehour') as TRoundRect;
  needleminute := FindStyleResource('needleminute') as TRoundRect;
  needlesecond := FindStyleResource('needlesecond') as TRoundRect;

  if assigned(needlehour) and Assigned(needleminute) and Assigned(needlesecond) then
  begin
    if Active then
      dt := Time
    else
      dt := ClockTime;

    dt := IncMinute(dt, Self.Offset);

    DecodeTime(dt, ht, mt, st, mst);
    val := MaximumValue - MinimumValue;

    if ht > 12 then
      ht := ht - 12;

    h := ht;
    m := mt;
    s := st;
    ms := mst;

    h := h + (m / 59);
    valhour := ((100 * (h - MinimumValue)) / val);
    valhour := ((GetDivisionsStopAngle - DivisionsStartAngle) * valhour) / 100;
    valhour := valhour + DivisionsStartAngle;
    needlehour.RotationAngle := -270 +  valhour;

    m := m + (s / 59);
    valminute := ((100 * ((12 / 60 * m) - MinimumValue)) / val);
    valminute := ((GetDivisionsStopAngle - DivisionsStartAngle) * valminute) / 100;
    valminute := valminute + DivisionsStartAngle;
    needleminute.RotationAngle := -270 +  valminute;

    needlesecond.Visible := ShowSeconds;
    if ShowSeconds then
    begin
      if Interval <= 500 then
        sec := s + (ms / 1000)
      else
        sec := s;

      valsecond := ((100 * ((12 / 60 * sec) - MinimumValue)) / val);
      valsecond := ((GetDivisionsStopAngle - DivisionsStartAngle) * valsecond) / 100;
      valsecond := valsecond + DivisionsStartAngle;
      needlesecond.RotationAngle := -270 +  valsecond;
    end;

    if Assigned(OnTimeChanged) then
      OnTimeChanged(Self);
  end;
end;

{ TTMSFMXRotarySwitch }

procedure TTMSFMXRotarySwitch.ApplyTextDivision(CenterX, CenterY: Single;
  iDiv: Integer; AValue: Single; lDiv: TText; LineDiv: TLine);
var
  angle, st: Single;
  tx, ty, off: Single;
  inner: TControl;
begin
  inherited;
  {$IFDEF DELPHIXE6_LVL}
  ldiv.Align := TAlignLayout.Scale;
  {$ELSE}
  ldiv.Align := TAlignLayout.alScale;
  {$ENDIF}
  angle := DivisionsStartAngle + ((GetDivisionsStopAngle - DivisionsStartAngle) / Divisions * iDiv);
  st := DegToRad(angle);

  inner := GetInnerElement;
  off := 0;
  if Assigned(inner) then
    off := lDiv.Height + inner.Margins.Left;

  tx := CenterX + ((CenterX + off + linediv.Width) * Cos(st));
  ty := CenterY + ((CenterY + off + linediv.Height) * Sin(st));

  ldiv.Position.X := tx - ldiv.Width / 2;
  ldiv.Position.Y := ty - ldiv.Height / 2;
end;

procedure TTMSFMXRotarySwitch.Changed;
begin
  if csDestroying in ComponentState then
    Exit;

  FDivisions := FPositions.Count - 1;
  FMaximumValue := FPositions.Count - 1;
  FMinimumValue := 0;
  UpdateValues;
  UpdateDivisions;
end;

constructor TTMSFMXRotarySwitch.Create(AOwner: TComponent);
begin
  inherited;
  FDivisionsStartAngle := 90;
  FDivisionsStopAngle := 0;
  FDivisions := 5;
  FSubDivisions := 0;
  FShowSubDivisions := False;
  FShowSubDivisionText := False;
  FMinimumValue := 0;
  FMaximumValue := 6;

  FPositions := TTMSFMXSwitchPositions.Create(Self);

  if (csDesigning in ComponentState) and not (csLoading in ComponentState) then
  begin
    FPositions.Add.Text := 'OFF';
    FPositions.Add.Text := 'Value 1';
    FPositions.Add.Text := 'Value 2';
    FPositions.Add.Text := 'Value 3';
    FPositions.Add.Text := 'Value 4';
    FPositions.Add.Text := 'ON';
  end;
end;

destructor TTMSFMXRotarySwitch.Destroy;
begin
  FPositions.Free;
  inherited;
end;

procedure TTMSFMXRotarySwitch.DoApplyDivisionTextStyle(Sender: TObject;
  AOriginalText, AText: TText; DivisionUnit: Integer; Value: Single);
begin
  if (Value >= 0) and (Value <= Positions.Count - 1) then
  begin
    AText.Text := Positions[Round(Value)].Text;
    if AText.Text = '' then
      AText.Text := floattostr(Value);
  end;
  inherited;
end;

function TTMSFMXRotarySwitch.GetPositionForShape(
  AShape: TFmxObject): TTMSFMXSwitchPosition;
var
  i: Integer;
begin
  Result := nil;
  for I := 0 to Positions.Count - 1 do
  begin
    if (Positions[I].TextShape = AShape) or (Positions[i].EllipseShape = AShape) then
    begin
      Result := Positions[I];
      Break;
    end;
  end;
end;

procedure TTMSFMXRotarySwitch.SetPositions(const Value: TTMSFMXSwitchPositions);
begin
  if FPositions <> Value then
  begin
    FPositions.Assign(Value);
    Changed;
  end;
end;

procedure TTMSFMXRotarySwitch.ValueClick(Sender: TObject);
var
  t: TFMXObject;
  p: TTMSFMXSwitchPosition;
begin
  if Assigned(Sender) then
  begin
    t := Sender as TFMXObject;
    if Assigned(OnValueClick) and Assigned(t) then
    begin
      p := GetPositionForShape(t);
      if Assigned(p) then
        OnValueClick(Self, p.Index);
    end;
  end;
end;

function TTMSFMXRotarySwitch.XYToValue(X, Y: Single): TGaugeValue;
begin
  Result := inherited;
  Result.Value := round(Result.Value);
  Value := Result.Value;
end;

procedure TTMSFMXRotarySwitch.ApplyLineDivision(CenterX, CenterY: Single;
  iDiv: Integer; AValue: Single; lDiv, LineDiv: TLine);
var
  angle, st: Single;
  tx, ty: Single;
  off: Single;
  inner: TControl;
begin
  inherited;
  {$IFDEF DELPHIXE6_LVL}
  ldiv.Align := TAlignLayout.Scale;
  {$ELSE}
  ldiv.Align := TAlignLayout.alScale;
  {$ENDIF}
  angle := DivisionsStartAngle + ((GetDivisionsStopAngle - DivisionsStartAngle) / Divisions * iDiv);
  st := DegToRad(angle);

  inner := GetInnerElement;
  off := 0;
  if Assigned(inner) then
    off := lDiv.Height + inner.Margins.Left;

  tx := CenterX + ((CenterX + off) * Cos(st));
  ty := CenterY + ((CenterY + off) * Sin(st));

  ldiv.Position.X := tx;
  ldiv.Position.Y := ty;

  ldiv.RotationCenter.X := 0;
  ldiv.RotationCenter.Y := 0;
  ldiv.RotationAngle := -270 + angle;
end;


{ TTMSFMXSwitchPosition }

constructor TTMSFMXSwitchPosition.Create(Collection: TCollection);
begin
  inherited;
  FOwner := (Collection as TTMSFMXSwitchPositions).FOwner;
  if Assigned(FOwner) then
    FOwner.Changed;
end;

destructor TTMSFMXSwitchPosition.Destroy;
begin
  inherited;
  if Assigned(FOwner) then
    FOwner.Changed;
end;

function TTMSFMXSwitchPosition.EllipseShape: TEllipse;
begin
  Result := FOwner.FindStyleResource('divisionellipse' + 'divisionline'+ inttostr(Index)) as TEllipse;
end;

procedure TTMSFMXSwitchPosition.SetText(const Value: string);
begin
  if FText <> Value then
  begin
    FText := Value;
    if Assigned(FOwner) then
      FOwner.Changed;
  end;
end;

function TTMSFMXSwitchPosition.TextShape: TText;
begin
  Result := FOwner.FindStyleResource('divisiontext'+ inttostr(Index)) as TText;
end;

{ TTMSFMXSwitchPositions }

function TTMSFMXSwitchPositions.Add: TTMSFMXSwitchPosition;
begin
  result := TTMSFMXSwitchPosition(inherited Add);
end;

constructor TTMSFMXSwitchPositions.Create(AOwner: TTMSFMXRotarySwitch);
begin
  inherited Create(TTMSFMXSwitchPosition);
  FOwner := AOwner;
end;

function TTMSFMXSwitchPositions.GetItem(
  Index: Integer): TTMSFMXSwitchPosition;
begin
  Result := TTMSFMXSwitchPosition(inherited Items[Index]);
end;

function TTMSFMXSwitchPositions.GetOwner: TPersistent;
begin
  Result := FOwner;
end;

function TTMSFMXSwitchPositions.Insert(Index: Integer): TTMSFMXSwitchPosition;
begin
  Result := TTMSFMXSwitchPosition(inherited Insert(Index));
end;

procedure TTMSFMXSwitchPositions.SetItem(Index: Integer;
  const Value: TTMSFMXSwitchPosition);
begin
  inherited Items[Index] := Value;
end;

{ TTMSFMXKnobSwitch }

procedure TTMSFMXKnobSwitch.ApplyAdditionalStyle(AControl: TFMXObject);
var
  o: TFmxObject;
  t: TText;
begin
  inherited;
  o := AControl.FindStyleResource('divisiontext');
  if Assigned(o) then
  begin
    t := o as TText;
    {$IFDEF DELPHIXE6_LVL}
    t.TextSettings.FontColor := TAlphaColorRec.White;
    t.TextSettings.Font.Style := [TFontStyle.fsBold];
    {$ELSE}
    t.Color := TAlphaColorRec.White;
    t.Font.Style := [TFontStyle.fsBold];
    {$ENDIF}
  end;
end;

constructor TTMSFMXKnobSwitch.Create(AOwner: TComponent);
var
  i: integer;
begin
  inherited;
  FEnableGlow := True;
  if (csDesigning in ComponentState) and not (csLoading in ComponentState) then
  begin
    FPositions.Clear;
    for I := 0 to 10 do
      FPositions.Add;
  end;
end;

procedure TTMSFMXKnobSwitch.DoApplyDivisionLineStyle(Sender: TObject;
  AOriginalLine, ALine: TLine; DivisionUnit: Integer; Value: Single);
var
  el: TEllipse;
  outerglow: TGlowEffect;
begin
  inherited;

  if ALine.ChildrenCount = 0 then
  begin
    el := TEllipse.Create(ALine);
    el.HitTest := True;
    el.OnClick := ValueClick;
    el.Height := 12;
    el.Width := 12;
    {$IFDEF DELPHIXE6_LVL}
    el.Align := TAlignLayout.Center;
    el.Fill.Kind := TBrushKind.Gradient;
    el.Fill.Gradient.Style := TGradientStyle.Radial;
    {$ELSE}
    el.Align := TAlignLayout.alCenter;
    el.Fill.Kind := TBrushKind.bkGradient;
    el.Fill.Gradient.Style := TGradientStyle.gsRadial;
    {$ENDIF}
    el.Fill.Gradient.StartPosition.X := 0;
    el.Fill.Gradient.StartPosition.Y := 0;
    el.Fill.Gradient.StopPosition.X := 1;
    el.Fill.Gradient.StopPosition.Y := 1;
    el.Name := 'divisionellipse'+ALine.Name;
    el.StyleName := el.Name;

    outerglow := TGlowEffect.Create(ALine);
    outerglow.GlowColor := MakeColor(172, 201, 211);
    outerglow.Opacity := 0.5;
    outerglow.Softness := 0.5;

    ALine.addobject(el);
    ALine.AddObject(outerglow);
  end;

  {$IFDEF DELPHIXE6_LVL}
  ALine.Stroke.Kind := TBrushKind.None;
  {$ELSE}
  ALine.Stroke.Kind := TBrushKind.bkNone;
  {$ENDIF}

  el := ALine.Children[0] as TEllipse;
  if Assigned(el) then
  begin
    outerglow := ALine.Children[1] as TGlowEffect;
    if Assigned(outerglow) then
      outerglow.Enabled := FEnableGlow;
    if Self.Value >= Value then
    begin
      el.Fill.Gradient.Color1 := claWhite;
      el.Fill.Gradient.Color := MakeColor(172,201,211);
    end
    else
    begin
      el.Fill.Gradient.Color1 := claBlack;
      el.Fill.Gradient.Color := claGray;
    end;
  end;
end;

procedure TTMSFMXKnobSwitch.EnableDisableGlow(Enable: Boolean);
begin
  FEnableGlow := Enable;
  UpdateDivisions;
end;

procedure TTMSFMXKnobSwitch.SetValue(const Value: Single);
begin
  inherited;
  UpdateDivisions;
end;

procedure TTMSFMXKnobSwitch.UpdateLayout;
var
  el: TControl;
begin
  inherited;
  el := FindStyleResource('backgroundellipse') as TControl;
  if Assigned(el) and (el is TTMSFMXPieShape) then
  begin
    (el as TTMSFMXPieShape).StartAngle := LayoutStartAngle;
    if LayoutStopAngle < LayoutStartAngle then
      (el as TTMSFMXPieShape).StopAngle := LayoutStopAngle + 360
    else
      (el as TTMSFMXPieShape).StopAngle := LayoutStopAngle;
  end;
end;

{ TTMSFMXPieShape }

constructor TTMSFMXPieShape.Create(AOwner: TComponent);
begin
  inherited;
  FStartAngle := 0;
  FStopAngle := 360;
end;

procedure TTMSFMXPieShape.Painting;
var
  pth: TPathData;
  p: TPie;
  r: TRectF;
begin
  inherited;
  r := BoundsRect;
  if (StopAngle - StartAngle) mod 360 = 0 then
  begin
    pth := TPathData.Create;
    pth.AddEllipse(r);
    Canvas.FillPath(pth, Opacity, Fill);
    Canvas.DrawPath(pth, Opacity, Stroke);
    pth.Free;
  end
  else
  begin
    p := TPie.Create(Self);
    p.Fill.Assign(Fill);
    p.Width := Width;
    p.Height := Height;
    p.Stroke.Assign(Stroke);
    p.StrokeCap := Stroke.Cap;
    p.StrokeDash := Stroke.Dash;
    p.StrokeJoin := Stroke.Join;
    p.StrokeThickness := Stroke.Thickness;
    p.StartAngle := StartAngle;
    p.EndAngle := StopAngle;
    p.PaintTo(Canvas, r, self);
    p.Free;
  end;
end;

procedure TTMSFMXPieShape.SetStartAngle(const Value: Integer);
begin
  FStartAngle := Value;
  Repaint;
end;

procedure TTMSFMXPieShape.SetStopAngle(const Value: Integer);
begin
  FStopAngle := Value;
  Repaint;
end;

initialization
  RegisterFmxClasses([TTMSFMXPieShape, TTMSFMXSetPointShape, TTMSFMXNeedleShape, TTMSFMXSectionShape]);

end.
