{**********************************************}
{  TTeeAnimation Editor                        }
{  Copyright (c) 2001-2020 by Steema Software  }
{**********************************************}
unit TeeAnimationEditor;
{$I TeeDefs.inc}

interface

uses
  {$IFNDEF LINUX}
  Windows,
  {$ENDIF}

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

  ExtCtrls, Graphics, Forms, StdCtrls, ComCtrls, Controls,

  {$IFDEF D6}
  Variants,
  {$ENDIF}

  Classes, SysUtils, TypInfo,

  TeeProcs, TeCanvas, TeeGalleryPanel, TeEngine, Series, TeeAnimate;

type
  TAnimationEditor = class(TForm)
    PageControl1: TPageControl;
    TabOptions: TTabSheet;
    TabTiming: TTabSheet;
    Panel1: TPanel;
    Label7: TLabel;
    CBTiming: TComboFlat;
    Gallery: TChartGalleryPanel;
    Label3: TLabel;
    Label4: TLabel;
    LabelTitle: TLabel;
    Label1: TLabel;
    Label2: TLabel;
    EDuration: TEdit;
    EStartTime: TEdit;
    CBEnabled: TCheckBox;
    CBLoop: TCheckBox;
    ETitle: TEdit;
    CBTwoWay: TCheckBox;
    CBInverted: TCheckBox;
    Label5: TLabel;
    CBEasing: TComboFlat;
    procedure CBEnabledClick(Sender: TObject);
    procedure CBLoopClick(Sender: TObject);
    procedure EDurationChange(Sender: TObject);
    procedure EStartTimeChange(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure ETitleChange(Sender: TObject);
    procedure CBTimingChange(Sender: TObject);
    procedure PageControl1Change(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure GalleryChangeChart(Sender: TObject);
    procedure CBTwoWayClick(Sender: TObject);
    procedure CBInvertedClick(Sender: TObject);
    procedure CBEasingChange(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
    Animation : TTeeAnimation;
    FOnChanged : TNotifyEvent;

    IPointerAnim : TTeeAnimation;

    procedure CreateTimingGallery;
    procedure DoChanged;
    procedure PointerAnimFrame(Sender: TTeeAnimation; const Fraction:Single);
  public
    { Public declarations }
    procedure RefreshAnimation(const AAnimation:TTeeAnimation);
    property OnChanged:TNotifyEvent read FOnChanged write FOnChanged;
  end;

implementation

{$IFNDEF LCL}
{$R *.DFM}
{$ELSE}
{$R *.lfm}
{$ENDIF}

Uses
  Math;

type
  TAnimationAccess=class(TTeeAnimation);

{ TAnimationEditor }

procedure TAnimationEditor.RefreshAnimation(const AAnimation: TTeeAnimation);
begin
  Animation:=AAnimation;

  if Assigned(Animation) then
  with Animation do
  begin
    EDuration.Text:=IntToStr(Duration);

    if Assigned(Parent) and TAnimationAccess(Parent).IsSequence then
       EStartTime.Enabled:=False;

    EStartTime.Text:=IntToStr(StartTime);
    CBTiming.ItemIndex:=Ord(Timing);
    CBEasing.ItemIndex:=Ord(TimingStyle);

    CBEnabled.Checked:=Enabled;
    CBLoop.Checked:=Loop;
    CBTwoWay.Checked:=TwoWay;
    CBInverted.Checked:=Inverted;
  end;
end;

procedure TAnimationEditor.CBEnabledClick(Sender: TObject);
begin
  Animation.Enabled:=CBEnabled.Checked;
end;

procedure TAnimationEditor.CBLoopClick(Sender: TObject);
begin
  Animation.Loop:=CBLoop.Checked;
end;

procedure TAnimationEditor.EDurationChange(Sender: TObject);
begin
  if Showing then
  begin
    Animation.Duration:=StrToIntDef(EDuration.Text,Animation.Duration);
    DoChanged;
  end;
end;

procedure TAnimationEditor.EStartTimeChange(Sender: TObject);
begin
  if Showing then
  begin
    Animation.StartTime:=StrToIntDef(EStartTime.Text,Animation.StartTime);
    DoChanged;
  end;
end;

procedure TAnimationEditor.FormShow(Sender: TObject);
begin
  if not Assigned(Animation) then
     Animation:=TTeeAnimation(Tag);
     
  RefreshAnimation(Animation);
end;

procedure TAnimationEditor.ETitleChange(Sender: TObject);
begin
  if Showing then
  begin
    Animation.Title:=ETitle.Text;
    DoChanged;
  end;
end;

procedure TAnimationEditor.DoChanged;
begin
  if Assigned(FOnChanged) then
     FOnChanged(Animation);
end;

procedure TAnimationEditor.CBTimingChange(Sender: TObject);
begin
  if Gallery.Charts.Count>0 then
  begin
    if Assigned(Gallery.SelectedChart) then
       Gallery.SelectedChart.Series[1].Visible:=False;

    Gallery.SelectedChart:=Gallery.Charts[CBTiming.ItemIndex];
  end
  else
     Animation.Timing:=TAnimationTiming(CBTiming.ItemIndex);
end;

procedure TAnimationEditor.CreateTimingGallery;
var
  tmpAnim : TTeeAnimation;

  procedure CreateChart(const ATitle:String; const ATiming:TAnimationTiming);
  var tmp : TGalleryChart;
      t   : Integer;
  begin
    tmp:=TGalleryChart.Create(Self);
    tmp.View3D:=False;
    tmp.Axes.Hide;
    tmp.Walls.Back.Show;

    tmp.AddSeries(TFastLineSeries);

    TFastLineSeries(tmp[0]).DrawStyle:=flAll;

    tmpAnim.Timing:=ATiming;

    for t:=0 to 100 do
          tmp[0].Add(tmpAnim.TimingFunction(t*0.01));

    tmp.Title.Caption:=ATitle;
    tmp.Hint:=ATitle;
    tmp.ShowHint:=True;

    tmp.AddSeries(TPointSeries);
    tmp[1].AddXY(49,tmp[0].MandatoryValueList[49]);
    TPointSeries(tmp[1]).Pointer.Style:=psCircle;
    tmp[1].Visible:=False;

    tmp.Parent:=Gallery;
    Gallery.FinishAdd(tmp);
    Gallery.Charts.Add(tmp);
  end;

var t : Integer;
begin
  tmpAnim:=TTeeAnimation.Create(nil);
  try
    tmpAnim.TimingStyle:=TAnimationTimingStyle(CBEasing.ItemIndex);

    Gallery.FreeAllCharts;

    for t:=0 to CBTiming.Items.Count-1 do
        CreateChart(CBTiming.Items[t],TAnimationTiming(t));

    if not Assigned(IPointerAnim) then
    begin
      IPointerAnim:=TTeeAnimation.Create(nil);
      IPointerAnim.Animate.Speed:=25; // 25 FPS instead of 60, to use less CPU %

      IPointerAnim.Animate.Loop:=True;
      IPointerAnim.StartTime:=500;
      IPointerAnim.OnFrame:=PointerAnimFrame;
    end;
  finally
    tmpAnim.Free;
  end;

  Gallery.ResizeCharts;

  Gallery.SelectedChart:=Gallery.Charts[CBTiming.ItemIndex];
end;

procedure TAnimationEditor.PageControl1Change(Sender: TObject);
begin
  if PageControl1.ActivePage=TabTiming then
  begin
    if Gallery.Charts.Count=0 then
        CreateTimingGallery
    else
    if Assigned(IPointerAnim) then
       IPointerAnim.Animate.Continue;
  end
  else
  if Assigned(IPointerAnim) then
     IPointerAnim.Animate.Pause;
end;

procedure TAnimationEditor.FormCreate(Sender: TObject);
begin
  PageControl1.ActivePage:=TabOptions;
end;

procedure TAnimationEditor.GalleryChangeChart(Sender: TObject);
begin
  if CBTiming.ItemIndex<>-1 then
     Gallery.Charts[CBTiming.ItemIndex].Series[1].Visible:=False;

  CBTiming.ItemIndex:=Gallery.Charts.IndexOf(Gallery.SelectedChart);
  Animation.Timing:=TAnimationTiming(CBTiming.ItemIndex);

  Gallery.SelectedChart[1].Visible:=True;

  if not IPointerAnim.Animate.Playing then
     IPointerAnim.Animate.Play;
end;

procedure TAnimationEditor.CBTwoWayClick(Sender: TObject);
begin
  Animation.TwoWay:=CBTwoWay.Checked;
end;

procedure TAnimationEditor.CBInvertedClick(Sender: TObject);
begin
  Animation.Inverted:=CBInverted.Checked;
end;

procedure TAnimationEditor.CBEasingChange(Sender: TObject);
begin
  Animation.TimingStyle:=TAnimationTimingStyle(CBEasing.ItemIndex);

  IPointerAnim.TimingStyle:=Animation.TimingStyle;

  CreateTimingGallery;
end;

procedure TAnimationEditor.FormDestroy(Sender: TObject);
begin
  IPointerAnim.Free;
end;

procedure TAnimationEditor.PointerAnimFrame(Sender: TTeeAnimation; const Fraction:Single);
var tmpX : Integer;
    tmpChart : TCustomAxisPanel;
    tmpXY    : TChartSeries;
begin
  tmpChart:=Gallery.SelectedChart;

  tmpX:=Round(Fraction*100);

  tmpXY:=tmpChart[1];

  if tmpXY.XValues.Value[0]<>tmpX then
  begin
    tmpXY.XValues.Value[0]:=tmpX;
    tmpXY.YValues.Value[0]:=tmpChart[0].YValues.Value[tmpX]; // <-- Using YValues.Value[] avoids invalidate
    tmpChart.Draw;
  end;
end;

initialization
  RegisterClass(TAnimationEditor);
end.
