{**********************************************}
{  TTeeAnimate Component                       }
{  Copyright (c) 2001-2020 by Steema Software  }
{**********************************************}
unit TeeAnimate;
{$I TeeDefs.inc}

interface

uses
  {$IFDEF MSWINDOWS}
  Windows,
  {$ENDIF}

  {$IFDEF D17}
  System.Types, System.UITypes,
  {$ENDIF}

  {$IFDEF D18}
  System.Generics.Collections,
  {$ENDIF}

  {$IFDEF FMX}
  FMX.Types, FMX.Forms,
  {$ELSE}
  Graphics, Controls, Forms,
  {$ENDIF}

  {$IFDEF D6}
  Variants,
  {$ENDIF}

  Classes, SysUtils, TypInfo,

  {$IFDEF FMX}
  FMXTee.Procs, FMXTee.Canvas
  {$ELSE}
  TeeProcs, TeCanvas
  {$ENDIF}
  ;

type
  TTeeAnimation=class;

  TAnimationEndEvent=procedure(Sender:TTeeAnimation; var FreeAnimation:Boolean) of object;

  TAnimationState=(asStopped, asPlaying, asPaused);

  TAnimationTiming=(atLinear, atSine, atSquare, atExp, atCubic, atBack, atBounce, atElastic);
  TAnimationTimingStyle=(atIn, atOut, atInOut);

  TAnimationFrameEvent=procedure(Sender:TTeeAnimation; const Fraction:Single) of object;

  TTeeAnimate=class; { forward }

  TTeeAnimationClass=class of TTeeAnimation;

  // Base Animation abstract class.

  TTeeAnimation=class(TComponent)
  private
    FDuration   : Integer; // Milliseconds
    FEnabled    : Boolean;
    FInverted   : Boolean;
    FLoop       : Boolean;
    FParent     : TTeeAnimation;
    FStart      : Integer; // Milliseconds
    FTagObject  : TObject;
    FTitle      : String;
    FTiming     : TAnimationTiming;
    FTimingStyle: TAnimationTimingStyle;
    FTwoWay     : Boolean;

    FOnBegin    : TNotifyEvent;
    FOnEnd      : TAnimationEndEvent;
    FOnFrame    : TAnimationFrameEvent;
    FOnStop     : TNotifyEvent;

    Procedure DoNextFrame(const Fraction:Single);
    function GetAnimate:TTeeAnimate; {$IFDEF D10}inline;{$ENDIF}
    function GetStartFrame:Integer;
    procedure ReadStartFrame(Reader: TReader);
    procedure SetStartFrame(Value:Integer);
  protected
    IAnimate      : TTeeAnimate;
    ICurrentTime  : Cardinal;
    IPlaying      : TAnimationState;
    IParentSource : String;

    IDontFree     : Boolean;

    procedure Added(const AAnimation:TTeeAnimation); dynamic;
    procedure ChildDurationChanged(const Child:TTeeAnimation); dynamic;
    procedure DefineProperties(Filer: TFiler); override;
    function DurationFrames:Integer;
    function IsFolder:Boolean; dynamic;
    function IsSequence:Boolean; dynamic;
    function IsValidChild(const AClass:TTeeAnimationClass):Boolean; dynamic;
    Procedure NewNode; dynamic;
    Procedure NextFrame(const Fraction:Single); virtual;
    Function GetOwner:TPersistent; override;
    procedure ReadState(Reader: TReader); override;
    procedure Removed(const AAnimation:TTeeAnimation); dynamic;
    procedure SetAnimate(const Value:TTeeAnimate); virtual;
    procedure SetDuration(const Value:Integer); virtual;
    procedure SetParent(const Value:TTeeAnimation); virtual;

    function EndAnimation:Boolean; virtual;
    procedure FixupReferences(const AParentSource:String); virtual;
    procedure SetParentComponent(AParent: TComponent); override;
  public
    Constructor Create(AOwner: TComponent); override;
    Destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;
    class Function Description:String; virtual;
    function EditorClass: String; dynamic;
    function EditorName: String; dynamic;
    function GetParentComponent: TComponent; override;
    function HasParent: Boolean; override;
    Function Index:Integer;
    Function IsEnabled:Boolean; dynamic;
    class function IsValidOwner(const AObject:TObject):Boolean; virtual;
    class function IsValidSource(const ASource:TObject; IsObject:Boolean):Boolean; virtual;

    property Animate:TTeeAnimate read GetAnimate write SetAnimate;
    procedure Continue; virtual;

    Function EndFrame:Integer;
    function EndTime:Integer;

    Procedure Pause; virtual;
    Procedure Play; overload; virtual;
    Procedure Preview; dynamic;
    Procedure Stop; virtual;
    Procedure StoreValue; virtual;

    function TimingFunction(const Fraction:Single):Single;

    property Playing:TAnimationState read IPlaying;
    property StartFrame:Integer read GetStartFrame write SetStartFrame; // deprecated, see StartTime property
    property TagObject:TObject read FTagObject write FTagObject;
  published
    property Duration:Integer read FDuration write SetDuration default 1000; // Millisec
    property Enabled:Boolean read FEnabled write FEnabled default True;
    property Inverted:Boolean read FInverted write FInverted default False;
    property Loop:Boolean read FLoop write FLoop default False;
    property Parent:TTeeAnimation read FParent write SetParent;
    property StartTime:Integer read FStart write FStart default 0; // Millisec
    property Timing:TAnimationTiming read FTiming write FTiming default atLinear;
    property TimingStyle:TAnimationTimingStyle read FTimingStyle write FTimingStyle default atIn;
    property Title:String read FTitle write FTitle;
    property TwoWay:Boolean read FTwoWay write FTwoWay default False;

    property OnBegin:TNotifyEvent read FOnBegin write FOnBegin;
    property OnEnd:TAnimationEndEvent read FOnEnd write FOnEnd;
    property OnFrame:TAnimationFrameEvent read FOnFrame write FOnFrame;
    property OnStop:TNotifyEvent read FOnStop write FOnStop;
  end;

  // List of Animations:

  TAnimations=class({$IFDEF D18}TObjectList<TTeeAnimation>{$ELSE}TList{$ENDIF})
  private
    Function Get(Index:Integer):TTeeAnimation;
    Procedure Put(Index:Integer; Const Value:TTeeAnimation);
  protected
    IAnimate : TComponent;
  public
    {$IFDEF D18}
    Destructor Destroy; override;
    procedure Assign(const Value:TAnimations);
    {$ENDIF}

    Procedure Add(const Animation:TTeeAnimation);

    {$IFNDEF D6}
    procedure Assign(const Value:TAnimations);
    {$ENDIF}

    Procedure Clear; {$IFNDEF D18}override;{$ENDIF}

    procedure FreeAll;

    property Animate:TComponent read IAnimate write IAnimate;
    property Animation[Index:Integer]:TTeeAnimation read Get write Put; default;
  end;

  TTeeAnimateEvents=packed record
    Clear    : TNotifyEvent;
    Continue : TNotifyEvent;
    Frame    : TNotifyEvent;
    Stop     : TNotifyEvent;
    Pause    : TNotifyEvent;
    Play     : TNotifyEvent;
    DeletedShapes : TNotifyEvent;
  end;

  // Helper base class, contains a "link" property to a TCustomTeePanel:

  TCustomTeePanelLink=class(TComponent)
  private
    { Private declarations }
    FPanel : TCustomTeePanel;
    procedure SetPanel(const Value: TCustomTeePanel);
  protected
    { Protected declarations }
    procedure Notification( AComponent: TComponent;
                            Operation: TOperation); override;
  public
    { Public declarations }
    property Panel:TCustomTeePanel read FPanel write SetPanel;
  end;

  TAnimateEditorOptions=record
    HorizScale : Integer;
    VertScale  : Integer;
    Size       : TPoint;
    NodeListX  : Integer;
  end;

  // Multithreading class, used to do an infinite loop calling a "Timer" procedure
  // that will process each running Animation tasks:

  TTimerThread=class(TThread)
  private
    FTimerProc : TNotifyEvent;

    DoSynchronize : Boolean;

    procedure Timer;
  public
    Constructor Create(const AProc:TNotifyEvent);

    procedure Execute; override;
  end;

  TTimerEvent=packed record
    FMethod  : TMethod;
    FEnabled : TMethod;
    FIsEnabled : Boolean;
    FRepeat  : Boolean;
    FData    : Variant;
    FOldTick  : Int64; //Cardinal;
    FInterval : Cardinal;
    FSource  : TObject;
  end;

  PTimerEvent=^TTimerEvent;

  TTimerEnabled=function(const Sender:TTimerEvent):Boolean of object;

  // Global pseudo-Timer class.
  // Contains a list of "events" that should be processed at desired millisecond
  // intervals.
  // Animations will add their own "event" when playing, and remove it when stopped.

  TMultiTimer=class
  private
    IAnyNil      : Boolean;
    IThreadTimer : TTimerThread;

    IList : Array of PTimerEvent;

    procedure CreateTimer;
    procedure Delete(Index:Integer);
  protected
    procedure OnTimer(Sender:TObject);
  public
    Destructor Destroy; override;

    procedure AddEvent(AEvent:TNotifyEvent; AEnabled:TTimerEnabled=nil;
                               const Interval:Cardinal=1000; DoRepeat:Boolean=True;
                               Source:TObject=nil;
                               const Data:String='');
    procedure ChangeInterval(AEvent:TNotifyEvent; AInterval:Cardinal);
    procedure EnableEvent(AEvent:TNotifyEvent; AEnable:Boolean);
    function IndexOfEvent(AEvent:TNotifyEvent):Integer;
    procedure RemoveEvent(AEvent:TNotifyEvent);
    procedure TryToEnable;
  end;

  // Main component class to perform animations.
  // Contains a list of Animation objects, and methods to Play, Pause, Stop, etc.

  {$IFDEF D16}
  [ComponentPlatformsAttribute(pidWin32 or pidWin64 or pidOSX32 {$IFDEF D26}or pidOSX64{$ENDIF})]
  {$ENDIF}
  TTeeAnimate=class(TCustomTeePanelLink)
  private
    FAnimations : TAnimations;
    FLoop       : Boolean;
    FSpeed      : Integer; // Frames per second !
    FSpeedFactor: Double;
    FState      : TAnimationState;

    FOnClear    : TNotifyEvent;
    FOnContinue : TNotifyEvent;
    FOnDeleted  : TNotifyEvent;
    FOnFrame    : TNotifyEvent;
    FOnPause    : TNotifyEvent;
    FOnPlay     : TNotifyEvent;
    FOnStop     : TNotifyEvent;

    { Private declarations }

    procedure CallOnPlay;
    procedure CallOnStop;
    Function Get(Index:Integer):TTeeAnimation;
    function GetEndFrame:Integer;
    function GetEndTime:Integer;

    procedure InternalStop;
    function IsSpeedFactorStored:Boolean;
    Procedure Put(Index:Integer; Const Value:TTeeAnimation);
    procedure SetLoop(const Value: Boolean);
    procedure SetSpeed(const Value: Integer);

    Procedure OnTimer(Sender: TObject);
  protected
    IOnPlay,
    IOnStop,
    FOnDestroy : TNotifyEvent;

    IEditor : TAnimateEditorOptions;

    PlayByFrames : Boolean;

    { Protected declarations }
    Procedure GetChildren(Proc:TGetChildProc; Root:TComponent); override;
    function GetChildOwner: TComponent; override;

    class function GlobalTimer:TMultiTimer;

    Procedure RestoreEvents(Const AnimateEvents:TTeeAnimateEvents);
    Procedure SaveEvents(Var AnimateEvents:TTeeAnimateEvents);
  public
    { Public declarations }
    CurrentFrame : Integer;
    StartTime    : Cardinal;

    FreeOnStop   : Boolean; // Used by TMaker.TBlocks only

    Constructor Create(AOwner:TComponent); override;
    Destructor Destroy; override;

    Procedure Continue;
    function MillisecondsPerFrame:Single;
    Procedure NextFrame;
    Procedure Pause;
    function Paused:Boolean;
    Procedure Play(Wait:Boolean=False; SynchronizeMain:Boolean=True);
    function Playing:Boolean;
    Procedure Stop;

    property Animation[Index:Integer]:TTeeAnimation read Get write Put; default;

    property EndFrame:Integer read GetEndFrame;
    property EndTime:Integer read GetEndTime;

    property OnDeleteShapes:TNotifyEvent read FOnDeleted write FOnDeleted;
  published
    { Published declarations }
    property Animations:TAnimations read FAnimations;
    property Loop:Boolean read FLoop write SetLoop default False;
    property Speed:Integer read FSpeed write SetSpeed default 60; // Frames per second
    property SpeedFactor:Double read FSpeedFactor write FSpeedFactor stored IsSpeedFactorStored; // 1x
    property State:TAnimationState read FState;

    property OnClear:TNotifyEvent read FOnClear write FOnClear;
    property OnContinue:TNotifyEvent read FOnContinue write FOnContinue;
    property OnFrame:TNotifyEvent read FOnFrame write FOnFrame;
    property OnPause:TNotifyEvent read FOnPause write FOnPause;
    property OnPlay:TNotifyEvent read FOnPlay write FOnPlay;
    property OnStop:TNotifyEvent read FOnStop write FOnStop;
  end;

  // Generic animation class that knows how to "bind" to any published property
  // of a given "Instance".
  // To support sub-properties, this class will internally parse the PropertyName
  // string to obtain the top instance object, (ie: 'Legend.Font.Gradient.StartColor'
  // will be converted to a Gradient instance, and a 'StartColor' property)

  TPropertyAnimation=class(TTeeAnimation)
  private
    procedure ReadSpecial(Reader:TReader);
    procedure SetInst(const Value: TObject);
    procedure WriteSpecial(Writer:TWriter);
  protected
    FInst : TObject;
    FProp : String;

    FOnChange : TNotifyEvent;

    IRealInstance : TObject;
    IRealProperty : String;

    IsSpecial     : Integer;

    procedure DefineProperties(Filer: TFiler); override;
    procedure FindRealNames; virtual;

    procedure FixupReferences(const AParentSource:String); override;

    function HasProperty:Boolean;
    procedure Notification( AComponent: TComponent;
                            Operation: TOperation); override;

    procedure SetProp(const Value: String);
  public
    procedure Assign(Source:TPersistent); override;

    function EditorClass: String; override;
    function EditorName: String; override;
    class function Fixup(var AInstance:TObject; var AName:String):{$IFDEF D8}PPropInfo{$ELSE}PPropInfo{$ENDIF};

    Function IsEnabled:Boolean; override;

    property PropertyName:String read FProp write SetProp;
    property OnChange:TNotifyEvent read FOnChange write FOnChange;
  published
    property Instance:TObject read FInst write SetInst;
  end;

  TGetPropertyValueProc=function(AInstance:TObject; const AProperty:String):Variant of object;

  // Defines a "bind" to an instance and one of its published properties:

  TPropertyLink=class(TPersistent)
  private
    FInst : TObject;
    FProp : String;

    IOwner : TComponent;
    IRealInstance : TObject;
    IRealProperty : String;

    procedure FindRealNames; dynamic;
    procedure SetInst(const Value: TObject);
    procedure SetProp(const Value: String);
  public
    Constructor Create(AOwner:TComponent);
    procedure Assign(Source:TPersistent); override;
    procedure CheckInstancePrefix(const APrefix:String; const AInstance:TObject);

    function PropertyInfo:{$IFDEF D8}PPropInfo{$ELSE}PPropInfo{$ENDIF};
    function PropertyValue(AProc:TGetPropertyValueProc=nil):Variant; overload;
    function PropertyValue(const APropName:String):Variant; overload;
  published
    property Instance:TObject read FInst write SetInst;
    property PropertyName:String read FProp write SetProp;
  end;

  // Middle base class to represent "Start" and "End" values for a property:

  TRangePropertyAnimation=class(TPropertyAnimation)
  private
    FKeepEnd  : Boolean;
    FUseEnd   : Boolean;
    FUseStart : Boolean;
  public
    Constructor Create(AOwner:TComponent); override;

    procedure Assign(Source:TPersistent); override;
  published
    property KeepEndValue:Boolean read FKeepEnd write FKeepEnd default False;
    property UseEndValue:Boolean read FUseEnd write FUseEnd default True;
    property UseStartValue:Boolean read FUseStart write FUseStart default True;
  end;

  // Contains an XY array of points that define a "curve".
  // This "curve" function can be used by animations to perform the animated
  // steps following the curve instead of being "linear" (from A to B)

  TAnimationCurve=class(TPersistent)
  private
    FInverted : Boolean;
    ICurve    : TDoubleArray;

    procedure ReadCurve(Stream: TStream);
    procedure WriteCurve(Stream: TStream);
  protected
    procedure DefineProperties(Filer: TFiler); override;
  public
    X : TDoubleArray;
    Y : TDoubleArray;

    Destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;
    procedure Calculate(Duration:Integer);
    procedure Reset;
  published
    property Inverted:Boolean read FInverted write FInverted default False;
  end;

  TAnimationSetValue=procedure(Sender:TTeeAnimation; const Value:Double) of object;

  // Generic animation for any numeric (float or integer) property.
  // The target instance and property will be animated from a "StartValue" to
  // and "EndValue".

  TNumberAnimation=class(TRangePropertyAnimation)
  private
    FCurve      : TAnimationCurve;
    FEndLink    : TPropertyLink;
    FEndValue   : Double;
    FInitStart  : Boolean;
    FOnSetValue : TAnimationSetValue;
    FStartLink  : TPropertyLink;
    FStartValue : Double;
    FValue      : Double;

    OldValue    : Double;

    function GetCurve:TAnimationCurve;
    function GetEndLink:TPropertyLink;
    function GetEndValue:Double;
    function GetStartLink:TPropertyLink;
    function GetStartValue:Double;

    function HasCurve:Boolean;
    procedure SetCurve(const Value:TAnimationCurve);
    procedure SetEndLink(const Value:TPropertyLink);
    procedure SetStartLink(const Value:TPropertyLink);
  protected
    Function GetValue:Double; virtual;
    Procedure NewNode; override;
    Procedure NextFrame(const Fraction:Single); override;
    procedure SetDuration(const Value:Integer); override;
    Procedure SetValue(const AValue:Double); virtual;
  public
    Destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;

    function CurveFrameValue(Frame:Integer):Single;
    function EndAnimation:Boolean; override;
    function HasLink(const ALink:TPropertyLink):Boolean;
    Function IsEnabled:Boolean; override;
    Procedure Play; override;
    Procedure StoreValue; override;

    property Value:Double read GetValue write SetValue;
  published
    property Curve:TAnimationCurve read GetCurve write SetCurve stored HasCurve;
    property EndLink:TPropertyLink read GetEndLink write SetEndLink;
    property EndValue:Double read GetEndValue write FEndValue;
    property InitStart:Boolean read FInitStart write FInitStart default False;
    property PropertyName;
    property StartLink:TPropertyLink read GetStartLink write SetStartLink;
    property StartValue:Double read GetStartValue write FStartValue;

    property OnSetValue:TAnimationSetValue read FOnSetValue write FOnSetValue;
  end;

  TColorMethod=(cmRGB, cmHLS);

  // Generic animation for any TColor property.
  // The property will be animated from a "StartColor" to an "EndColor",
  // using RGB or HLS interpolation.

  TColorsAnimation=class(TNumberAnimation)
  private
    FMethod : TColorMethod;

    function GetEnd:TColor;
    function GetStart:TColor;
    procedure SetEnd(const Value:TColor);
    procedure SetStart(const Value:TColor);
  protected
    Procedure NextFrame(const Fraction:Single); override;
  public
    property EndColor:TColor read GetEnd write SetEnd;
    property StartColor:TColor read GetStart write SetStart;
  published
    property Method:TColorMethod read FMethod write FMethod default cmRGB;
    property PropertyName;
  end;

  // Generic animation for boolean properties.

  TToggleAnimation=class(TPropertyAnimation)
  private
    FEvery : Integer;

    function GetValue: Boolean;
    procedure SetValue(const Value: Boolean);
  protected
    Procedure NextFrame(const Fraction:Single); override;
  public
    Constructor Create(AOwner:TComponent); override;

    property Value:Boolean read GetValue write SetValue;
  published
    property Every:Integer read FEvery write FEvery default 5;
    property PropertyName;
  end;

  TSinusAnimation=class(TNumberAnimation)
  protected
    procedure NextFrame(const Fraction:Single); override;
  end;

  TPlaySoundAnimation=class(TTeeAnimation)
  private
    FFile : String;
    FSync : Boolean;
  protected
    function CheckSoundLibrary(const AFile:String):String;
    procedure SetFile(const Value: String); virtual;
  public
    procedure Assign(Source:TPersistent); override;

    class procedure ChangeVolume(const Value:Integer);
    Function IsEnabled:Boolean; override;
    Procedure Play; overload; override;
    class procedure Play(const AFileName:String; Synchronous:Boolean=True); overload;
    class procedure SetGlobalPath(const Value: String);
  published
    property FileName:String read FFile write SetFile;
    property Synchronous:Boolean read FSync write FSync default False;
  end;

var
  TeeAnimationClasses : TStringList;  // Due to BCB QC115377, it can't be a TList<TTeeAnimationClass>

Procedure TeeRegisterAnimation(const Animation:TTeeAnimationClass);

implementation

Uses
  {$IFDEF FMX}
  FMXTee.Filters,
  {$ELSE}
  TeeFilters, 
  {$IFNDEF LINUX}
  MMSystem,
  {$ENDIF}
  {$ENDIF}

  {$IFDEF D18}
  Rtti,
  {$ENDIF}
  
  Math;

var
  ITimer : TMultiTimer=nil;

Constructor TTimerThread.Create(const AProc:TNotifyEvent);
begin
  inherited Create(False);
  FreeOnTerminate:=True;

  DoSynchronize:=True;

  {$IFDEF FMX}
  {$IFDEF MSWINDOWS}
  Priority:=TThreadPriority.tpIdle;
  {$ENDIF}
  {$ELSE}
  Priority:=tpIdle;
  {$ENDIF}

  FTimerProc:=AProc;
end;

procedure TTimerThread.Timer;
begin
  FTimerProc(Self);
end;

procedure TTimerThread.Execute;
begin
  while not Terminated do
  begin
    if DoSynchronize then
       Synchronize(Timer)
    else
       Timer;

    if Application.Terminated then
    begin
      Terminate;
      break;
    end;

    // Problem in Tokyo+Win10? A sleep seems to be necessary, otherwise
    // threads get "stuck"
    Sleep(1);
  end;
end;

procedure TMultiTimer.Delete(Index:Integer);
var tt,
    tmpCount : Integer;
begin
  tmpCount:=Length(IList);

  for tt:=Index to tmpCount-2 do
      IList[tt]:=IList[tt+1];

  SetLength(IList,tmpCount-1);
end;

{$IFDEF MSWINDOWS}
var
  IFrequency : Int64;
{$ENDIF}

function AnimateTick:Int64;
begin
  {$IFDEF MSWINDOWS}
  if (IFrequency>0) and QueryPerformanceCounter(result) then
    result:=(result div IFrequency)
  else
  {$ENDIF}
    result:=Round(TeeTickCount);
end;

procedure TMultiTimer.OnTimer(Sender:TObject);
var t : Integer;
    tmp : Int64;
    tmpDif : Int64;
    p : PTimerEvent;
begin
  if not Assigned(IThreadTimer) then
     Exit;

  tmp:=AnimateTick;

  for t:=0 to Length(IList)-1 do
  begin
    p:=IList[t];

    if Assigned(p) and p^.FIsEnabled then
    begin
      tmpDif:=(tmp div p^.FInterval);

      if tmpDif > p^.FOldTick then
      begin
        p^.FOldTick:=tmpDif;

        TNotifyEvent(p^.FMethod)(nil);

        // Just in case, (p can be nil) :
        p:=IList[t];

        if Assigned(p) and (not p^.FRepeat) then
           p^.FIsEnabled:=False;
      end;
    end;
  end;

  if IAnyNil then
  begin
    t:=0;
    while t<Length(IList) do
    if IList[t]=nil then
       Delete(t)
    else
       Inc(t);

    IAnyNil:=False;

    TryToEnable;
  end;
end;

Destructor TMultiTimer.Destroy;
var t : Integer;
begin
  for t:=0 to Length(IList)-1 do
      if Assigned(IList[t]) then
         Dispose(IList[t]);

  IList:=nil;
  inherited;
end;

procedure TMultiTimer.EnableEvent(AEvent:TNotifyEvent; AEnable:Boolean);
var tmp : Integer;
    tmpEvent : PTimerEvent;
begin
  tmp:=IndexOfEvent(AEvent);

  if tmp<>-1 then
  begin
    tmpEvent:=IList[tmp];

    if Assigned(tmpEvent) then
    begin
      tmpEvent^.FIsEnabled:=AEnable;

      TryToEnable;
    end;
  end;
end;

procedure TMultiTimer.ChangeInterval(AEvent:TNotifyEvent; AInterval:Cardinal);
var tmp : Integer;
    tmpEvent : PTimerEvent;
begin
  tmp:=IndexOfEvent(AEvent);

  if tmp<>-1 then
  begin
    tmpEvent:=IList[tmp];

    if Assigned(tmpEvent) then
    begin
      tmpEvent^.FInterval:=AInterval;
      tmpEvent^.FOldTick:=0;
    end;
  end;
end;

function TMultiTimer.IndexOfEvent(AEvent:TNotifyEvent):Integer;
var t   : Integer;
    tmp : PTimerEvent;
begin
  result:=-1;

  for t:=0 to Length(IList)-1 do
  begin
    tmp:=IList[t];

    if Assigned(tmp) then
    with tmp^ do
       if (TMethod(AEvent).Code=FMethod.Code) and
          (TMethod(AEvent).Data=FMethod.Data) then
       begin
         result:=t;
         break;
       end;
  end;
end;

procedure TMultiTimer.CreateTimer;
begin
  if not Assigned(IThreadTimer) then
     IThreadTimer:=TTimerThread.Create(OnTimer)
end;

procedure TMultiTimer.TryToEnable;
var tmpEnabled : Boolean;
    tmpP : PTimerEvent;
    e    : TTimerEnabled;
    t    : Integer;
begin
  tmpEnabled:=False;

  for t:=0 to Length(IList)-1 do
  begin
    tmpP:=IList[t];

    if Assigned(tmpP) and tmpP^.FIsEnabled then
    begin
      e:=TTimerEnabled(tmpP^.FEnabled);

      if (not Assigned(e)) or e(tmpP^) then
      begin
        tmpEnabled:=True;
        break;
      end;
    end;
  end;

  if tmpEnabled then
     CreateTimer
  else
  if Assigned(IThreadTimer) then
  begin
    IThreadTimer.Terminate;
    IThreadTimer:=nil;
  end;
end;

procedure TMultiTimer.RemoveEvent(AEvent:TNotifyEvent);
var tmp : Integer;
begin
  tmp:=IndexOfEvent(AEvent);

  if tmp<>-1 then
  begin
    Dispose(IList[tmp]);

    Delete(tmp);
    TryToEnable;
  end;
end;

procedure TMultiTimer.AddEvent(AEvent:TNotifyEvent; AEnabled:TTimerEnabled=nil;
                               const Interval:Cardinal=1000; DoRepeat:Boolean=True;
                               Source:TObject=nil;
                               const Data:String='');
var tmp : PTimerEvent;
    tmpCount : Integer;
begin
  if IndexOfEvent(AEvent)=-1 then
  begin
    tmp:=New(PTimerEvent);

    with tmp^.FMethod do
    begin
      Code := TMethod(AEvent).Code;
      Data := TMethod(AEvent).Data;
    end;

    tmp^.FRepeat:=DoRepeat;
    tmp^.FData:=Data;
    tmp^.FInterval:=Interval;
    tmp^.FSource:=Source;

    if Assigned(AEnabled) then
    begin
      with tmp^.FEnabled do
      begin
        Code := TMethod(AEnabled).Code;
        Data := TMethod(AEnabled).Data;
      end;

      tmp^.FIsEnabled:=AEnabled(tmp^);
    end
    else
    begin
      tmp^.FIsEnabled:=True;
      tmp^.FEnabled.Code:=nil;
    end;

    tmp^.FOldTick:=0;

    // Add to list
    tmpCount:=Length(IList);
    SetLength(IList,tmpCount+1);
    IList[tmpCount]:=tmp;

    if tmp^.FIsEnabled then
       CreateTimer;
  end;
end;

{ TTeeAnimate }

Constructor TTeeAnimate.Create(AOwner: TComponent);
begin
  inherited;

  FAnimations:=TAnimations.Create{$IFDEF D18}(False){$ENDIF};
  FAnimations.IAnimate:=Self;

  FSpeed:=60; // Maximum desired number of frames per second
  FSpeedFactor:=1;  // 1x 
end;

Destructor TTeeAnimate.Destroy;
begin
  if Assigned(FOnDestroy) then
     FOnDestroy(Self);

  // First Stop all animations, then free them:
  Stop;

  FAnimations.Free;

  inherited;
end;

class function TTeeAnimate.GlobalTimer:TMultiTimer;
begin
  result:=ITimer;
end;

// Returns the biggest frame number
// (The longest StartFrame + Duration of all Enabled actions)
function TTeeAnimate.GetEndFrame: Integer;
var t : Integer;
begin
  result:=0;

  for t:=0 to Animations.Count-1 do
  with Animations[t] do
       if IsEnabled then
          result:=Max(result,EndFrame);
end;

function TTeeAnimate.GetEndTime:Integer;
var t : Integer;
begin
  result:=0;

  for t:=0 to Animations.Count-1 do
  with Animations[t] do
       if IsEnabled then
          result:=Max(result,EndTime);
end;

function TTeeAnimate.Playing:Boolean;
begin
  result:=FState=asPlaying;
end;

function TTeeAnimate.Paused:Boolean;
begin
  result:=FState=asPaused;
end;

procedure TTeeAnimate.Pause;  // pause playing
var t: Integer;
begin
  ITimer.EnableEvent(OnTimer,False);

  for t:=0 to Animations.Count-1 do
      Animations[t].Pause;

  FState:=asPaused;

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

procedure TTeeAnimate.Continue; // resumes playing
var t: Integer;
begin
  for t:=0 to Animations.Count-1 do
      Animations[t].Continue;

  FState:=asPlaying;

  if Assigned(FOnContinue) then
     FOnContinue(Self);

  ITimer.EnableEvent(OnTimer,True);
end;

procedure TTeeAnimate.Play(Wait:Boolean=False; SynchronizeMain:Boolean=True);  // start playing

  procedure CheckTimer;
  begin
    ITimer.CreateTimer;
    ITimer.IThreadTimer.DoSynchronize:=SynchronizeMain;
  end;

var t : Integer;
begin
  if FState=asPaused then
     Continue
  else
  if FState<>asPlaying then
  begin
    CurrentFrame:=-1;
    StartTime:=AnimateTick;

    CheckTimer;
    
    ITimer.AddEvent(OnTimer,nil,Trunc(MillisecondsPerFrame));

    if Animations.Count>0 then
    begin
      for t:=0 to Animations.Count-1 do
      with Animations[t] do
           if IsEnabled then
              StoreValue;

      FState:=asPlaying;

      CallOnPlay;

      CheckTimer;

      ITimer.TryToEnable;

      if Wait then
      begin
        CheckTimer;

        // This is no longer necessary with the CheckTimer calls above:
        // Synchronize should be called at least once, even our
        // SynchronizeMain=False. Otherwise the Thread never executes.
        // ITimer.IThreadTimer.Synchronize(Application.ProcessMessages);

        while Playing do
        begin
          if SynchronizeMain then
             Application.ProcessMessages;

          if Application.Terminated then
             Stop;

          if Assigned(ITimer) and Assigned(ITimer.IThreadTimer) and
             ITimer.IThreadTimer.Terminated then
                break;
        end;

        if Assigned(ITimer) and Assigned(ITimer.IThreadTimer) then
           ITimer.IThreadTimer.DoSynchronize:=True;
      end;
    end;
  end;
end;

Procedure TTeeAnimate.NextFrame;  // process one single frame
begin
  if FState<>asStopped then
  begin
    // process next frame
    OnTimer(Self);

    // just in case it was the last frame and the Loop = True, do pause
    if FState<>asPaused then Pause;
  end;
end;

// Every frame is processed here, when the Timer triggers
Procedure TTeeAnimate.OnTimer(Sender: TObject);
var t : Integer;
    tmpDelta,
    Delta : Integer;
    tmpTime : Int64;
    tmp : Cardinal;
    tmpFraction : Single;
begin
  Inc(CurrentFrame);

  if CurrentFrame=0 then // first frame
     Delta:=0
  else
  begin
    if PlayByFrames then
       Delta:=Round(CurrentFrame*MillisecondsPerFrame*SpeedFactor)
    else
    begin
      tmpTime:=AnimateTick; // Avoid overflow, using Int64 instead of Cardinal
      tmp:=tmpTime-StartTime;
      Delta:=Round(tmp*SpeedFactor);
    end;
  end;

  for t:=0 to Animations.Count-1 do
  with Animations[t] do
  if IsEnabled then
  begin
    if Loop then
       tmpDelta:=Delta mod Duration
    else
       tmpDelta:=Delta;

    if IPlaying=asStopped then
    begin
      if (tmpDelta>=StartTime) and (tmpDelta<EndTime) then
         Play
    end
    else
    if IPlaying=asPlaying then
    begin
      if tmpDelta>EndTime then
      begin
        if not TwoWay then
           if Inverted then
              DoNextFrame(0)
           else
              DoNextFrame(1);

        Stop;

        if Loop then
           Play;
      end
      else
      begin
        tmpFraction:=(tmpDelta-StartTime)/FDuration;

        if TwoWay then
           if tmpFraction>0.5 then
              tmpFraction:=1-2*(tmpFraction-0.5)
           else
              tmpFraction:=2*tmpFraction;

        if Inverted then
           tmpFraction:=1-tmpFraction;

        DoNextFrame(tmpFraction);
      end;
    end;
  end;

  if Assigned(FOnFrame) then
     FOnFrame(Self);

  // Last Frame ?
  if Delta>EndTime then
  begin
    if Loop then
    begin
      CurrentFrame:=-1;
      StartTime:=AnimateTick;
    end
    else
    begin
      // Stop animation
      InternalStop;

      // Used by TMaker.TBlocks, should be replaced with an internal OnStop
      // to destroy the Animate there, instead of free it here.
      if FreeOnStop then
         {$IFDEF D18}DisposeOf{$ELSE}Free{$ENDIF}
    end;
  end;
end;

procedure TTeeAnimate.InternalStop;
var t   : Integer;
    tmp : Integer;
begin
  if Assigned(ITimer) then
  begin
    tmp:=ITimer.IndexOfEvent(OnTimer);

    if tmp<>-1 then
    begin
      Dispose(ITimer.IList[tmp]);

      ITimer.IList[tmp]:=nil;
      ITimer.IAnyNil:=True;
    end;
  end;

  CurrentFrame:=-1;

  if FState<>asStopped then
  begin
    t:=0;

    while t<Animations.Count do
    begin
      with Animations[t] do
      if IsEnabled then
      begin
        if not Self.Loop then
           DoNextFrame(1);

        Stop;

        if EndAnimation then
           Free
        else
           Inc(t);
      end
      else
        Inc(t);
    end;

    FState:=asStopped;

    if (not (csDestroying in ComponentState)) then
       if Assigned(ITimer) and Assigned(ITimer.IThreadTimer) then
          ITimer.IThreadTimer.Synchronize(CallOnStop)
       else
          CallOnStop;
  end;
end;

procedure TTeeAnimate.CallOnPlay;
begin
  if Assigned(IOnPlay) then
     IOnPlay(Self);

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

procedure TTeeAnimate.CallOnStop;
begin
  // Internal notify event used at Animations editor dialog to detect stop and
  // enable/disable buttons.
  if Assigned(IOnStop) then
     IOnStop(Self);

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

procedure TTeeAnimate.Stop; // stop playing
begin
  InternalStop;
end;

Function TTeeAnimate.Get(Index:Integer):TTeeAnimation;
begin
  result:=Animations[Index];
end;

Procedure TTeeAnimate.Put(Index:Integer; Const Value:TTeeAnimation);
begin
  Animations[Index]:=Value;
end;

procedure TTeeAnimate.SetLoop(const Value: Boolean);
begin
  FLoop:=Value;
end;

function TTeeAnimate.MillisecondsPerFrame:Single;
begin
  result:=1000/Max(1,FSpeed);
end;

function TTeeAnimate.IsSpeedFactorStored:Boolean;
begin
  result:=FSpeedFactor<>1;
end;

// Change animation speed (in Frames Per Second)
procedure TTeeAnimate.SetSpeed(const Value: Integer);
begin
  if FSpeed<>Value then
  begin
    FSpeed:=Value;
    ITimer.ChangeInterval(OnTimer,Trunc(MillisecondsPerFrame));
  end;
end;

function TTeeAnimate.GetChildOwner: TComponent;
begin
  result:=Owner;
end;

procedure TTeeAnimate.GetChildren(Proc: TGetChildProc; Root: TComponent);
var t : Integer;
    tmp : TTeeAnimation;
begin
  inherited;

  for t:=0 to Animations.Count-1 do
  begin
    tmp:=Animation[t];

    if not tmp.IDontFree then
       if (not Assigned(tmp.Parent)) or (tmp.Parent<>tmp) then
          Proc(tmp);
  end;
end;

procedure TTeeAnimate.SaveEvents(var AnimateEvents: TTeeAnimateEvents);
begin
  with AnimateEvents do
  begin
    Clear    :=Self.FOnClear;
    Continue :=Self.FOnContinue;
    DeletedShapes:=Self.FOnDeleted;
    Frame    :=Self.FOnFrame;
    Stop     :=Self.FOnStop;
    Pause    :=Self.FOnPause;
    Play     :=Self.FOnPlay;
  end;
end;

procedure TTeeAnimate.RestoreEvents(const AnimateEvents: TTeeAnimateEvents);
begin
  with AnimateEvents do
  begin
    Self.FOnClear    :=Clear;
    Self.FOnContinue :=Continue;
    Self.FOnDeleted  :=DeletedShapes;
    Self.FOnFrame    :=Frame;
    Self.FOnPause    :=Pause;
    Self.FOnPlay     :=Play;
    Self.FOnStop     :=Stop;
  end;
end;

{ TAnimations }

{$IFDEF D18}
Destructor TAnimations.Destroy;
begin
  Clear;
  inherited;
end;
{$ENDIF}

// Unique Add:
procedure TAnimations.Add(const Animation: TTeeAnimation);
begin
  if IndexOf(Animation)=-1 then
  begin
    Animation.IAnimate:=IAnimate as TTeeAnimate;
    inherited Add(Animation);
  end;
end;

{$IFNDEF D6}
{$DEFINE ANIMASSIGN}
{$ELSE}
{$IFDEF D18}
{$DEFINE ANIMASSIGN}
{$ENDIF}
{$ENDIF}

{$IFDEF ANIMASSIGN}
procedure TAnimations.Assign(const Value:TAnimations);
var tmpClass : TTeeAnimationClass;
    tmp      : TTeeAnimation;
    t        : Integer;
begin
  Clear;

  for t:=0 to Value.Count-1 do
  begin
    tmpClass:=TTeeAnimationClass(Value[t].ClassType);

    tmp:=tmpClass.Create(Value[t].Owner);
    tmp.Assign(Value[t]);

    Add(tmp);
  end;
end;
{$ENDIF}

procedure TAnimations.Clear;
var tmp : TTeeAnimate;
begin
  FreeAll;

  {$IFNDEF D18}
  inherited;
  {$ENDIF}

  if Animate is TTeeAnimate then
  begin
    tmp:=TTeeAnimate(Animate);

    if Assigned(tmp.OnClear) then
       if (not (csDestroying in tmp.ComponentState)) then
           tmp.OnClear(tmp);
  end;
end;

procedure TAnimations.FreeAll;
var t : Integer;
begin
  t:=0;

  while t<Count do
  begin
    if Assigned(Animation[t]) then
    begin
      Animation[t].IAnimate:=nil;

      if not Animation[t].IDontFree then
      begin
         Animation[t].{$IFDEF D18}DisposeOf{$ELSE}Free{$ENDIF};

         {$IFNDEF D18}
         inherited Items[t]:=nil;
         {$ENDIF}
      end;
    end;

    Inc(t);
  end;
end;

function TAnimations.Get(Index: Integer): TTeeAnimation;
begin
  result:=TTeeAnimation(Items[Index]);
end;

procedure TAnimations.Put(Index: Integer; const Value: TTeeAnimation);
begin
  TTeeAnimation(Items[Index]).Assign(Value);
end;

{ TTeeAnimation }

Constructor TTeeAnimation.Create(AOwner:TComponent);
begin
  inherited;
  FDuration:=1000; // 1 second
  FEnabled:=True;
end;

procedure TTeeAnimation.DoNextFrame(const Fraction:Single);
var tmp : Single;
begin
  tmp:=TimingFunction(Fraction);
  NextFrame(tmp);

  if Assigned(FOnFrame) then
     FOnFrame(Self,tmp);
end;

// Execute next frame. Virtual abstract.
procedure TTeeAnimation.NextFrame(const Fraction:Single);
begin
end;

procedure TTeeAnimation.Pause; // pause individual action
begin
  if IPlaying=asPlaying then
     IPlaying:=asPaused;
end;

procedure TTeeAnimation.Play; // initialize action
begin
  IPlaying:=asPlaying;

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

procedure TTeeAnimation.Stop; // stop individual action
begin
  IPlaying:=asStopped;

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

procedure TTeeAnimation.Continue; // resume playing individual action
begin
  if IPlaying=asPaused then
     IPlaying:=asPlaying;
end;

procedure TTeeAnimation.FixupReferences(const AParentSource:String);
begin
  IParentSource:=AParentSource;
end;

function TTeeAnimation.GetOwner: TPersistent;
begin
  if csWriting in ComponentState then
     result:=Self
  else
     result:=inherited GetOwner;
end;

const
  FramesPerMSec=60.0/1000;

function TTeeAnimation.GetStartFrame:Integer;
begin
  result:=Round(FStart*FramesPerMSec);
end;

procedure TTeeAnimation.SetStartFrame(Value:Integer);
begin
  FStart:=Round(Value/FramesPerMSec);
end;

function TTeeAnimation.GetAnimate:TTeeAnimate;
begin
  if not Assigned(IAnimate) then
  begin
    IAnimate:=TTeeAnimate.Create(Self);
    IAnimate.Animations.Add(Self);
  end;

  result:=IAnimate;
end;

procedure TTeeAnimation.SetAnimate(const Value:TTeeAnimate);
begin
  if IAnimate<>Value then
  begin
    if Assigned(IAnimate) then
       Animate.Animations.Remove(Self);

    IAnimate:=Value;

    if Assigned(IAnimate) then
       with Animate.Animations do
       if IndexOf(Self)=-1 then
          Add(Self);
  end;
end;

class function TTeeAnimation.IsValidSource(const ASource:TObject; IsObject:Boolean):Boolean;
begin
  result:=Assigned(ASource) and (not IsObject);
end;

procedure TTeeAnimation.SetDuration(const Value:Integer);
begin
  if FDuration<>Value then
  begin
    FDuration:=Value;

    if Assigned(Parent) then
       Parent.ChildDurationChanged(Self);
  end;
end;

procedure TTeeAnimation.SetParent(const Value:TTeeAnimation);
begin
  if FParent<>Value then
  begin
    FParent:=Value;

    if Assigned(FParent) then
       FParent.Added(Self);
  end;
end;

procedure TTeeAnimation.ReadState(Reader: TReader);
begin
  inherited;
  SetParentComponent(Reader.Parent);
end;

procedure TTeeAnimation.SetParentComponent(AParent: TComponent);
begin
  if AParent is TTeeAnimate then
     SetAnimate(TTeeAnimate(AParent));
end;

function TTeeAnimation.GetParentComponent: TComponent;
begin
  result:=IAnimate;
end;

function TTeeAnimation.HasParent: Boolean;
begin
  result:=True;
end;

procedure TTeeAnimation.StoreValue;
begin
end;

procedure TTeeAnimation.Added(const AAnimation:TTeeAnimation);
begin
end;

procedure TTeeAnimation.Removed(const AAnimation:TTeeAnimation);
begin
  AAnimation.FParent:=nil;
end;

procedure TTeeAnimation.ChildDurationChanged(const Child:TTeeAnimation);
begin
end;

procedure TTeeAnimation.ReadStartFrame(Reader: TReader);
begin
  StartFrame:=Reader.ReadInteger;
end;

procedure TTeeAnimation.DefineProperties(Filer: TFiler);
begin
  inherited;
  Filer.DefineProperty('StartFrame',ReadStartFrame,nil,False);
end;

function TTeeAnimation.IsFolder:Boolean;
begin
  result:=False;
end;

function TTeeAnimation.IsSequence:Boolean;
begin
  result:=False;
end;

function TTeeAnimation.IsValidChild(const AClass:TTeeAnimationClass):Boolean;
begin
  result:=True;
end;

class function TTeeAnimation.IsValidOwner(const AObject:TObject):Boolean;
begin
  result:=True;
end;

// Inspired by FireMonkey and http://www.robertpenner.com/easing
function TTeeAnimation.TimingFunction(const Fraction:Single):Single;
const HalfPi=0.5*Pi;
var tmp,
    tmp2,
    tmp3 : Single;
begin
  if FTiming=atLinear then
     result:=Fraction
  else
  begin
    if FTimingStyle=atInOut then
       if Fraction>0.5 then
          tmp:=2-2*Fraction
       else
          tmp:=2*Fraction
    else
    if FTimingStyle=atOut then
       tmp:=1-Fraction
    else
       tmp:=Fraction;

    case FTiming of
      atSine:   result:=1-Cos(HalfPi*tmp);
      atSquare: result:=Sqr(tmp);
      atExp:    result:=0.5*Power(2,tmp);
      atCubic:  result:=Sqr(tmp)*tmp;
      atBack:   begin
                   tmp2:=tmp*tmp;
                   tmp3:=tmp2*tmp;
                   result:=4*tmp3-3*tmp2;
                end;
      atElastic: begin
                   tmp2:=tmp*tmp;
                   tmp3:=tmp2*tmp;
                   result:=33*tmp3*tmp2 -59*tmp2*tmp2 + 32*tmp3 -5*tmp2;
                 end;

    else
      {atBounce:}
                 begin
                   tmp:=1-tmp;

                   if tmp<(1/2.75) then
                      result:=7.5625*tmp*tmp
                   else
                   if tmp<(2/2.75) then
                      result:=7.5625*Sqr(tmp-(1.5/2.75)) + 0.75
                   else
                   if tmp<(2.5/2.75) then
                      result:=7.5625*Sqr(tmp-(2.25/2.75)) + 0.9375
                   else
                      result:=7.5625*Sqr(tmp-(2.625/2.75)) + 0.984375;

                   result:=1-result;
                end;
    end;

    if FTimingStyle=atOut then
       result:=1-result
    else
    if FTimingStyle=atInOut then
       if Fraction>0.5 then
          result:=1-0.5*result
       else
          result:=0.5*result
  end;
end;

procedure TTeeAnimation.NewNode;
begin
end;

function TTeeAnimation.Index: Integer;
begin
  if Assigned(IAnimate) then
     result:=Animate.Animations.IndexOf(Self)
  else
     result:=-1;
end;

function TTeeAnimation.EndAnimation:Boolean;
begin
  result:=False;

  if Assigned(FOnEnd) then
     FOnEnd(Self,result);
end;

function TTeeAnimation.EditorClass: String;
begin
  result:='TAnimationEditor';
end;

function TTeeAnimation.EditorName: String;
begin
  result:=FTitle;

  if result='' then
  begin
    result:=Name;

    if result='' then
       result:=Description;
  end;
end;

class function TTeeAnimation.Description: String;
begin
  result:=ClassName;

  if Length(result)>0 then
  begin
    if result[1]='T' then Delete(result,1,1);

    if result<>'Animation' then
    begin
      if UpperCase(Copy(result,Length(result)-8,9))='ANIMATION' then
         Delete(result,Length(result)-8,9);
    end;
  end;
end;

function TTeeAnimation.IsEnabled: Boolean;
begin
  result:=FEnabled and
          ((not Assigned(FParent)) or FParent.IsEnabled);
end;

Destructor TTeeAnimation.Destroy;
var t : Integer;
begin
  if Assigned(IAnimate) then
  with Animate.Animations do
  begin
    for t:=0 to Count-1 do
        if Animation[t].FParent=Self then
           Animation[t].FParent:=nil;

    Remove(Self);
  end;

  IAnimate:=nil;
  
  if Assigned(Parent) then
     Parent.Removed(Self);

  inherited;
end;

procedure TTeeAnimation.Assign(Source:TPersistent);
begin
  if Source is TTeeAnimation then
  with TTeeAnimation(Source) do
  begin
    Self.FDuration:=FDuration;
    Self.FEnabled:=FEnabled;
    Self.FInverted:=FInverted;
    Self.FLoop:=FLoop;
    Self.FStart:=FStart;
    Self.FTitle:=FTitle;
    Self.FTiming:=FTiming;
    Self.FTwoWay:=FTwoWay;
  end
  else
    inherited;
end;

function TTeeAnimation.DurationFrames:Integer;
begin
  if Assigned(IAnimate) then
     result:=Round(Duration/Animate.MillisecondsPerFrame)
  else
     result:=Round(Duration*0.06); // Dummy 60fps
end;

function TTeeAnimation.EndTime: Integer;
begin
  result:=FStart+FDuration-1;
end;

function TTeeAnimation.EndFrame: Integer;
begin
  result:=StartFrame+DurationFrames-1;
end;

procedure TTeeAnimation.Preview;
begin
  // nothing
end;

Procedure TeeRegisterAnimation(const Animation:TTeeAnimationClass);
begin
  TeeActivateGroup;

  if not Assigned(TeeAnimationClasses) then
     TeeAnimationClasses:=TStringList.Create;

  if TeeAnimationClasses.IndexOf(Animation.ClassName)=-1 then
  begin
    Classes.RegisterClass(Animation);
    TeeAnimationClasses.Add(Animation.ClassName);
  end;
end;

{ TCustomTeePanelLink }
procedure TCustomTeePanelLink.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited;

  if (Operation=opRemove) and Assigned(FPanel) and (AComponent=FPanel) then
     Panel:=nil;
end;

procedure TCustomTeePanelLink.SetPanel(const Value: TCustomTeePanel);
begin
  if FPanel<>Value then
  begin
    if Assigned(FPanel) then
       FPanel.RemoveFreeNotification(Self);

    FPanel:=Value;

    if Assigned(FPanel) then
       FPanel.FreeNotification(Self);
  end;
end;

{ TPropertyAnimation }

class function TPropertyAnimation.Fixup(var AInstance:TObject; var AName:String):{$IFDEF D8}PPropInfo{$ELSE}PPropInfo{$ENDIF};
var tmpSt    : String;
    i        : Integer;
    tmpIndex : Integer;

    {$IFDEF D18}
    tmpValue : TValue;
    tmpList  : TRttiProperty;
    tmpContext : TRttiContext;
    tmpType : TRttiType;
    {$ENDIF}
begin
  result:=nil;

  if Assigned(AInstance) then
  repeat
    i:=Pos('.',AName);

    if i>0 then
    begin
      tmpSt:=Copy(AName,1,i-1);

      result:=GetPropInfo(AInstance,tmpSt);

      if Assigned(result) then
         AInstance:=GetObjectProp(AInstance,result)
      else
      {$IFNDEF NEXTGEN}
      if AInstance is TList then
      begin
        if UpperCase(Copy(tmpSt,1,4))='ITEM' then
        begin
          tmpIndex:=StrToInt(Copy(tmpSt,5,Length(tmpSt)));

          if TList(AInstance).Count>tmpIndex then
             AInstance:=TList(AInstance).Items[tmpIndex]
          else
          begin
            AInstance:=nil; // error? forward?
            result:=nil;
            break;
          end;
        end;
      end
      else
      {$ENDIF}
      if AInstance is TCollection then
      begin
        if UpperCase(Copy(tmpSt,1,4))='ITEM' then
        begin
          tmpIndex:=StrToInt(Copy(tmpSt,5,Length(tmpSt)));

          if TCollection(AInstance).Count>tmpIndex then
             AInstance:=TCollection(AInstance).Items[tmpIndex]
          else
          begin
            AInstance:=nil; // error? forward?
            result:=nil;
            break;
          end;
        end;
      end
      else
      begin
        {$IFDEF D18}

        tmpContext:=TRttiContext.Create;
        try
          tmpType:=tmpContext.GetType(AInstance.ClassInfo);
          tmpList:=tmpType.GetProperty('List');

          if Assigned(tmpList) and (tmpList.PropertyType is TRttiDynamicArrayType) then
          begin
            tmpValue:=tmpList.GetValue(AInstance);

            if UpperCase(Copy(tmpSt,1,4))='ITEM' then
            begin
              tmpIndex:=StrToInt(Copy(tmpSt,5,Length(tmpSt)));

              if tmpValue.GetArrayLength>tmpIndex then
                 AInstance:=tmpValue.GetArrayElement(tmpIndex).AsObject
              else
              begin
                AInstance:=nil; // error? forward?
                result:=nil;
                break;
              end;
            end;
          end
          else
            break;
        finally
          tmpContext.Free;
        end;
        {$ELSE}
        break;
        {$ENDIF}
      end;

      Delete(AName,1,i);
    end
    else
    if (AInstance is TStrings) and (UpperCase(AName)='TEXT') then
    else
       result:=GetPropInfo(AInstance,AName);

  until i=0;
end;

procedure TPropertyAnimation.ReadSpecial(Reader:TReader);
begin
  IsSpecial:=Reader.ReadInteger;
end;

procedure TPropertyAnimation.WriteSpecial(Writer:TWriter);
begin
  Writer.WriteInteger(Ord(IsSpecial));
end;

procedure TPropertyAnimation.DefineProperties(Filer: TFiler);
begin
  inherited;
  Filer.DefineProperty('Special',ReadSpecial,WriteSpecial,IsSpecial<>0);
end;

procedure TPropertyAnimation.FindRealNames;
begin
  IRealInstance:=Instance;
  IRealProperty:=PropertyName;

  Fixup(IRealInstance,IRealProperty);
end;

function TPropertyAnimation.EditorClass: String;
begin
  result:='TAnimationEditors';
end;

procedure TPropertyAnimation.Assign(Source:TPersistent);
begin
  if Source is TPropertyAnimation then
  with TPropertyAnimation(Source) do
  begin
    Self.FInst:=FInst;
    Self.FProp:=FProp;
  end;

  inherited;
end;

procedure TPropertyAnimation.Notification( AComponent: TComponent;
                            Operation: TOperation);
begin
  inherited;

  if (Operation=opRemove) and Assigned(FInst) and (AComponent=FInst) then
     Instance:=nil;
end;

procedure TPropertyAnimation.FixupReferences(const AParentSource:String);
begin
  inherited;

  // Relink "IRealInstance"
  if not HasProperty then
     FindRealNames;
end;

function TPropertyAnimation.EditorName: String;
var tmpName : String;
begin
  if HasProperty then
  begin
    if Instance is TComponent then
       tmpName:=TComponent(Instance).Name
    else
    if Instance is TPersistent then
       tmpName:=TPersistent(Instance).GetNamePath
    else
       tmpName:=Instance.ClassName;

    if Title='' then
       result:=tmpName
    else
       result:=Title+' '+tmpName;

    if result='' then
       result:=Instance.ClassName;

    result:=result+' '+PropertyName;
  end
  else
     result:=inherited EditorName;
end;

function TPropertyAnimation.HasProperty:Boolean;
begin
  result:=Assigned(IRealInstance) and (PropertyName<>'') and
          ( (not (FInst is TComponent)) or (not (csDestroying in TComponent(FInst).ComponentState)) );
end;

function TPropertyAnimation.IsEnabled: Boolean;
begin
  result:=inherited IsEnabled and HasProperty;
end;

procedure TPropertyAnimation.SetInst(const Value: TObject);
begin
  if FInst<>Value then
  begin
    if Assigned(FInst) and (FInst is TComponent) then
       TComponent(FInst).RemoveFreeNotification(Self);

    FInst:=Value;

    if Assigned(FInst) and (FInst is TComponent) then
       TComponent(FInst).FreeNotification(Self);

    FindRealNames;

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

procedure TPropertyAnimation.SetProp(const Value: String);
begin
  if FProp<>Value then
  begin
    FProp:=Trim(Value);
    FindRealNames;

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

{ TPropertyLink }

Constructor TPropertyLink.Create(AOwner:TComponent);
begin
  inherited Create;
  IOwner:=AOwner;
end;

procedure TPropertyLink.FindRealNames;
begin
  IRealInstance:=Instance;
  IRealProperty:=PropertyName;

  TPropertyAnimation.Fixup(IRealInstance,IRealProperty);
end;

procedure TPropertyLink.CheckInstancePrefix(const APrefix:String; const AInstance:TObject);
var i : Integer;
begin
  IRealProperty:=Trim(IRealProperty);

  i:=Pos(APrefix,UpperCase(IRealProperty));
  if i=1 then
  begin
    Delete(IRealProperty,1,Length(APrefix));
    IRealInstance:=AInstance;
  end;
end;

function TPropertyLink.PropertyInfo:{$IFDEF D8}PPropInfo{$ELSE}PPropInfo{$ENDIF};
begin
 result:=GetPropInfo(IRealInstance,IRealProperty);
end;

function TPropertyLink.PropertyValue(const APropName:String):Variant;
begin
  result:=GetPropValue(IRealInstance,APropName)
end;

function TPropertyLink.PropertyValue(AProc:TGetPropertyValueProc=nil):Variant;
begin
  if Assigned(IRealInstance) and (GetPropInfo(IRealInstance,IRealProperty)<>nil) then
     if Assigned(AProc) then
        result:=AProc(IRealInstance,IRealProperty)
     else
        result:=GetPropValue(IRealInstance,IRealProperty)
  else
     result:=Null;
end;

procedure TPropertyLink.Assign(Source:TPersistent);
begin
  if Source is TPropertyLink then
  with TPropertyLink(Source) do
  begin
    Self.FProp:=FProp;  // <-- before setting "Instance"
    Self.Instance:=FInst;
  end
  else
    inherited;
end;

procedure TPropertyLink.SetInst(const Value: TObject);
begin
  if FInst<>Value then
  begin
    if Assigned(FInst) and (FInst is TComponent) then
       TComponent(FInst).RemoveFreeNotification(IOwner);

    FInst:=Value;

    if Assigned(FInst) and (FInst is TComponent) then
       TComponent(FInst).FreeNotification(IOwner);

    FindRealNames;
  end;
end;

procedure TPropertyLink.SetProp(const Value: String);
begin
  if FProp<>Value then
  begin
    FProp:=Trim(Value);
    FindRealNames;
  end;
end;

{ TRangePropertyAnimation }

Constructor TRangePropertyAnimation.Create(AOwner:TComponent);
begin
  inherited;
  FUseEnd:=True;
  FUseStart:=True;
end;

procedure TRangePropertyAnimation.Assign(Source:TPersistent);
begin
  if Source is TRangePropertyAnimation then
  with TRangePropertyAnimation(Source) do
  begin
    Self.FKeepEnd:=FKeepEnd;
    Self.FUseEnd:=FUseEnd;
    Self.FUseStart:=FUseStart;
  end;

  inherited;
end;

{ TAnimationCurve }

Destructor TAnimationCurve.Destroy;
begin
  X:=nil;
  Y:=nil;
  ICurve:=nil;
  inherited;
end;

procedure TAnimationCurve.ReadCurve(Stream: TStream);
var tmp : Integer;
    t   : Integer;
begin
  Stream.Read(tmp,SizeOf(tmp));

  SetLength(X,tmp);
  SetLength(Y,tmp);

  for t:=0 to tmp-1 do
  begin
    Stream.Read(X[t],SizeOf(Double));
    Stream.Read(Y[t],SizeOf(Double));
  end;
end;

procedure TAnimationCurve.WriteCurve(Stream: TStream);
var tmp : Integer;
    t   : Integer;
begin
  tmp:=Length(X);
  Stream.Write(tmp,SizeOf(tmp));

  for t:=0 to tmp-1 do
  begin
    Stream.Write(X[t],SizeOf(Double));
    Stream.Write(Y[t],SizeOf(Double));
  end;
end;

procedure TAnimationCurve.DefineProperties(Filer: TFiler);

  function IsFlat:Boolean;
  begin
    result:=(Length(X)=2) and (Y[0]=0) and (Y[1]=0);
  end;

begin
  inherited;
  Filer.DefineBinaryProperty('Curve',ReadCurve,WriteCurve,
                             (Length(X)>0) and (not IsFlat));
end;

procedure TAnimationCurve.Assign(Source:TPersistent);
var t : Integer;
begin
  if Source is TAnimationCurve then
  with TAnimationCurve(Source) do
  begin
    Self.FInverted:=FInverted;

    SetLength(Self.X,Length(X));
    for t:=0 to Length(X)-1 do Self.X[t]:=X[t];

    SetLength(Self.Y,Length(Y));
    for t:=0 to Length(Y)-1 do Self.Y[t]:=Y[t];

    Self.ICurve:=nil;
  end
  else
    inherited;
end;

procedure TAnimationCurve.Calculate(Duration:Integer);

  procedure Stretch;
  var t : Integer;
      tmp : Double;
  begin
    tmp:=1/ICurve[Duration-1];

    for t:=1 to Duration-1 do
        ICurve[t]:=ICurve[t]*tmp;
  end;

  function Interpolate(const AX:Double):Double;
  var t     : Integer;
      dx,dy : Double;
  begin
    for t:=0 to Length(X)-1 do
    if X[t]>AX then
       break;

    dx:=X[t]-X[t-1];
    dy:=Y[t]-Y[t-1];

    if dx<>0 then
       result:=(dy*(AX-X[t-1])/dx) + Y[t-1]
    else
       result:=0;
  end;

var t : Integer;
    tmp,
    InvDuration2,
    InvDuration : Double;
begin
  if Length(X)=0 then
     ICurve:=nil
  else
  begin
    SetLength(ICurve,Duration);

    InvDuration:=1/Duration;
    InvDuration2:=1/(Duration-1);

    if FInverted then tmp:= -1
                 else tmp:= +1;

    ICurve[0]:=0;

    for t:=1 to Duration-1 do
        ICurve[t]:=ICurve[t-1]+
            (InvDuration2+InvDuration2*tmp*Interpolate(t*InvDuration));

    if ICurve[Duration-1]<>1 then
       Stretch;
  end;
end;

procedure TAnimationCurve.Reset;
begin
  SetLength(X,2);
  SetLength(Y,2);

  X[0]:=0;
  X[1]:=1;
  Y[0]:=0;
  Y[1]:=0;
end;

{ TNumberAnimation }

Destructor TNumberAnimation.Destroy;
begin
  FEndLink.Free;
  FStartLink.Free;
  FreeAndNil(FCurve);
  inherited;
end;

procedure TNumberAnimation.Assign(Source:TPersistent);
begin
  if Source is TNumberAnimation then
  with TNumberAnimation(Source) do
  begin
    Self.EndLink:=FEndLink;
    Self.FEndValue:=FEndValue;
    Self.FInitStart:=FInitStart;
    Self.StartLink:=FStartLink;
    Self.FStartValue:=FStartValue;
    Self.Curve:=FCurve;
  end;

  inherited;
end;

function TNumberAnimation.GetEndLink:TPropertyLink;
begin
  if not Assigned(FEndLink) then
     FEndLink:=TPropertyLink.Create(Self);

  result:=FEndLink;
end;

function TNumberAnimation.GetStartLink:TPropertyLink;
begin
  if not Assigned(FStartLink) then
     FStartLink:=TPropertyLink.Create(Self);

  result:=FStartLink;
end;

procedure TNumberAnimation.SetEndLink(const Value:TPropertyLink);
begin
  if Assigned(Value) then EndLink.Assign(Value)
                     else FreeAndNil(FEndLink);
end;

procedure TNumberAnimation.SetStartLink(const Value:TPropertyLink);
begin
  if Assigned(Value) then StartLink.Assign(Value)
                     else FreeAndNil(FStartLink);
end;

procedure TNumberAnimation.SetCurve(const Value:TAnimationCurve);
begin
  if Assigned(Value) then
  begin
    Curve.Assign(Value);
    Curve.Calculate(Duration);
  end
  else
    FreeAndNil(FCurve);
end;

function TNumberAnimation.GetCurve:TAnimationCurve;
begin
  if not Assigned(FCurve) then
  begin
    FCurve:=TAnimationCurve.Create;
    FCurve.Reset;
  end;

  result:=FCurve;
end;

function TNumberAnimation.HasLink(const ALink:TPropertyLink):Boolean;
begin
  result:=Assigned(ALink) and (Assigned(ALink.Instance) or (ALink.PropertyName<>''));
end;

function TNumberAnimation.GetEndValue:Double;
begin
  if HasLink(FEndLink) then
     result:=FEndLink.PropertyValue
  else
     result:=FEndValue;
end;

function TNumberAnimation.GetStartValue:Double;
begin
  if HasLink(FStartLink) then
     result:=FStartLink.PropertyValue
  else
     result:=FStartValue;
end;

function TNumberAnimation.HasCurve:Boolean;
begin
  result:=Assigned(FCurve) and (Length(FCurve.X)>0);
end;

function TNumberAnimation.CurveFrameValue(Frame:Integer):Single;
begin
  if HasCurve then
     result:=FCurve.ICurve[Frame]
  else
     result:=TimingFunction(Frame/DurationFrames);
end;

procedure TNumberAnimation.NextFrame(const Fraction:Single);
var tmp : Double;
begin
  tmp:=StartValue;
  Value:=tmp+Fraction*(EndValue-tmp);
end;

function TNumberAnimation.EndAnimation:Boolean;
begin
  if not (csDestroying in ComponentState) then
     if KeepEndValue then
        Value:=EndValue
     else
        Value:=OldValue;

  result:=inherited EndAnimation;
end;

Procedure TNumberAnimation.NewNode;
begin
  if Assigned(IAnimate) and (not (csLoading in IAnimate.ComponentState)) then
    if Assigned(Instance) and (PropertyName<>'') then
    begin
      if FStartValue=0 then
         FStartValue:=Value;

      if FEndValue=0 then
         FEndValue:=Value;
    end;
end;

Procedure TNumberAnimation.Play;
begin
  if not UseStartValue then
     FStartValue:=GetPropValue(IRealInstance,IRealProperty);

  if not UseEndValue then
     FEndValue:=GetPropValue(IRealInstance,IRealProperty);

  inherited;
end;

procedure TNumberAnimation.StoreValue;
begin
  inherited;
  OldValue:=Value;

  if UseStartValue and InitStart then
     Value:=StartValue;
end;

function TNumberAnimation.GetValue: Double;
begin
  if HasProperty then
     result:=GetPropValue(IRealInstance,IRealProperty)
  else
     result:=FValue;
end;

procedure TNumberAnimation.SetDuration(const Value:Integer);
begin
  inherited;

  if Assigned(FCurve) then
     FCurve.Calculate(Duration);
end;

Function TNumberAnimation.IsEnabled:Boolean;
begin
  result:=Assigned(FOnSetValue) or (inherited IsEnabled);
end;

procedure TNumberAnimation.SetValue(const AValue: Double);
begin
  if HasProperty then
     SetPropValue(IRealInstance,IRealProperty,AValue)
  else
  begin
    FValue:=AValue;

    if Assigned(FOnSetValue) then
       FOnSetValue(Self,FValue);
  end;
end;

{ TColorsAnimation }

procedure TColorsAnimation.NextFrame(const Fraction:Single);
var sh, sl, ss,
    eh, el, es : Word;
    tmpC : TColor;
    sr, sg, sb,
    er, eg, eb : Byte;
begin
  if Method=cmRGB then
  begin
    tmpC:={ColorToRGB}(Round(StartValue));
    sr:=GetRValue(tmpC);
    sg:=GetGValue(tmpC);
    sb:=GetBValue(tmpC);

    tmpC:={ColorToRGB}(Round(EndValue));
    er:=GetRValue(tmpC);
    eg:=GetGValue(tmpC);
    eb:=GetBValue(tmpC);

    Value:=RGB(
      sr+Round(Fraction*(er-sr)),
      sg+Round(Fraction*(eg-sg)),
      sb+Round(Fraction*(eb-sb)) );
  end
  else
  begin
    ColorToHLS({ColorToRGB}(Round(StartValue)),sh,sl,ss);
    ColorToHLS({ColorToRGB}(Round(EndValue)),eh,el,es);

    Value:=HLSToColor(
      sh+Round(Fraction*(eh-sh)),
      sl+Round(Fraction*(el-sl)),
      ss+Round(Fraction*(es-ss)) );
  end;
end;

function TColorsAnimation.GetEnd:TColor;
begin
  result:=Round(EndValue);
end;

function TColorsAnimation.GetStart:TColor;
begin
  result:=Round(StartValue);
end;

procedure TColorsAnimation.SetEnd(const Value:TColor);
begin
  EndValue:=Value;
end;

procedure TColorsAnimation.SetStart(const Value:TColor);
begin
  StartValue:=Value;
end;

{ TToggleAnimation }

Constructor TToggleAnimation.Create(AOwner: TComponent);
begin
  inherited;
  FEvery:=5;
end;

function TToggleAnimation.GetValue: Boolean;
begin
  if HasProperty then
     result:=GetPropValue(IRealInstance,IRealProperty)
  else
     result:=False;
end;

procedure TToggleAnimation.NextFrame;
begin
  if (Every=0) or
     (((Animate.CurrentFrame-StartFrame) mod Every)=0) then
       Value:=not Value;
end;

procedure TToggleAnimation.SetValue(const Value: Boolean);
var tmp : Variant;
begin
  if HasProperty then
  begin
    tmp:=Value;

    SetPropValue(IRealInstance,IRealProperty,tmp);
  end;
end;

{ TSinusAnimation }

procedure TSinusAnimation.NextFrame(const Fraction:Single);
var tmp : Double;
begin
  tmp:=StartValue;
  Value:=tmp+Sin(Fraction*2*Pi)*(EndValue-tmp);
end;

{ TPlaySoundAnimation }

var
  GlobalSoundPath:String='';

procedure TPlaySoundAnimation.Assign(Source: TPersistent);
begin
  if Source is TPlaySoundAnimation then
     FFile:=TPlaySoundAnimation(Source).FFile;

  inherited;
end;

function TPlaySoundAnimation.IsEnabled: Boolean;
begin
  result:=inherited IsEnabled and (FFile<>'');
end;

function TPlaySoundAnimation.CheckSoundLibrary(const AFile:String):String;
begin
  result:=Trim(AFile);

  if UpperCase(Copy(result,1,12))='$(TEEMAKER)\' then
     result:=GlobalSoundPath+'\'+Copy(result,13,Length(result));

  if (not TeeIsURL(result)) and (IParentSource<>'') then
     if (not FileExists(result)) then
        if FileExists(IParentSource+'\'+result) then
           result:=IParentSource+'\'+result;
end;

class procedure TPlaySoundAnimation.Play(const AFileName:String; Synchronous:Boolean=True);
{$IFNDEF FMX}
var tmpFlag : Cardinal;
{$ENDIF}
begin
  if AFileName<>'' then
  begin
    {$IFNDEF FMX}
    {$IFDEF MSWINDOWS}
    if Synchronous then
       tmpFlag:=SND_SYNC
    else
       tmpFlag:=SND_ASYNC;

    {$IFNDEF LINUX}
    sndPlaySound({$IFDEF D12}PChar{$ELSE}PAnsiChar{$ENDIF}(AFileName),tmpFlag);
    {$ENDIF}
    {$ENDIF}
    {$ENDIF}
  end;
end;

procedure TPlaySoundAnimation.Play;
begin
  if FFile<>'' then
     Play(CheckSoundLibrary(FFile),Synchronous);
end;

procedure SetVolume(const volL, volR: Word);
{$IFDEF FMX}
begin
end;
{$ELSE}
{$IFDEF MSWINDOWS}
var
  hWO: HWAVEOUT;
  waveF: TWAVEFORMATEX;
  vol: DWORD;
begin
  FillChar(waveF, SizeOf(waveF), 0);
  waveOutOpen(@hWO, WAVE_MAPPER, @waveF, 0, 0, 0);

  vol := volL + volR shl 16;
  waveOutSetVolume(hWO, vol);
  waveOutClose(hWO);
end;
{$ELSE}
begin
end;
{$ENDIF}
{$ENDIF}

class procedure TPlaySoundAnimation.ChangeVolume(const Value:Integer);
var tmp : Integer;
begin
  tmp:=Round(Value*65535*0.01);
  SetVolume(tmp,tmp);
end;

class procedure TPlaySoundAnimation.SetGlobalPath(const Value: String);
begin
  GlobalSoundPath:=Value;
end;

procedure TPlaySoundAnimation.SetFile(const Value: String);
begin
  FFile:=Value;
end;

initialization
  TeeActivateGroup;

  {$IFDEF FMX}
    GroupDescendentsWith(TCustomTeePanelLink,TFMXObject);
    GroupDescendentsWith(TTeeAnimation,TFMXObject);
  {$ELSE}
    {$IFDEF D6}
    GroupDescendentsWith(TCustomTeePanelLink,TControl);
    GroupDescendentsWith(TTeeAnimation,TControl);
    {$ENDIF}
  {$ENDIF}

  Classes.RegisterClass(TTeeAnimate);

  TeeRegisterAnimation(TNumberAnimation);
  TeeRegisterAnimation(TColorsAnimation);
  TeeRegisterAnimation(TToggleAnimation);
  TeeRegisterAnimation(TSinusAnimation);
  TeeRegisterAnimation(TPlaySoundAnimation);

  ITimer:=TMultiTimer.Create;

  {$IFDEF MSWINDOWS}
  if QueryPerformanceFrequency(IFrequency) then
     IFrequency:=IFrequency div 1000
  else
     IFrequency:=0;
  {$ENDIF}

finalization
  FreeAndNil(ITimer);
  FreeAndNil(TeeAnimationClasses);
end.
