{**********************************************}
{   TArrowTool and editor dialog               }
{   Copyright (c) 2000-2020 by Steema Software }
{**********************************************}
unit TeeArrowTool;
{$I TeeDefs.inc}

interface

uses
  {$IFNDEF LINUX}
  {$IFNDEF FMX}
  Windows, Messages,
  {$ENDIF}
  {$ENDIF}
  SysUtils, Classes,
   
  {$IFDEF D17}
  System.UIConsts,
  {$ENDIF}
  
  {$IFDEF FMX}
  System.UITypes, FMX.Types,

  {$IFDEF D19}
  FMX.Graphics,
  {$ENDIF}

  {$ELSE}

  {$IFDEF D16}
  System.UITypes,
  {$ENDIF}

  Graphics, Controls,
  {$ENDIF}

  {$IFDEF D6}
  Types,
  {$ENDIF}

  {$IFDEF FMX}
  FMXTee.Canvas, FMXTee.Engine
  {$ELSE}
  TeEngine, TeCanvas
  {$ENDIF}
  ;

type
  TPointXY=class(TPersistent)
  private
    IOwner : TCustomChartElement;
    function GetX: Integer;
    function GetY: Integer;
    procedure SetX(const Value: Integer);
    procedure SetY(const Value: Integer);
  public
    Point : TPoint;

    procedure Assign(Source:TPersistent); override;
  published
    property X:Integer read GetX write SetX default 0;
    property Y:Integer read GetY write SetY default 0;
  end;

  TArrowTool=class(TTeeCustomTool)
  private
    FAngle    : Double;
    FDraw2D   : Boolean;
    FFilled   : Boolean;
    FFrom     : TPointXY;
    FHeight   : Integer;
    FPercent  : Double;
    FRotateGradient : Boolean;
    FSelected : Boolean;
    FTo       : TPointXY;
    FTransp   : TTeeTransparency;
    FWidth    : Integer;
    FZ        : Integer;

    procedure CalcPoints(out AFrom,ATo:TPoint);
    function GetGradient:TTeeGradient;
    procedure SetAngle(const Value: Double);
    procedure SetFilled(const Value: Boolean);
    procedure SetHeight(const Value: Integer);
    procedure SetWidth(const Value: Integer);
    procedure SetZ(const Value: Integer);
    procedure SetPercent(const Value: Double);
    procedure SetRotateGradient(const Value: Boolean);
    procedure SetGradient(const Value: TTeeGradient);
    procedure SetSelected(const Value: Boolean);
    procedure SetDraw2D(const Value: Boolean);
    procedure SetFrom(const Value: TPointXY);
    procedure SetTo(const Value: TPointXY);
    procedure SetTransp(const Value: TTeeTransparency);
  protected
    Procedure ChartEvent(AEvent:TChartToolEvent); override;
    class Function GetEditorClass:String; override;
  public
    Constructor Create(AOwner:TComponent); override;
    Destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;
    function Clicked(X,Y:Integer):Boolean;
    class Function Description:String; override;
    class Function LongDescription:String; override;

    property Selected:Boolean read FSelected write SetSelected;
  published
    property Active;
    property Angle:Double read FAngle write SetAngle;
    property Brush;
    property Draw2D:Boolean read FDraw2D write SetDraw2D default False;
    property Filled:Boolean read FFilled write SetFilled default True;
    property FromPoint:TPointXY read FFrom write SetFrom;
    property Gradient:TTeeGradient read GetGradient write SetGradient;
    property Height:Integer read FHeight write SetHeight default 20;
    property ParentChart;
    property Pen;
    property Percent:Double read FPercent write SetPercent;
    property RotateGradient:Boolean read FRotateGradient write SetRotateGradient default True;
    property ToPoint:TPointXY read FTo write SetTo;
    property Transparency:TTeeTransparency read FTransp write SetTransp default 0;
    property Width:Integer read FWidth write SetWidth default 20;
    property Z:Integer read FZ write SetZ default 0;
  end;

implementation

uses
  Math,
  {$IFDEF FMX}
  FMXTee.Procs, FMXTee.Chart, FMXTee.ProConstants
  {$ELSE}
  TeeProcs, Chart, TeeProCo
  {$ENDIF}
  ;

{$IFDEF FMX}
const
  clBlack=claBlack;
  clSilver=claSilver;
{$ENDIF}

{ TArrowTool }
Constructor TArrowTool.Create(AOwner: TComponent);
begin
  inherited;

  FFilled:=True;
  FRotateGradient:=True;
  
  FHeight:=20;
  FWidth:=20;
  FPercent:=25;

  FFrom:=TPointXY.Create;
  FFrom.IOwner:=Self;

  FTo:=TPointXY.Create;
  FTo.IOwner:=Self;

  // Initialize From/To

  if (csDesigning in ComponentState) and
     ( (not Assigned(Owner)) or (not (csLoading in Owner.ComponentState))) then
  begin
    FFrom.Point:=TeePoint(100,50);
    FTo.Point:=TeePoint(200,100);
  end;
end;

Destructor TArrowTool.Destroy;
begin
  FTo.Free;
  FFrom.Free;
  inherited;
end;

procedure TArrowTool.Assign(Source: TPersistent);
begin
  if Source is TArrowTool then
  with TArrowTool(Source) do
  begin
    Self.FAngle:=FAngle;
    Self.FDraw2D:=FDraw2D;
    Self.FFilled:=FFilled;
    Self.FromPoint:=FromPoint;
    Self.Gradient:=Gradient;
    Self.FHeight:=FHeight;
    Self.FPercent:=FPercent;
    Self.FRotateGradient:=FRotateGradient;
    Self.ToPoint:=ToPoint;
    Self.FTransp:=FTransp;
    Self.FWidth:=FWidth;
    Self.FZ:=FZ;
  end;

  inherited;
end;

procedure TArrowTool.CalcPoints(out AFrom,ATo:TPoint);
var tmpCenter : TPoint;

  procedure RotatePoint(var P:TPoint);
  var tmpS,
      tmpC : Extended;
      tmpX, tmpY : Extended;
  begin
    SinCos(Angle*TeePiStep,tmpS,tmpC);

    tmpX:=P.X-tmpCenter.X;
    tmpY:=P.Y-tmpCenter.Y;

    P.X:=tmpCenter.X+Round(tmpX*tmpC+tmpY*tmpS);
    P.Y:=tmpCenter.Y+Round(-tmpX*tmpS+tmpY*tmpC);
  end;

begin
  AFrom:=FromPoint.Point;
  ATo:=ToPoint.Point;

  if Angle<>0 then
  begin
    tmpCenter.X:=(AFrom.X+ATo.X) {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF};
    tmpCenter.Y:=(AFrom.Y+ATo.Y) {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF};

    RotatePoint(AFrom);
    RotatePoint(ATo);
  end;
end;

procedure TArrowTool.ChartEvent(AEvent: TChartToolEvent);

  Procedure DrawHandle(P:TPoint);
  begin
    with ParentChart.Canvas do
    begin
      Brush.Style:=bsSolid;

      if ParentChart.Color=clBlack then Brush.Color:=clSilver
                                   else Brush.Color:=clBlack;

      Gradient.Visible:=False;

      Pen.Style:=psClear;
      RectangleWithZ(RectFromCenter(P,3),0);
    end;
  end;

var
  tmpFrom  : TPoint;
  tmpTo    : TPoint;
  tmpP     : TPointArray;
  tmpBlend : TTeeBlend;
  tmpR     : TRect;
begin
  inherited;

  tmpP:=nil;

  CalcPoints(tmpFrom,tmpTo);

  if AEvent=cteAfterDraw then
  begin
    ParentChart.Canvas.AssignVisiblePen(Pen);
    ParentChart.Canvas.AssignBrush(Brush);

    if RotateGradient and Gradient.Visible and (Angle<>0) then
       with ParentChart.Canvas.Gradient do
            Angle:=Angle-Round(Self.Angle);

    if Draw2D or (not ParentChart.View3D) then
       tmpP:=ParentChart.Canvas.GetArrowPoints(Percent,tmpFrom,tmpTo,Width,Height)
    else
       tmpP:=ParentChart.Canvas.GetArrowPoints(Percent,tmpFrom,tmpTo,Width,Height,Z);

    if FTransp>0 then
    begin
      if ParentChart.Canvas.SupportsAlpha then
         tmpBlend:=ParentChart.Canvas.BeginBlending(TeeZeroRect,FTransp)
      else
      begin
        tmpR:=PolygonBounds(tmpP);
        tmpR.Right:=tmpR.Right+1;
        tmpR.Bottom:=tmpR.Bottom+1;

        tmpBlend:=ParentChart.Canvas.BeginBlending(tmpR,FTransp);
      end;
    end
    else
      tmpBlend:=nil;

    if Draw2D or (not ParentChart.View3D) then
       ParentChart.Canvas.Arrow(Filled,Percent,tmpFrom,tmpTo,Width,Height)
    else
       ParentChart.Canvas.Arrow(Filled,tmpFrom,tmpTo,Width,Height,Z-5,Z+5,Percent);

    if FTransp>0 then
       ParentChart.Canvas.EndBlending(tmpBlend);

    tmpP:=nil;

    if Selected then
    begin
      DrawHandle(tmpFrom);
      DrawHandle(tmpTo);
    end;
  end;
end;

class function TArrowTool.Description: String;
begin
  result:=TeeMsg_ToolArrow;
end;

class function TArrowTool.LongDescription: String;
begin
  result:=TeeMsg_ToolArrowDesc;
end;

function TArrowTool.GetGradient:TTeeGradient;
begin
  result:=Brush.Gradient;
end;

procedure TArrowTool.SetAngle(const Value: Double);
begin
  SetDoubleProperty(FAngle,Value);
end;

procedure TArrowTool.SetFilled(const Value: Boolean);
begin
  SetBooleanProperty(FFilled,Value);
end;

procedure TArrowTool.SetHeight(const Value: Integer);
begin
  SetIntegerProperty(FHeight,Value);
end;

procedure TArrowTool.SetWidth(const Value: Integer);
begin
  SetIntegerProperty(FWidth,Value);
end;

procedure TArrowTool.SetZ(const Value: Integer);
begin
  SetIntegerProperty(FZ,Value);
end;

procedure TArrowTool.SetPercent(const Value: Double);
begin
  SetDoubleProperty(FPercent,Value);
end;

class function TArrowTool.GetEditorClass: String;
begin
  result:='TArrowToolEditor';
end;

procedure TArrowTool.SetGradient(const Value: TTeeGradient);
begin
  Brush.Gradient:=Value;
end;

procedure TArrowTool.SetSelected(const Value: Boolean);
begin
  SetBooleanProperty(FSelected,Value);
end;

function TArrowTool.Clicked(X, Y: Integer): Boolean;
var tmpP : TPointArray;
    tmp,
    tmpFrom,
    tmpTo : TPoint;
begin
  CalcPoints(tmpFrom,tmpTo);

  tmpP:=ParentChart.Canvas.GetArrowPoints(Percent,tmpFrom,tmpTo,Width,Height {,Z});
  try
    tmp:=TeePoint(X,Y);

    if Filled then
       result:=PointInPolygon(tmp,tmpP)
    else
       result:=PointInLine(tmp,tmpFrom,tmpTo,3) or
               PointInLine(tmp,tmpTo,tmpP[5],3) or
               PointInLine(tmp,tmpTo,tmpP[3],3);
  finally
    tmpP:=nil;
  end;
end;

procedure TArrowTool.SetDraw2D(const Value: Boolean);
begin
  SetBooleanProperty(FDraw2D,Value);
end;

procedure TArrowTool.SetFrom(const Value: TPointXY);
begin
  FFrom.Assign(Value);
end;

procedure TArrowTool.SetTo(const Value: TPointXY);
begin
  FTo.Assign(Value);
end;

procedure TArrowTool.SetTransp(const Value: TTeeTransparency);
begin
  if FTransp<>Value then
  begin
    FTransp:=Value;
    Repaint;
  end;
end;

procedure TArrowTool.SetRotateGradient(const Value: Boolean);
begin
  SetBooleanProperty(FRotateGradient,Value);
end;

{ TPointXY }

procedure TPointXY.Assign(Source: TPersistent);
begin
  if Source is TPointXY then
     Point:=TPointXY(Source).Point
  else
     inherited;
end;

function TPointXY.GetX: Integer;
begin
  result:={$IFDEF FMX}Round{$ENDIF}(Point.X);
end;

function TPointXY.GetY: Integer;
begin
  result:={$IFDEF FMX}Round{$ENDIF}(Point.Y);
end;

type
  TCustomAccess=class(TCustomChartElement);

procedure TPointXY.SetX(const Value: Integer);
begin
  {$IFDEF FMX}
  if Point.X<>Value then
  begin
    Point.X:=Value;
    IOwner.Repaint;
  end;
  {$ELSE}
  TCustomAccess(IOwner).SetIntegerProperty(Point.X,Value);
  {$ENDIF}
end;

procedure TPointXY.SetY(const Value: Integer);
begin
  {$IFDEF FMX}
  if Point.Y<>Value then
  begin
    Point.Y:=Value;
    IOwner.Repaint;
  end;
  {$ELSE}
  TCustomAccess(IOwner).SetIntegerProperty(Point.Y,Value);
  {$ENDIF}
end;

initialization
  RegisterTeeTools([TArrowTool]);
finalization
  UnRegisterTeeTools([TArrowTool]);
end.
