// ------------------------------------------------------------------------------
// DPF.iOS.GLKView 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.GLKView;

interface

{$I DPF.iOS.Defs.inc}

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

  DPF.iOS.BaseControl,
  DPF.iOS.NSTimer,
{$IFDEF IOS}
  iOSapi.GLKit,
  iOSapi.OpenGLES,
  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.Types,
  FMX.Controls,
  FMX.StdCtrls;

type

  TDPFGLKViewOnDrawRect = procedure( Sender: TObject; Rect: DPFNSRect ) of object;
  TDPFGLKView           = class;

{$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
    FDPFGLKView: TDPFGLKView;
  public
    constructor Create( ADPFGLKView: TDPFGLKView );

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

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

  TDPFBaseGLKView = class( TOCLocal )
  private
    FDPFGLKView: TDPFGLKView;
  protected
  public
    constructor Create( ADPFGLKView: TDPFGLKView );
    function GetObjectiveCClass: PTypeInfo; override;
    procedure drawRect( rect: CGRect ); overload; cdecl;
  end;

{$ENDIF}

  [ComponentPlatformsAttribute( PidWin32 or PidWin64 or pidLinux32 or pidAndroid or PidOSX32 or PidiOSDevice or PidiOSSimulator )]
  TDPFGLKView = class( TDPFiOSBaseControl )
  private
    FBackgroundColor        : TAlphaColor;
    FBackgroundImage        : string;
    FOnClick                : TDPFOnClicked;
    FNumberOfTouchesRequired: Byte;
    FNumberOfTapsRequired   : Byte;
    FDelaysTouchesBegan     : Boolean;
    FDelaysTouchesEnded     : Boolean;
    FCheckReceiveTouch      : Boolean;
    FOnDrawRect             : TDPFGLKViewOnDrawRect;
    FEnableRender           : Boolean;

    FDPFNSTimer : TDPFNSTimer;
    FRenderTimer: Longword;

    procedure SetBackgroundColor( const Value: TAlphaColor );
    procedure SetBackgroundImage( const Value: string );
    procedure SetOnClick( const Value: TDPFOnClicked );
    procedure SetEnableRender( const Value: Boolean );
    procedure RenderGL( Sender: TObject );
    procedure SetRenderTimer( const Value: Longword );
  protected
{$IFDEF IOS}
    FGLKView    : GLKView;
    FEAGLContext: EAGLContext;
    // FGLKViewDelegate: TGLKViewDelegate;
    DPFBaseGLKView: TDPFBaseGLKView;

    FTapGestureRecognizer        : UITapGestureRecognizer;
    FDPFGestureRecognizerDelegate: TDPFViewGestureRecognizerDelegate;
{$ENDIF}
    procedure Resize; override;
    procedure Move; override;
{$IFNDEF IOS}
    procedure Paint; override;
{$ENDIF}
  public
{$IFDEF IOS}
    procedure Loaded; override;
{$ENDIF}
    constructor Create( AOwner: TComponent ); override;
    destructor Destroy; override;

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

    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 SetOnClick;
    property OnDrawRect: TDPFGLKViewOnDrawRect read FOnDrawRect write FOnDrawRect;

    property RenderEnabled: Boolean read FEnableRender write SetEnableRender default false;
    property RenderTimer  : Longword read FRenderTimer write SetRenderTimer default 25;

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

implementation

// ------------------------------------------------------------------------------
{ TDPFGLKView }
constructor TDPFGLKView.Create( AOwner: TComponent );
begin
  inherited Create( AOwner );
  ControlCaption   := 'GLKView';
  FBackgroundColor := TAlphaColors.Lightsteelblue;
  Visible          := True;
  FEnableRender    := false;

  FNumberOfTapsRequired    := 1;
  FNumberOfTouchesRequired := 1;

  FDelaysTouchesBegan := true;
  DelaysTouchesEnded  := false;

  FRenderTimer := 25;

  FCheckReceiveTouch := true;

{$IFDEF IOS}
  // FGLKViewDelegate := TGLKViewDelegate.Create( self );
  FEAGLContext := TEAGLContext.Wrap( TEAGLContext.Alloc.initWithAPI( kEAGLRenderingAPIOpenGLES2 ) );
  if TOSVersion.Major >= 5 then
  begin
    DPFBaseGLKView := TDPFBaseGLKView.Create( self );
    FGLKView       := TGLKView.Wrap( DPFBaseGLKView.Super.init );
  end
  else
    FGLKView := TGLKView.Create;
  // FGLKView.setDelegate( FGLKViewDelegate.GetObjectID );
  FUIControl := FGLKView;

  FDPFNSTimer          := TDPFNSTimer.Create( nil );
  FDPFNSTimer.Enabled  := false;
  FDPFNSTimer.OnTimer  := RenderGL;
  FDPFNSTimer.Interval := FRenderTimer;

{$ENDIF}
end;

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

  if Assigned( FDPFGestureRecognizerDelegate ) then
    FDPFGestureRecognizerDelegate.DisposeOf;

  // FGLKViewDelegate.DisposeOf;
  FEAGLContext.release;
{$ENDIF}
  inherited;
end;

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

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

  FGLKView.setHidden( not Visible );

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

  SetOnClick( FOnClick );

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

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

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

{$IFNDEF IOS}

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

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

// ------------------------------------------------------------------------------
procedure TDPFGLKView.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( FGLKView ) then
  begin
    if FBackgroundImage <> '' then
    begin
      // Image2 := TUIImage.Wrap( TUIImage.OCClass.ImageNamed( NSStr( FBackgroundImage ) ) );

      Image1 := TUIImage.Wrap( TUIImage.OCClass.ImageNamed( NSStr( FBackgroundImage ) ) );
      UIGraphicsBeginImageContext( FGLKView.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( FGLKView.bounds );
      Image2 := TUIImage.Wrap( UIGraphicsGetImageFromCurrentImageContext( ) );
      UIGraphicsEndImageContext( );

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

// ------------------------------------------------------------------------------
procedure TDPFGLKView.SetRenderTimer( const Value: Longword );
begin
  FRenderTimer := Value;
  if Assigned( FDPFNSTimer ) then
  begin
    FDPFNSTimer.Interval := FRenderTimer;
    if FDPFNSTimer.Enabled then
    begin
      FDPFNSTimer.Enabled := false;
      FDPFNSTimer.Enabled := true;
    end;

  end;
end;

// ------------------------------------------------------------------------------
procedure TDPFGLKView.SetEnableRender( const Value: Boolean );
begin
  FEnableRender := Value;
  if Assigned( FDPFNSTimer ) then
  begin
    FDPFNSTimer.Interval := FRenderTimer;
    FDPFNSTimer.Enabled  := value;
  end;
end;

// ------------------------------------------------------------------------------
procedure TDPFGLKView.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 );
      FGLKView.removeGestureRecognizer( FTapGestureRecognizer );
      FGLKView.addGestureRecognizer( FTapGestureRecognizer );
    end;
  end
  else
  begin
    if Assigned( FTapGestureRecognizer ) then
      FGLKView.removeGestureRecognizer( FTapGestureRecognizer );

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

{$ENDIF}
end;

// ------------------------------------------------------------------------------
procedure TDPFGLKView.RenderGL( Sender: TObject );
begin
{$IFDEF IOS}
  if Assigned( FGLKView ) then
    FGLKView.display;
{$ENDIF}
end;

{$IFDEF IOS}

// ------------------------------------------------------------------------------
{ TDPFViewGestureRecognizerDelegate }
constructor TDPFViewGestureRecognizerDelegate.Create( ADPFGLKView: TDPFGLKView );
begin
  inherited Create;
  FDPFGLKView := ADPFGLKView;
end;

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

// ------------------------------------------------------------------------------
function TDPFViewGestureRecognizerDelegate.gestureRecognizer( gestureRecognizer: UIGestureRecognizer; shouldReceiveTouch: UITouch ): Boolean;
begin
  result := true;
  if FDPFGLKView.FCheckReceiveTouch then
    result := shouldReceiveTouch.view.isMemberOfClass( objc_getClass( 'GLKView' ) );
end;

// ------------------------------------------------------------------------------
{ TDPFBaseGLKView }

constructor TDPFBaseGLKView.Create( ADPFGLKView: TDPFGLKView );
var
  V: Pointer;
begin
  inherited create;
  FDPFGLKView := ADPFGLKView;
  V           := GLKView( Super ).initWithFrame( CGRectMake( 0, 0, 0, 0 ), FDPFGLKView.FEAGLContext );
  if GetObjectID <> V then
    UpdateObjectID( V );
end;

// ------------------------------------------------------------------------------
function TDPFBaseGLKView.GetObjectiveCClass: PTypeInfo;
begin
  Result := TypeInfo( DPFBaseGLKView );
end;

// ------------------------------------------------------------------------------
procedure TDPFBaseGLKView.drawRect( rect: CGRect );
begin
  GLKView( Super ).drawRect( rect );
  if assigned( FDPFGLKView.FOnDrawRect ) then
    FDPFGLKView.FOnDrawRect( FDPFGLKView, DPFNSRect( rect ) );
end;

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

end.
