// SPDX-License-Identifier: LGPL-3.0-linking-exception
{
  Created by BGRA Controls Team
  Dibo, Circular, lainz (007) and contributors.
  For detailed information see readme.txt

  Site: https://sourceforge.net/p/bgra-controls/
  Wiki: http://wiki.lazarus.freepascal.org/BGRAControls
  Forum: http://forum.lazarus.freepascal.org/index.php/board,46.0.html
}
{******************************* CONTRIBUTOR(S) ******************************
- Edivando S. Santos Brasil | mailedivando@gmail.com
  (Compatibility with delphi VCL 11/2018)

***************************** END CONTRIBUTOR(S) *****************************}
unit BCEffect;

{$I bgracontrols.inc}
{$IFDEF FPC}
{$modeswitch advancedrecords}
{$ENDIF}

interface

uses
  Classes, SysUtils, {$IFDEF FPC}LCLProc, LazUTF8, {$ELSE}Types, BGRAGraphics, GraphType, FPImage, {$ENDIF} BGRABitmapTypes;

{-- Fading --}

type
  TFadingMode = (fmSuspended, fmFadeIn, fmFadeOut, fmFadeInCycle, fmFadeOutCycle, fmFadeInOut, fmFadeOutIn);

const
  FadingModeStr: array[TFadingMode] of string = ('Suspended', 'Fade In', 'Fade Out', 'Fade In Cycle','Fade Out Cycle', 'Fade In Out', 'Fade Out In');

function StrToTFadingMode(const s: ansistring): TFadingMode;
procedure FadingModeStrList(s: TStrings);

type

  { TFading }

  TFading = record
  private
    FAlpha: byte;
    FMode: TFadingMode;
    FAlphaStep: byte;
    FDuration: integer;
    FPrevDate: TDateTime;
    FElapsedMsAccumulator: integer;
  public
    procedure SetFAlpha(AValue: byte);
    procedure SetFMode(AValue: TFadingMode);
    procedure SetFAlphaStep(AValue: byte);
    procedure SetFDuration(AValue: integer);
  public
    function Execute(AStepCount: integer= 1): byte; // execute and return new alpha
    function Reset: byte;   // reset and return new alpha
    procedure PutImage(ADestination: TBGRACustomBitmap; AX,AY: integer; ASource: TBGRACustomBitmap);
    procedure FillRect(ADestination: TBGRACustomBitmap; ARect: TRect; AColor: TBGRAPixel);
  public
    property Alpha: byte read FAlpha write SetFAlpha;
    property Mode: TFadingMode read FMode write SetFMode;
    property Step: byte read FAlphaStep write SetFAlphaStep;
    property Duration: integer read FDuration write SetFDuration;
  end;

{-- Fading --}

implementation

{-- Fading --}

function StrToTFadingMode(const s: ansistring): TFadingMode;
var
  fm: TFadingMode;
  ls: ansistring;
begin
  ls := {$IFDEF FPC}UTF8LowerCase{$ELSE}LowerCase{$ENDIF}(s);
  for fm := low(TFadingMode) to high(TFadingMode) do
    if ls = {$IFDEF FPC}UTF8LowerCase{$ELSE}LowerCase{$ENDIF}(FadingModeStr[fm]) then
    begin
      Result := fm;
      break;
    end;
  Result := fm;
end;

procedure FadingModeStrList(s: TStrings);
var
  fm: TFadingMode;
begin
  for fm := low(TFadingMode) to high(TFadingMode) do
    s.Add(FadingModeStr[fm]);
end;

{ TFading }

procedure TFading.SetFAlpha(AValue: byte);
begin
  if FAlpha = AValue then
    Exit;
  FAlpha := AValue;
end;

procedure TFading.SetFMode(AValue: TFadingMode);
begin
  if FMode = AValue then
    Exit;
  FMode := AValue;
  FPrevDate:= 0;
end;

procedure TFading.SetFAlphaStep(AValue: byte);
begin
  if FAlphaStep = AValue then
    Exit
  else
    FAlphaStep := AValue;
end;

procedure TFading.SetFDuration(AValue: integer);
begin
  FDuration:= AValue;
end;

function TFading.Execute(AStepCount: integer= 1): byte;
var curDate: TDateTime;
  alphaStep: byte;
  timeGrain: integer;
begin
  if FAlphaStep <= 0 then
    alphaStep := 1
  else
    alphaStep := FAlphaStep;

  if FDuration > 0 then
  begin
    curDate := Now;
    if FPrevDate = 0 then
    begin
      FPrevDate := curDate;
      FElapsedMsAccumulator := 0;
      result := FAlpha;
      exit;
    end;

    inc(FElapsedMsAccumulator, round((curDate-FPrevDate)*(24*60*60*1000)) );
    timeGrain := round(FDuration*alphaStep/255);
    if timeGrain <= 0 then timeGrain := 1;
    AStepCount := FElapsedMsAccumulator div timeGrain;
    FElapsedMsAccumulator:= FElapsedMsAccumulator mod timeGrain;
    FPrevDate := curDate;
  end;

  if AStepCount < 0 then AStepCount := 0
  else if AStepCount > 255 then AStepCount := 255;

  case FMode of
    fmFadeIn, fmFadeInOut, fmFadeInCycle:
    begin
      if (FAlpha = 255) and (FMode = fmFadeInCycle) then
        FAlpha := 0
      else
      if FAlpha + alphaStep*AStepCount >= 255 then
      begin
        FAlpha := 255;
        if FMode = fmFadeInOut then
          FMode := fmFadeOutIn
        else if FMode <> fmFadeInCycle then
          FMode := fmSuspended;
      end
      else
        FAlpha := FAlpha + (alphaStep*AStepCount);
    end;
    fmFadeOut,fmFadeOutIn, fmFadeOutCycle:
    begin
      if (FAlpha = 0) and (FMode = fmFadeOutCycle) then
        FAlpha := 255
      else
      if FAlpha - alphaStep*AStepCount <= 0 then
      begin
        FAlpha := 0;
        if FMode = fmFadeOutIn then
          FMode := fmFadeInOut
        else if FMode <> fmFadeOutCycle then
          FMode := fmSuspended;
      end
      else
        FAlpha := FAlpha - (alphaStep*AStepCount);
    end;
  end;

  Result := FAlpha;
end;

function TFading.Reset: byte;
begin
  case FMode of
    fmFadeIn, fmFadeInOut:
    begin
      FAlpha := 0;
    end;
    fmFadeOut,fmFadeOutIn:
    begin
      FAlpha := 255;
    end;
  end;
  Result := FAlpha;
  FPrevDate := 0;
end;

procedure TFading.PutImage(ADestination: TBGRACustomBitmap; AX, AY: integer;
  ASource: TBGRACustomBitmap);
begin
  ADestination.PutImage(AX,AY,ASource,dmDrawWithTransparency,Alpha);
end;

procedure TFading.FillRect(ADestination: TBGRACustomBitmap; ARect: TRect;
  AColor: TBGRAPixel);
begin
  ADestination.FillRect(ARect, BGRA(AColor.red,AColor.green,AColor.blue,AColor.alpha*Alpha div 255),dmDrawWithTransparency);
end;

{-- Fading --}

end.
