// ------------------------------------------------------------------------------
// DPF.iOS.UIView Component
//
// Dadeh Pardazane Faragir ( DPF ) Co.
//
// Web: http://www.dpfaragir.com
//
// Developed By: Babak Yaghoobi
//
// Email #1: yaghoobi@dpfaragir.com
// Email #2: b_yaghobi@yahoo.com
//
// ------------------------------------------------------------------------------
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ------------------------------------------------------------------------------
unit DPF.iOS.UIView;

interface

{$I DPF.iOS.Defs.inc}

uses
  System.SysUtils,
  System.Classes,
  System.Types,
  System.UITypes,
  System.Variants,
  System.Math,

  System.TypInfo,
  DPF.iOS.BaseControl,
{$IFDEF IOS}
  iOSapi.GLKit,
  Macapi.ObjectiveC,
  Macapi.ObjCRuntime,
  IOSapi.CocoaTypes,
  IOSapi.Foundation,
  iOSapi.QuartzCore,
  IOSapi.Uikit,
  IOSapi.CoreGraphics,
  IOSapi.CoreImage,
  FMX.Platform.iOS,
  DPF.iOS.Common,
{$ENDIF}
  FMX.Forms,
  FMX.Layouts, FMX.Memo,
  FMX.Types, FMX.Controls, FMX.Dialogs, FMX.StdCtrls;

const
  DBL_TAP_DELAY = 0.2;

type

  TDPFUIView = class;
  // TDPFUIViewClass = class of TDPFUIView;

{$IFDEF IOS}

  // ------------------------------------------------------------------------------

  (*
    IGestureRecognizerDelegate = interface( IObjectiveC )
    ['{E24404F9-DBAD-43E6-8371-9D13EFB25BA2}']
    function gestureRecognizer( gestureRecognizer: UIGestureRecognizer; shouldReceiveTouch: UITouch ): Boolean; cdecl; overload;
    procedure handleSingleTap( gr: UITapGestureRecognizer ); cdecl;
    end;

    TDPFViewGestureRecognizerDelegate = class( TOCLocal, IGestureRecognizerDelegate )
    private
    FDPFUIView: TDPFUIView;
    public
    constructor Create( ADPFUIView: TDPFUIView );

    function gestureRecognizer( gestureRecognizer: UIGestureRecognizer; shouldReceiveTouch: UITouch ): Boolean; overload; cdecl;
    procedure handleSingleTap( gr: UITapGestureRecognizer ); cdecl;
    end;
  *)

  DPFView = interface( UIView )
    ['{6FE239D4-CC58-485C-9BBC-409AD6AFA29A}']
    procedure drawRect( rect: CGRect ); cdecl; overload;

    procedure SingleTap( Sender: pointer ); cdecl;
    procedure DoubleTap( Sender: pointer ); cdecl;

    procedure touchesBegan( touches: NSSet; withEvent: UIEvent ); cdecl;
    procedure touchesMoved( touches: NSSet; withEvent: UIEvent ); cdecl;
    procedure touchesEnded( touches: NSSet; withEvent: UIEvent ); cdecl;
  end;

  TDPFView = class( TOCLocal )
  private
  protected
    FDPFUIView: TDPFUIView;
  public
    constructor Create( ADPFUIView: TDPFUIView );
    function GetObjectiveCClass: PTypeInfo; override;
    procedure drawRect( rect: CGRect ); overload; cdecl;

    procedure SingleTap( Sender: pointer ); cdecl;
    procedure DoubleTap( Sender: pointer ); cdecl;

    procedure touchesBegan( touches: NSSet; withEvent: UIEvent ); cdecl;
    procedure touchesMoved( touches: NSSet; withEvent: UIEvent ); cdecl;
    procedure touchesEnded( touches: NSSet; withEvent: UIEvent ); cdecl;
  end;
{$ENDIF}

  TDPFViewOnDrawRect = procedure( Sender: TObject; Rect: DPFNSRect ) of object;
  TDPFTouchesBegan   = procedure( Sender: TObject; const Location: DPFNSPoint; const PreviousLocation: DPFNSPoint ) of object;
  TDPFTouchesMoved   = procedure( Sender: TObject; const Location: DPFNSPoint; const PreviousLocation: DPFNSPoint ) of object;
  TDPFTouchesEnded   = procedure( Sender: TObject; const TapCount: Integer; const Location: DPFNSPoint; const PreviousLocation: DPFNSPoint ) of object;

  [ComponentPlatformsAttribute( PidWin32 or PidWin64 or pidLinux32 or pidAndroid or PidOSX32 or PidiOSDevice or PidiOSSimulator )]
  TDPFUIView = class( TDPFiOSBaseControl )
  private
    FEmbFrame               : TFrame;
    FBackgroundColor        : TAlphaColor;
    FForm                   : TForm;
    FBackgroundImage        : string;
    FOnClick                : TDPFOnClicked;
    FNumberOfTouchesRequired: Byte;
    FNumberOfTapsRequired   : Byte;
    FDelaysTouchesBegan     : Boolean;
    FDelaysTouchesEnded     : Boolean;
    FOnFormChanging         : TDPFFormChanging;
    FOnFormChanged          : TDPFFormChanged;
    FFrame                  : TDPFFrame;
    FOnFrameChanged         : TDPFFrameChanged;
    FOnFrameChanging        : TDPFFrameChanging;
    FCheckReceiveTouch      : Boolean;
    FOnDrawRect             : TDPFViewOnDrawRect;
    FTouchesEnded           : TDPFTouchesEnded;
    FOnDoubleClick          : TDPFOnDoubleClicked;
    FTouchesBegan           : TDPFTouchesBegan;
    FTouchesMoved           : TDPFTouchesMoved;
    procedure SetBackgroundColor( const Value: TAlphaColor );
    procedure SetBackgroundImage( const Value: string );
    procedure SetForm( const Value: TForm );
    procedure SetFrame( const Value: TDPFFrame );
    // procedure SetOnClick( const Value: TDPFOnClicked );

  protected
{$IFDEF IOS}
    FUIView: UIView;

    FDPFView: TDPFView;

    // FTapGestureRecognizer        : UITapGestureRecognizer;
    // FDPFGestureRecognizerDelegate: TDPFViewGestureRecognizerDelegate;
{$ENDIF}
    procedure Resize; override;
    procedure Move; override;
{$IFNDEF IOS}
    procedure Paint; override;
{$ENDIF}
  public
{$IFDEF IOS}
    property GetUIView: UIView read FUIView;
    procedure Loaded; override;
{$ENDIF}
    constructor Create( AOwner: TComponent ); override;
    destructor Destroy; override;
    // procedure clearAllSubviews( isRelease: Boolean = false );
    function GetFrame: TFrame;
    procedure Rotate( Degree: Single );

  published
    property BackgroundColor: TAlphaColor read FBackgroundColor write SetBackgroundColor default TAlphaColors.Lightsteelblue;
    property BackgroundImage: string read FBackgroundImage write SetBackgroundImage;

    property Form : TForm read FForm write SetForm;
    property Frame: TDPFFrame read FFrame write SetFrame;

    property OnFormChanging: TDPFFormChanging read FOnFormChanging write FOnFormChanging;
    property OnFormChanged : TDPFFormChanged read FOnFormChanged write FOnFormChanged;

    property OnFrameChanging: TDPFFrameChanging read FOnFrameChanging write FOnFrameChanging;
    property OnFrameChanged : TDPFFrameChanged read FOnFrameChanged write FOnFrameChanged;

    property NumberOfTapsRequired   : Byte read FNumberOfTapsRequired write FNumberOfTapsRequired default 1;
    property NumberOfTouchesRequired: Byte read FNumberOfTouchesRequired write FNumberOfTouchesRequired default 1;

    property DelaysTouchesBegan: Boolean read FDelaysTouchesBegan write FDelaysTouchesBegan default true;
    property DelaysTouchesEnded: Boolean read FDelaysTouchesEnded write FDelaysTouchesEnded default false;

    property CheckReceiveTouch: Boolean read FCheckReceiveTouch write FCheckReceiveTouch default true;

    property OnClick      : TDPFOnClicked read FOnClick write FOnClick { SetOnClick };
    property OnDoubleClick: TDPFOnDoubleClicked read FOnDoubleClick write FOnDoubleClick;

    property OnTouchesEnded: TDPFTouchesEnded read FTouchesEnded write FTouchesEnded;
    property OnTouchesBegan: TDPFTouchesBegan read FTouchesBegan write FTouchesBegan;
    property OnTouchesMoved: TDPFTouchesMoved read FTouchesMoved write FTouchesMoved;

    property OnDrawRect: TDPFViewOnDrawRect read FOnDrawRect write FOnDrawRect;

    property UserInteraction;
    property ContentMode;
    property Alpha;
    property Align;
    property Position;
    property Width;
    property Height;
    property Visible;
    property OnPanGestureRecognizer;
  end;

implementation

// ------------------------------------------------------------------------------
{ TDPFUIView }
constructor TDPFUIView.Create( AOwner: TComponent );
begin
  inherited Create( AOwner );
  ControlCaption   := 'View Control';
  FBackgroundColor := TAlphaColors.Lightsteelblue;
  Visible          := True;
  FForm            := nil;

  FNumberOfTapsRequired    := 1;
  FNumberOfTouchesRequired := 1;

  FDelaysTouchesBegan := true;
  DelaysTouchesEnded  := false;

  FCheckReceiveTouch := true;

{$IFDEF IOS}
  if TOSVersion.Major >= 5 then
  begin
    FDPFView := TDPFView.Create( self );
    FUIView  := TUIView.Wrap( FDPFView.Super.init );
  end
  else
    FUIView  := TUIView.Create;
  FUIControl := FUIView;
{$ENDIF}
end;

// ------------------------------------------------------------------------------
destructor TDPFUIView.Destroy;
begin
{$IFDEF IOS}
  { if Assigned( FTapGestureRecognizer ) then
    FUIView.removeGestureRecognizer( FTapGestureRecognizer );

    if Assigned( FDPFGestureRecognizerDelegate ) then
    FDPFGestureRecognizerDelegate.DisposeOf; }
{$ENDIF}
  inherited;
end;

// ------------------------------------------------------------------------------
function TDPFUIView.GetFrame: TFrame;
begin
  result := FEmbFrame;
end;

// ------------------------------------------------------------------------------
{$IFDEF IOS}

procedure TDPFUIView.Loaded;
begin
  Resize; // Very Important for Embbeded Forms, Frames

  FUIView.setHidden( not Visible );

  if FBackgroundImage <> '' then
    SetBackgroundImage( FBackgroundImage )
  else
    SetBackgroundColor( FBackgroundColor );

  // SetOnClick( FOnClick );

  addSubview( Self, ParentControl );
  // ----------------------------
  // Important
  inherited;
end;
{$ENDIF}

// ------------------------------------------------------------------------------
procedure TDPFUIView.Resize;
begin
{$IFDEF IOS}
  //if FUIView <> nil then FUIView.SetFrame( CGRectMake( Position.X, Position.Y, Width, Height ) );
{$ENDIF}
  inherited;
end;

// ------------------------------------------------------------------------------
procedure TDPFUIView.Move;
begin
  Resize;
end;

{$IFNDEF IOS}

// ------------------------------------------------------------------------------
procedure TDPFUIView.Paint;
var
  C: TAlphaColor;
begin
  C := BackgroundColor;
  if C = TAlphaColors.Null then
    C := TAlphaColors.White;
  InternalPaint( '', TAlphaColors.Black, TDPFTextAlignment.taCenter, C );
end;
{$ENDIF}

// ------------------------------------------------------------------------------
procedure TDPFUIView.SetBackgroundColor( const Value: TAlphaColor );
begin
  FBackgroundColor := Value;
{$IFDEF IOS}
  if FUIView <> nil then
  begin
    if FBackgroundColor <> TAlphaColors.Null then
      FUIView.SetBackgroundColor( TColorToUIColor( FBackgroundColor ) )
    else
      FUIView.SetBackgroundColor( TUIColor.Wrap( TUIColor.OCClass.clearColor ) );
  end;
{$ELSE}
  Invalidate;
{$ENDIF}
end;

// ------------------------------------------------------------------------------
procedure TDPFUIView.SetBackgroundImage( const Value: string );
{$IFDEF IOS}
var
  Image2: UIImage;
  Image1: UIImage;
{$IFNDEF IOSDEVICE}
  transform: CGAffineTransform;
  context  : CGContextRef;
{$ENDIF}
{$ENDIF}
begin
  FBackgroundImage := Value;
{$IFDEF IOS}
  if Assigned( FUIView ) then
  begin
    if FBackgroundImage <> '' then
    begin
      // Image2 := TUIImage.Wrap( TUIImage.OCClass.ImageNamed( NSStr( FBackgroundImage ) ) );

      Image1 := TUIImage.Wrap( TUIImage.OCClass.ImageNamed( NSStr( FBackgroundImage ) ) );
      UIGraphicsBeginImageContext( FUIView.frame.size );
{$IFNDEF IOSDEVICE}
      // ----------------------------------------------------------
      // Mirror on Simulator
      context   := UIGraphicsGetCurrentContext( );
      transform := CGAffineTransformMakeTranslation( 0.0, Height );
      transform := CGAffineTransformScale( transform, 1.0, -1.0 );
      CGContextConcatCTM( context, transform );
{$ENDIF}
      Image1.drawInRect( FUIView.bounds );
      Image2 := TUIImage.Wrap( UIGraphicsGetImageFromCurrentImageContext( ) );
      UIGraphicsEndImageContext( );

      FUIView.SetBackgroundColor( TUIColor.Wrap( TUIColor.OCClass.colorWithPatternImage( Image2 ) ) );
    end
    else
      SetBackgroundColor( FBackgroundColor );
  end;
{$ENDIF}
end;

// ------------------------------------------------------------------------------
procedure TDPFUIView.SetForm( const Value: TForm );
{$IFDEF IOS}
var
  C        : TDPFiOSBaseControl;
  CanChange: Boolean;
{$ENDIF}
begin
{$IFDEF IOS}
  if ( FUIView <> nil ) and ( ( FForm <> Value ) or ( Assigned( Value ) and not Assigned( Value.TagObject ) ) ) then
  begin
    CanChange := True;
    if Assigned( FOnFormChanging ) then
      FOnFormChanging( Self, FForm, Value, CanChange );
    if not CanChange then
      exit;

    if Assigned( FForm ) then
    begin
      MoveChildsToOriginParent( Self );
      // clearAllSubviews( );
      FForm.TagObject := nil;
    end;

    if Assigned( Value ) then
    begin

      if Value.TagObject <> nil then
        MoveChildsToOriginParent( Value.TagObject as TDPFiOSBaseControl );

      while Value.ChildrenCount <> 0 do
        if Value.Children[0] is TDPFiOSBaseControl then
        begin
          C        := Value.Children[0] as TDPFiOSBaseControl;
          C.Parent := Self;
          C.Loaded;
          Value.TagObject := Self;
        end;
    end;
    if Assigned( FOnFormChanged ) then
      FOnFormChanged( Self, FForm, Value );
  end;
{$ENDIF}
  FForm := Value;
end;

// ------------------------------------------------------------------------------
procedure TDPFUIView.SetFrame( const Value: TDPFFrame );
{$IFDEF IOS}
var
  C        : TDPFiOSBaseControl;
  CanChange: Boolean;
{$ENDIF}
begin
{$IFDEF IOS}
  if Assigned( FEmbFrame ) and ( FEmbFrame is Value ) then
    exit;

  if ( FUIView <> nil ) then
  begin
    CanChange := True;
    if Assigned( FOnFrameChanging ) then
      FOnFrameChanging( Self, FFrame, Value, CanChange );
    if not CanChange then
      exit;

    if Assigned( FEmbFrame ) then
    begin
      MoveChildsToOriginParent( Self );
      // clearAllSubviews(  );
      FEmbFrame.TagObject := nil;
      FEmbFrame.DisposeOf;
      FEmbFrame := nil;
    end;
    if Assigned( Value ) then
    begin
      FEmbFrame := Value.Create( nil );

      while FEmbFrame.ChildrenCount <> 0 do
        if FEmbFrame.Children[0] is TDPFiOSBaseControl then
        begin
          C        := FEmbFrame.Children[0] as TDPFiOSBaseControl;
          C.Parent := Self;
          C.Loaded;
        end;
    end;
    if Assigned( FOnFrameChanged ) then
      FOnFrameChanged( Self, Frame, Value );
  end;
{$ENDIF}
  FFrame := Value;
end;

// ------------------------------------------------------------------------------
procedure TDPFUIView.Rotate( Degree: Single );
{$IFDEF IOS}
var
  swingTransform: CGAffineTransform;
{$ENDIF}
begin
{$IFDEF IOS}
  swingTransform := CGAffineTransformIdentity;
  swingTransform := CGAffineTransformRotate( swingTransform, DegToRad( Degree ) );

  TUIView.OCClass.beginAnimations( PNSStr( ' swing ' ), ( FUIView as ILocalObject ).GetObjectID );
  TUIView.OCClass.setAnimationDuration( 0.25 );
  FUIView.setTransform( swingTransform );
{$ENDIF}
end;

// ------------------------------------------------------------------------------
(* procedure TDPFUIView.SetOnClick( const Value: TDPFOnClicked );
  begin
  FOnClick := Value;
  {$IFDEF IOS}
  if Assigned( value ) then
  begin
  if Assigned( FDPFGestureRecognizerDelegate ) then
  exit;

  FDPFGestureRecognizerDelegate := TDPFViewGestureRecognizerDelegate.Create( Self );
  FTapGestureRecognizer         := TUITapGestureRecognizer.Wrap( TUITapGestureRecognizer.Alloc.initWithTarget( FDPFGestureRecognizerDelegate.GetObjectID, Sel_getUid( 'handleSingleTap:' ) ) );

  if Assigned( FTapGestureRecognizer ) then
  begin
  FTapGestureRecognizer.setNumberOfTapsRequired( FNumberOfTapsRequired );
  FTapGestureRecognizer.setNumberOfTouchesRequired( FNumberOfTouchesRequired );
  FTapGestureRecognizer.setDelaysTouchesBegan( FDelaysTouchesBegan );
  FTapGestureRecognizer.setDelaysTouchesEnded( FDelaysTouchesEnded );
  FTapGestureRecognizer.setDelegate( FDPFGestureRecognizerDelegate.GetObjectID );
  FUIView.removeGestureRecognizer( FTapGestureRecognizer );
  FUIView.addGestureRecognizer( FTapGestureRecognizer );
  end;
  end
  else
  begin
  if Assigned( FTapGestureRecognizer ) then
  FUIView.removeGestureRecognizer( FTapGestureRecognizer );

  if Assigned( FDPFGestureRecognizerDelegate ) then
  FDPFGestureRecognizerDelegate.DisposeOf;
  end;

  {$ENDIF}
  end;
*)

{$IFDEF IOS}
// ------------------------------------------------------------------------------
(*
  { TDPFViewGestureRecognizerDelegate }
  constructor TDPFViewGestureRecognizerDelegate.Create( ADPFUIView: TDPFUIView );
  begin
  inherited Create;
  FDPFUIView := ADPFUIView;
  end;

  // ------------------------------------------------------------------------------
  procedure TDPFViewGestureRecognizerDelegate.handleSingleTap( gr: UITapGestureRecognizer );
  begin
  if Assigned( FDPFUIView.OnClick ) then
  FDPFUIView.OnClick( FDPFUIView );
  end;

  // ------------------------------------------------------------------------------
  function TDPFViewGestureRecognizerDelegate.gestureRecognizer( gestureRecognizer: UIGestureRecognizer; shouldReceiveTouch: UITouch ): Boolean;
  begin
  result := true;
  if FDPFUIView.FCheckReceiveTouch then
  result := shouldReceiveTouch.view.isMemberOfClass( objc_getClass( 'UIView' ) );
  end;
*)
// ------------------------------------------------------------------------------
{ TDPFView }

constructor TDPFView.Create( ADPFUIView: TDPFUIView );
var
  V: Pointer;
begin
  inherited Create;
  FDPFUIView := ADPFUIView;
  V          := UIView( Super ).initWithFrame( CGRectMake( 0, 0, 0, 0 ) );
  if GetObjectID <> V then
    UpdateObjectID( V );
end;

// ------------------------------------------------------------------------------
function TDPFView.GetObjectiveCClass: PTypeInfo;
begin
  Result := TypeInfo( DPFView );
end;

// ------------------------------------------------------------------------------
procedure TDPFView.drawRect( rect: CGRect );
begin
  UIView( Super ).drawRect( rect );
  if assigned( FDPFUIView.FOnDrawRect ) then
    FDPFUIView.FOnDrawRect( FDPFUIView, DPFNSRect( rect ) );
end;

// ------------------------------------------------------------------------------
procedure TDPFView.touchesBegan( touches: NSSet; withEvent: UIEvent ); cdecl;
var
  Touch   : UITouch;
  P, PrevP: NSPoint;
begin
  UIView( Super ).touchesBegan( touches, withEvent );

  Touch := TUITouch.Wrap( touches.anyObject );
  P     := Touch.locationInView( FDPFUIView.FUIView );
  PrevP := Touch.previousLocationInView( FDPFUIView.FUIView );
  if Assigned( FDPFUIView.FTouchesBegan ) then
    FDPFUIView.FTouchesBegan( FDPFUIView, DPFNSPoint( P ), DPFNSPoint( PrevP ) );
end;

// ------------------------------------------------------------------------------
procedure TDPFView.touchesMoved( touches: NSSet; withEvent: UIEvent ); cdecl;
var
  Touch   : UITouch;
  P, PrevP: NSPoint;
begin
  UIView( Super ).touchesMoved( touches, withEvent );

  Touch := TUITouch.Wrap( touches.anyObject );
  P     := Touch.locationInView( FDPFUIView.FUIView );
  PrevP := Touch.previousLocationInView( FDPFUIView.FUIView );
  if Assigned( FDPFUIView.FTouchesMoved ) then
    FDPFUIView.FTouchesMoved( FDPFUIView, DPFNSPoint( P ), DPFNSPoint( PrevP ) );
end;

// ------------------------------------------------------------------------------
procedure TDPFView.touchesEnded( touches: NSSet; withEvent: UIEvent ); cdecl;
var
  Touch   : UITouch;
  P, PrevP: NSPoint;
begin
  UIView( Super ).touchesEnded( touches, withEvent );

  Touch := TUITouch.Wrap( touches.anyObject );
  P     := Touch.locationInView( FDPFUIView.FUIView );
  PrevP := Touch.previousLocationInView( FDPFUIView.FUIView );
  if Assigned( FDPFUIView.FTouchesEnded ) then
    FDPFUIView.FTouchesEnded( FDPFUIView, Touch.tapCount, DPFNSPoint( P ), DPFNSPoint( PrevP ) );

  if Touch.tapCount = 1 then
    NSObject( self.Super ).performSelector( sel_getUid( 'SingleTap:' ), nil, DBL_TAP_DELAY )
  else if Touch.tapCount > 1 then
  begin
    iOSapi.CocoaTypes.TNSObject.OCClass.cancelPreviousPerformRequestsWithTarget( Self.GetObjectID );
    NSObject( self.Super ).performSelector( sel_getUid( 'DoubleTap:' ), nil, DBL_TAP_DELAY );
  end;
end;

// ------------------------------------------------------------------------------
procedure TDPFView.SingleTap( Sender: pointer ); cdecl;
begin
  if Assigned( FDPFUIView.FOnClick ) then
    FDPFUIView.FOnClick( FDPFUIView );
end;

// ------------------------------------------------------------------------------
procedure TDPFView.DoubleTap( Sender: pointer ); cdecl;
begin
  if Assigned( FDPFUIView.FOnDoubleClick ) then
    FDPFUIView.FOnDoubleClick( FDPFUIView );
end;

// ------------------------------------------------------------------------------
{$ENDIF}

end.
