{*********************************************}
{  TeeChart Charting Library for FireMonkey   }
{  Copyright (c) 2012-2020 by Steema Software }
{  All Rights Reserved                        }
{*********************************************}
unit FMXTee.Editor.Animations;
{$I TeeDefs.inc}

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Rtti, System.Classes,
  {$IFNDEF UPTOXE3}
  FMX.StdCtrls,
  {$ENDIF}
  {$IFDEF D19}
  FMX.Graphics,
  {$ENDIF}
  System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs,
  FMXTee.Engine, FMXTee.Chart, FMXTee.Animations.Tools, FMX.TabControl,
  FMX.Layouts, FMX.ListBox, FMX.Menus, FMXTee.Animate, FMXTee.Editor.Animation,
  {$IFDEF D21}
  FMX.ComboTrackBar,
  {$ENDIF}
  FMX.Edit, FMX.TreeView;

type
  TChartAnimationsEditor = class(TForm)
    LayoutAnims: TLayout;
    Panel1: TPanel;
    LBAnims: TListBox;
    Splitter1: TSplitter;
    PanelEditor: TLayout;
    Panel2: TPanel;
    Layout3: TLayout;
    Layout4: TLayout;
    TabControl1: TTabControl;
    Splitter2: TSplitter;
    TabAniOptions: TTabItem;
    TabAnimation: TTabItem;
    Panel3: TPanel;
    LBItems: TListBox;
    SBAddItem: TButton;
    BDeleteItem: TButton;
    BAdd: TButton;
    BDelete: TButton;
    BRename: TButton;
    CBLoop: TCheckBox;
    CBSpeed: TComboBox;
    SBPlay: TButton;
    SBPause: TButton;
    SBStop: TButton;
    PopupMenu1: TPopupMenu;
    TBFramesPerSecond: TComboTrackBar;
    Label1: TLabel;
    procedure BAddClick(Sender: TObject);
    procedure BDeleteClick(Sender: TObject);
    procedure BRenameClick(Sender: TObject);
    procedure LBAnimsChange(Sender: TObject);
    procedure SBPlayClick(Sender: TObject);
    procedure SBPauseClick(Sender: TObject);
    procedure SBStopClick(Sender: TObject);
    procedure SBAddItemClick(Sender: TObject);
    procedure BDeleteItemClick(Sender: TObject);
    procedure CBLoopChange(Sender: TObject);
    procedure CBSpeedChange(Sender: TObject);
    procedure LBItemsChange(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure TBFramesPerSecondChangeTracking(Sender: TObject);
  private
    { Private declarations }
    { [weak] } IOldAnimation : TTeeAnimationTool;
    IAnimEditor : TAnimationEditor;
    IAnimation  : TTeeAnimationTool;

    procedure ChangedAnimation(Sender: TObject);
    procedure CheckPlayButtons(Sender:TObject);
    procedure ClearOnChangeEvent;
    procedure FillAnimations;
    function NewAnimation:TTeeAnimationTool;
    procedure PopupSelected(Sender:TObject);
    function SelectedAnimation:TTeeAnimationTool;
    function SelectedItem:TTeeAnimation;
    procedure SetInternalEvents(const AEvent:TNotifyEvent);
    procedure UnselectedAnimation;
  protected
    OnAdded   : TNotifyEvent;
    OnDeleted : TNotifyEvent;
  public
    { Public declarations }
    Animations : TChartAnimations;
    EditorNode : TTreeViewItem;

    procedure EditAnimation(const Value:TTeeCustomAnimation);
    procedure RefreshAnimations(const Value:TChartAnimations);
    Procedure Reload;
    procedure SelectAnimation(const Value:TTeeCustomAnimation);
    procedure SelectIfAny;

    class function CreateEditor(const AOwner:TComponent; const Anims:TChartAnimations):TChartAnimationsEditor;
    class procedure Edit(const AOwner:TComponent; const Animation:TTeeAnimationTool);
  end;

implementation

{$R *.fmx}

uses
  FMXTee.Editor.Stroke, FMXTee.Constants, FMXTee.Canvas,
  FMXTee.Procs, FMXTee.Animations.Gallery;

{ TChartAnimationsEditor }

function TChartAnimationsEditor.NewAnimation:TTeeAnimationTool;
var tmpClass : TTeeAnimationToolClass;
    tmp : String;
    tmpChart : TCustomAxisPanel;
    t : Integer;
    tmpAnimClass : TTeeAnimationClass;
begin
  tmpClass:=TAnimationsGallery.Choose(Self,tmp);

  if Assigned(tmpClass) then
  begin
    tmpChart:=Animations.Owner;

    result:=tmpClass.Create(tmpChart.Owner) as TTeeAnimationTool;

    with result do
    begin
      ParentChart:=tmpChart;
      Name:=TeeGetUniqueName(Owner,TeeMsg_DefaultAnimationName);
    end;

    Animations.Add(result);

    for t:=0 to TeeAnimationClasses.Count-1 do
    begin
      tmpAnimClass:=TTeeAnimationClass(GetClass(TeeAnimationClasses[t]));

      if tmp=tmpAnimClass.Description then
      begin
        result.Animations.Add(tmpAnimClass.Create(Animations.Owner));
        break;
      end;
    end;
  end
  else
     result:=nil;
end;

procedure TChartAnimationsEditor.PopupSelected(Sender:TObject);
var tmp : TTeeAnimation;
    tmpClass : TTeeAnimationClass;
begin
  tmpClass:=TTeeAnimationClass(GetClass(TeeAnimationClasses[TMenuItem(Sender).Tag]));

  tmp:=tmpClass.Create(SelectedAnimation.Owner);
  tmp.Animate:=SelectedAnimation.Animate;

  if tmp is TCustomAnimationChart then
     TCustomAnimationChart(tmp).Chart:=SelectedAnimation.ParentChart
  else
     tmp.TagObject:=SelectedAnimation.ParentChart;

  LBItems.Items.AddObject(tmp.EditorName,tmp);
  LBItems.ItemIndex:=LBItems.Items.Count-1;
  //LBItemsChange(Self);
end;

procedure TChartAnimationsEditor.BAddClick(Sender: TObject);
var tmpAnim : TTeeAnimationTool;
    tmpS : String;
    tmpNode : TTreeViewItem;
begin
  tmpAnim:=NewAnimation;

  if Assigned(tmpAnim) then
  begin
    tmpS:=tmpAnim.Title;

    LBAnims.Items.AddObject(tmpS,tmpAnim);
    LBAnims.ItemIndex:=LBAnims.Items.Count-1;

    if Assigned(EditorNode) then
    begin
      tmpNode:=TTreeViewItem.Create(EditorNode.Owner);
      tmpNode.Text:=tmpS;
      tmpNode.TextSettings.WordWrap:=False; // Fix
      tmpNode.TagObject:=tmpAnim;
      tmpNode.Parent:=EditorNode;
    end;
  end;
end;

procedure TChartAnimationsEditor.SBAddItemClick(Sender: TObject);
var tmp : TMenuItem;
    t   : Integer;
    P   : TPointF;
    tmpClass : TTeeAnimationClass;
begin
  if PopupMenu1.ItemsCount=0 then
  begin
    for t:=0 to TeeAnimationClasses.Count-1 do
    begin
      tmpClass:=TTeeAnimationClass(GetClass(TeeAnimationClasses[t]));

      if tmpClass.IsValidOwner(Animations.Owner) then
      begin
        tmp:=TMenuItem.Create(Self);
        tmp.Text:=TTeeAnimationClass(GetClass(TeeAnimationClasses[t])).Description;
        tmp.Tag:=t;
        tmp.OnClick:=PopupSelected;
        tmp.Parent:=PopupMenu1; // PopupMenu1.Items.Add(tmp);
      end;
    end;
  end;

  P.X:=SBAddItem.Position.X+SBAddItem.Width;
  P.Y:=SBAddItem.Position.Y+SBAddItem.Height;

//  P:=SBAddItem.AbsoluteToLocal(P);

  PopupMenu1.Popup(P.X,P.Y);
end;

procedure TChartAnimationsEditor.SBPauseClick(Sender: TObject);
begin
  if SelectedAnimation.Paused then
     SelectedAnimation.Continue
  else
     SelectedAnimation.Pause;

  CheckPlayButtons(nil);
end;

procedure TChartAnimationsEditor.SBPlayClick(Sender: TObject);
begin
  SelectedAnimation.Play;
  CheckPlayButtons(nil);
end;

procedure TChartAnimationsEditor.SBStopClick(Sender: TObject);
begin
  SelectedAnimation.Stop;
  CheckPlayButtons(nil);
end;

procedure TChartAnimationsEditor.BDeleteItemClick(Sender: TObject);
var tmp : Integer;
begin
  tmp:=LBItems.ItemIndex;

  if YesNo(Format(TeeMsg_SureToDelete,[LBItems.Items[tmp]])) then
  begin
    SelectedAnimation.Animations[tmp].{$IFDEF AUTOREFCOUNT}DisposeOf{$ELSE}Free{$ENDIF};

    LBItems.Items.Delete(tmp);

    if tmp>LBItems.Items.Count-1 then
       LBItems.ItemIndex:=LBItems.Items.Count-1
    else
       LBItems.ItemIndex:=tmp;

    //LBItemsChange(Self);
  end;
end;

procedure TChartAnimationsEditor.BDeleteClick(Sender: TObject);
var tmp : Integer;
begin
  tmp:=LBAnims.ItemIndex;

  if YesNo(Format(TeeMsg_SureToDelete,[LBAnims.Items[tmp]])) then
  begin
    SelectedAnimation.{$IFDEF AUTOREFCOUNT}DisposeOf{$ELSE}Free{$ENDIF};

    UnselectedAnimation;

    LBAnims.Items.Delete(tmp);
    LBItems.Clear;

    if Assigned(EditorNode) then
       EditorNode.ItemByIndex(tmp).{$IFDEF AUTOREFCOUNT}DisposeOf{$ELSE}Free{$ENDIF};

    if tmp>LBAnims.Items.Count-1 then
       tmp:=LBAnims.Items.Count-1;

    LBAnims.ItemIndex:=tmp;

    if Assigned(EditorNode) and (tmp<>-1) then
       EditorNode.ItemByIndex(tmp).Select;

    BDelete.Enabled:=tmp<>-1;
    BRename.Enabled:=BDelete.Enabled;
  end;
end;

procedure TChartAnimationsEditor.BRenameClick(Sender: TObject);
var tmp : String;
begin
  tmp:=SelectedAnimation.Name;

  if TeeInputQuery(TeeMsg_ChangeAnimationName,TeeMsg_NewAnimationName,tmp) then
  if tmp<>'' then
  begin
    SelectedAnimation.Name:=tmp;
    LBAnims.Items[LBAnims.ItemIndex]:=tmp;

    if Assigned(EditorNode) then
       EditorNode.ItemByIndex(LBAnims.ItemIndex).Text:=tmp;
  end;
end;

procedure TChartAnimationsEditor.CBLoopChange(Sender: TObject);
begin
  SelectedAnimation.Animate.Loop:=CBLoop.IsChecked;
end;

procedure TChartAnimationsEditor.CBSpeedChange(Sender: TObject);
var s : String;
begin
  s:=CBSpeed.Items[CBSpeed.ItemIndex];
  Delete(s,Length(s),1);
  SelectedAnimation.Animate.SpeedFactor:=StrToFloat(s);
end;

procedure TChartAnimationsEditor.RefreshAnimations(const Value:TChartAnimations);
begin
  if Assigned(Value) then
  begin
    Animations:=Value;
    FillAnimations;
    SelectIfAny;
  end;
end;

procedure TChartAnimationsEditor.Reload;
begin
  if Assigned(Animations) and Assigned(Animations.Owner) then
     if Animations.Count<>LBAnims.Items.Count then
     begin
       FillAnimations;
       SelectIfAny;
     end;

  {
  if LBAnims.ItemIndex<>-1 then
     LBAnimsChange(Self);
  }
end;

procedure TChartAnimationsEditor.SelectAnimation(const Value: TTeeCustomAnimation);
begin
  if Assigned(Value) and (Value.ParentChart.Animations<>Animations) then
  begin
    Animations:=Value.ParentChart.Animations;
    RefreshAnimations(Animations);
  end;

  LBAnims.ItemIndex:=LBAnims.Items.IndexOfObject(Value);
  LBAnimsChange(Self);
end;

procedure TChartAnimationsEditor.SelectIfAny;
begin
  if Animations.Count>0 then
  begin
    LBAnims.ItemIndex:=0;
    //LBAnimsChange(Self);
  end
  else
  begin
    PanelEditor.Visible:=True;
    PanelEditor.Visible:=False;
  end;
end;

class function TChartAnimationsEditor.CreateEditor(const AOwner: TComponent;
  const Anims: TChartAnimations): TChartAnimationsEditor;
begin
  result:=TChartAnimationsEditor.Create(AOwner);
  result.Animations:=Anims;
end;

class procedure TChartAnimationsEditor.Edit(const AOwner: TComponent;
  const Animation: TTeeAnimationTool);
var tmp : TChartAnimations;
begin
  tmp:=(Animation.ParentChart as TCustomChart).Animations;

  with TChartAnimationsEditor.CreateEditor(AOwner,tmp) do
  try
    LayoutAnims.Visible:=False;
    IAnimation:=Animation;
    ShowModal;
  finally
    Free;
  end;
end;

procedure TChartAnimationsEditor.EditAnimation(const Value: TTeeCustomAnimation);
begin
  LayoutAnims.Visible:=False;
  Splitter1.Visible:=False;
  SelectAnimation(Value);
end;

procedure RemoveControlsFromTab(const ATab:TTabItem);
var t : Integer;
begin
  for t:=0 to ATab.ControlsCount-1 do
  if ATab.Controls[t] is TContent then
  begin
    while ATab.Controls[t].ControlsCount>0 do
      {$IFDEF AUTOREFCOUNT}
      ATab.Controls[t].Controls[0].DisposeOf;
      {$ELSE}
      ATab.Controls[t].Controls[0].Free;
      {$ENDIF}

    break;
  end;
end;

procedure TChartAnimationsEditor.UnselectedAnimation;
begin
  ClearOnChangeEvent;
  SetInternalEvents(nil);

  PanelEditor.Visible:=LBAnims.ItemIndex<>-1;
  PanelEditor.Enabled:=PanelEditor.Visible;

  BDeleteItem.Enabled:=False;
end;

type
  TAnimateAccess=class(TTeeAnimate);

procedure TChartAnimationsEditor.LBAnimsChange(Sender: TObject);
var t : Integer;
    tmpFPS : Integer;
begin
  if IOldAnimation<>SelectedAnimation then
  begin
    UnselectedAnimation;

    if SelectedAnimation<>nil then
    begin
      RemoveControlsFromTab(TabAniOptions);
      TabAniOptions.Visible:=False;

      LBItems.Clear;

      for t:=0 to SelectedAnimation.Animations.Count-1 do
          LBItems.Items.AddObject(SelectedAnimation.Animations[t].EditorName,SelectedAnimation.Animations[t]);

      if LBItems.Items.Count>0 then
         LBItems.ItemIndex:=0;

      //LBItemsChange(Self);

      //Timer1.Enabled:=True;

      CheckPlayButtons(nil);

      tmpFPS:=SelectedAnimation.Animate.Speed;

      while tmpFPS>TBFramesPerSecond.Max do
         TBFramesPerSecond.Max:=2*TBFramesPerSecond.Max;

      if TBFramesPerSecond.Max>120 then
         TBFramesPerSecond.Frequency:=Round(TBFramesPerSecond.Max) div 12;

      TBFramesPerSecond.Value:=tmpFPS;

      CBLoop.IsChecked:=SelectedAnimation.Animate.Loop;

      CBSpeed.ItemIndex:=CBSpeed.Items.IndexOf(FloatToStr(SelectedAnimation.Animate.SpeedFactor)+'x');
    end;

    IOldAnimation:=SelectedAnimation;

    SetInternalEvents(CheckPlayButtons);
  end;

  BDelete.Enabled:=PanelEditor.Enabled;
  BRename.Enabled:=BDelete.Enabled;
end;

procedure TChartAnimationsEditor.SetInternalEvents(const AEvent:TNotifyEvent);
begin
  if Assigned(IOldAnimation) then
  begin
    TAnimateAccess(IOldAnimation.Animate).IOnPlay:=AEvent;
    TAnimateAccess(IOldAnimation.Animate).IOnStop:=AEvent;
  end;
end;

procedure TChartAnimationsEditor.TBFramesPerSecondChangeTracking(
  Sender: TObject);
begin
  if SelectedAnimation<>nil then
     SelectedAnimation.Animate.Speed:=Round(TBFramesPerSecond.Value);
end;

type
  TFormClass=class of TCommonCustomForm;

procedure TChartAnimationsEditor.LBItemsChange(Sender: TObject);

  function AddEditor(const Animation:TTeeAnimation):TCommonCustomForm;
  var tmpFormClass : TFormClass;
  begin
    tmpFormClass:=TFormClass(TeeGetClass(Animation.EditorClass));

    if Assigned(tmpFormClass) and (tmpFormClass<>TAnimationEditor) then
    begin
      result:=tmpFormClass.Create(Self);

      //TeeTranslateControl(result);
      result.TagObject:=Animation;
      EmbeddForm(result,TabAniOptions);

      if Assigned(result.OnShow) then
         result.OnShow(result);
    end
    else
      result:=nil;
  end;

var tmpItem : TTeeAnimation;
    tmpForm : TCommonCustomForm;
begin
  ClearOnChangeEvent;

  TabControl1.Enabled:=LBItems.ItemIndex<>-1;
  TabControl1.Visible:=TabControl1.Enabled;

  RemoveControlsFromTab(TabAniOptions);

  if LBItems.ItemIndex<>-1 then
  begin
    tmpItem:=SelectedItem;

    if tmpItem is TCustomAnimationChart then
    begin
      if TCustomAnimationChart(tmpItem).Chart=nil then
         TCustomAnimationChart(tmpItem).Chart:=SelectedAnimation.ParentChart
    end
    else
    if not Assigned(tmpItem.TagObject) then
       tmpItem.TagObject:=SelectedAnimation.ParentChart;

    if not Assigned(IAnimEditor) then
    begin
      IAnimEditor:=TAnimationEditor.Create(Self);
      EmbeddForm(IAnimEditor,TabAnimation);
    end;

    IAnimEditor.RefreshAnimation(tmpItem);

    tmpForm:=AddEditor(tmpItem);

    if tmpItem is TPropertyAnimation then
       TPropertyAnimation(tmpItem).OnChange:=ChangedAnimation;

    TabAniOptions.Visible:=Assigned(tmpForm);

    if TabAniOptions.Visible then
       TabControl1.ActiveTab:=TabAniOptions
    else
       TabControl1.ActiveTab:=TabAnimation;
  end;

  BDeleteItem.Enabled:=LBItems.ItemIndex<>-1;
end;

procedure TChartAnimationsEditor.ChangedAnimation(Sender: TObject);
begin
  LBItems.Items[LBItems.ItemIndex]:=SelectedItem.EditorName;
end;

procedure TChartAnimationsEditor.CheckPlayButtons(Sender:TObject);
begin
  with SelectedAnimation do
  begin
    SBPlay.Enabled:=not Animate.Playing;
    SBPause.Enabled:=Animate.Playing and (not Paused);
    SBStop.Enabled:=Animate.Playing or Paused;
  end;
end;

procedure TChartAnimationsEditor.ClearOnChangeEvent;
var t : Integer;
    tmp : TTeeAnimation;
begin
  for t:=0 to LBItems.Items.Count-1 do
  begin
    tmp:=TTeeAnimation(LBItems.Items.Objects[t]);

    if tmp is TPropertyAnimation then
       TPropertyAnimation(tmp).OnChange:=nil;
  end;
end;

procedure TChartAnimationsEditor.FillAnimations;
var t : Integer;
    tmpS : String;
    tmp : TTeeCustomAnimation;
begin
  LBAnims.Clear;

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

    tmpS:=tmp.Name;
    if tmpS='' then
    begin
      tmpS:=tmp.Description;

      if tmpS='' then
         tmpS:=tmp.ClassName;
    end;

    LBAnims.Items.AddObject(tmpS,tmp);
  end;
end;

procedure TChartAnimationsEditor.FormDestroy(Sender: TObject);
begin
  SetInternalEvents(nil);
end;

procedure TChartAnimationsEditor.FormShow(Sender: TObject);
begin
  if Assigned(Animations) then
  begin
    RefreshAnimations(Animations);

    if Assigned(IAnimation) then
       SelectAnimation(IAnimation);
  end;
end;

function TChartAnimationsEditor.SelectedAnimation: TTeeAnimationTool;
begin
  if LBAnims.ItemIndex=-1 then
     result:=nil
  else
     result:=TTeeAnimationTool(LBAnims.Items.Objects[LBAnims.ItemIndex]);
end;

function TChartAnimationsEditor.SelectedItem:TTeeAnimation;
begin
  if LBItems.ItemIndex=-1 then
     result:=nil
  else
     result:=TTeeAnimation(LBItems.Items.Objects[LBItems.ItemIndex]);
end;

end.
