{
 *****************************************************************************
  See the file COPYING.modifiedLGPL.txt, included in this distribution,
  for details about the license.
 *****************************************************************************

 Author: Boban Spasic
}

unit BCLeaBoard;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, LResources, Controls, Graphics,
  BGRABitmapTypes, BGRABitmap, BGRAGradients, BCLeaTheme, BCLeaTypes;

type
  TBCLeaBoard = class(TCustomControl)
  private
    FBitmap: TBGRABitmap;
    FTheme: TBCLeaTheme;
    FFrameColor: TColor;
    FBoardColor: TColor;
    FBkgColor: TColor;
    FFrameStyle: TZStyle;
    FBoardStyle: TZStyle;
    FAltitude: integer;
    FRounding: integer;
    FFrameHeight: integer;
    FFrameDistance: integer;
    //global intensity of the light
    FLightSourceIntensity: single;
    //minimum distance always added (positive value)
    FLightSourceDistanceTerm: single;
    //how much actual distance is taken into account (usually 0 or 1)
    FLightSourceDistanceFactor: single;
    //how much the location of the lightened pixel is taken into account (usually 0 or 1)
    FLightDestFactor: single;
    //color of the light reflection
    FLightColor: TColor;
    //how much light is reflected (0..1)
    FSpecularFactor: single;
    //how concentrated reflected light is (positive value)
    FSpecularIndex: single;
    //ambiant lighting whereever the point is (0..1)
    FAmbientFactor: single;
    //diffusion, i.e. how much pixels are lightened by light source (0..1)
    FDiffusionFactor: single;
    //how much hidden surface are darkened (0..1)
    FNegativeDiffusionFactor: single;
    //when diffusion saturates, use light color to show it
    FDiffuseSaturation: boolean;
    FLightPositionX: integer;
    FLightPositionY: integer;
    FLightPositionZ: integer;
    procedure SetAltitude(AValue: integer);
    procedure SetFrameColor(AValue: TColor);
    procedure SetBoardColor(AValue: TColor);
    procedure SetBkgColor(AValue: TColor);
    procedure SetFrameStyle(AValue: TZStyle);
    procedure SetBoardStyle(AValue: TZStyle);
    procedure SetTheme(AValue: TBCLeaTheme);
    procedure SetRounding(AValue: integer);
    procedure SetFrameHeight(AValue: integer);
    procedure SetFrameDistance(AValue: integer);
  protected
    procedure SetEnabled(Value: boolean); override;
    procedure SetVisible(Value: boolean); override;
    procedure Paint; override;
    procedure Redraw;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure UpdateTheme;
    procedure ApplyTheme;
    procedure SaveThemeToFile(AFileName: string);
    procedure LoadThemeFromFile(AFileName: string);
    procedure ApplyDefaultTheme;
  published
    property Align;
    property Cursor;
    property Enabled;
    property Font;
    property PopupMenu;
    property ShowHint;
    property TabOrder;
    property TabStop default True;
    property Anchors;
    property Constraints;
    property Visible;
    property OnClick;
    property OnDblClick;
    property OnEnter;
    property OnExit;
    property OnContextPopup;
    property FrameColor: TColor read FFrameColor write SetFrameColor default clBtnFace;
    property BoardColor: TColor read FBoardColor write SetBoardColor default clBtnFace;
    property BackgroundColor: TColor read FBkgColor write SetBkgColor default clBtnFace;
    property FrameStyle: TZStyle read FFrameStyle write SetFrameStyle default zsRaised;
    property BoardStyle: TZStyle read FBoardStyle write SetBoardStyle default zsFlat;
    property Theme: TBCLeaTheme read FTheme write SetTheme;
    property Altitude: integer read FAltitude write SetAltitude default 2;
    property Rounding: integer read FRounding write SetRounding default 10;
    property FrameHeight: integer read FFrameHeight write SetFrameHeight default 10;
    property FrameDistance: integer read FFrameDistance write SetFrameDistance default 3;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('BGRA Controls', [TBCLeaBoard]);
end;

constructor TBCLeaBoard.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  with GetControlClassDefaultSize do
    SetInitialBounds(0, 0, 200, 150);
  ControlStyle := [csAcceptsControls, csReplicatable, csClickEvents];
  ApplyDefaultTheme;
  FBitmap := TBGRABitmap.Create(Width, Height, FBkgColor);
end;

destructor TBCLeaBoard.Destroy;
begin
  FreeAndNil(FBitmap);
  inherited Destroy;
end;

procedure TBCLeaBoard.SetEnabled(Value: boolean);
begin
  inherited SetEnabled(Value);
  Invalidate;
end;

procedure TBCLeaBoard.SetVisible(Value: boolean);
begin
  inherited SetVisible(Value);
  Invalidate;
end;

procedure TBCLeaBoard.Paint;
begin
  inherited Paint;
  Redraw;
end;

procedure TBCLeaBoard.SetFrameStyle(AValue: TZStyle);
begin
  if FFrameStyle = AValue then
    Exit;
  FFrameStyle := AValue;
  Invalidate;
end;

procedure TBCLeaBoard.SetBoardStyle(AValue: TZStyle);
begin
  if FBoardStyle = AValue then
    Exit;
  FBoardStyle := AValue;
  Invalidate;
end;

procedure TBCLeaBoard.SetFrameColor(AValue: TColor);
begin
  if FFrameColor = AValue then
    Exit;
  FFrameColor := AValue;
  Invalidate;
end;

procedure TBCLeaBoard.SetBoardColor(AValue: TColor);
begin
  if FBoardColor = AValue then
    Exit;
  FBoardColor := AValue;
  Invalidate;
end;

procedure TBCLeaBoard.SetBkgColor(AValue: TColor);
begin
  if FBkgColor = AValue then
    Exit;
  FBkgColor := AValue;
  Invalidate;
end;

procedure TBCLeaBoard.SetAltitude(AValue: integer);
begin
  if FAltitude = AValue then
    Exit;
  FAltitude := AValue;
  Invalidate;
end;

procedure TBCLeaBoard.SetRounding(AValue: integer);
begin
  if FRounding = AValue then
    Exit;
  FRounding := AValue;
  Invalidate;
end;

procedure TBCLeaBoard.SetTheme(AValue: TBCLeaTheme);
begin
  if FTheme = AValue then
    Exit;
  FTheme := AValue;
  Invalidate;
end;

procedure TBCLeaBoard.SetFrameDistance(AValue: integer);
begin
  if FFrameDistance = AValue then
    Exit;
  FFrameDistance := AValue;
  Invalidate;
end;

procedure TBCLeaBoard.SetFrameHeight(AValue: integer);
begin
  if FFrameHeight = AValue then
    Exit;
  FFrameHeight := AValue;
  Invalidate;
end;

procedure TBCLeaBoard.ApplyDefaultTheme;
begin
  FFrameColor := clBtnFace;
  FBoardColor := clBtnFace;
  FBkgColor := clBtnFace;
  FFrameStyle := zsRaised;
  FBoardStyle := zsFlat;
  FFrameHeight := 10;
  FFrameDistance := 3;
  FAltitude := 2;
  FRounding := 10;
  FAmbientFactor := 0.3;
  FSpecularIndex := 10;
  FSpecularFactor := 0.6;
  FLightDestFactor := 1;
  FLightPositionX := -100;
  FLightPositionY := -100;
  FLightPositionZ := 100;
  FLightSourceIntensity := 500;
  FLightSourceDistanceTerm := 150;
  FLightSourceDistanceFactor := 1;
  FNegativeDiffusionFactor := 0.1;
  FLightColor := clWhite;
  FDiffuseSaturation := False;
  FDiffusionFactor := 0.9;
end;

procedure TBCLeaBoard.UpdateTheme;
begin
  if Assigned(FTheme) then
  begin
    FTheme.BRD_FrameColor := FFrameColor;
    FTheme.BRD_BoardColor := FBoardColor;
    FTheme.BRD_BkgColor := FBkgColor;
    FTheme.BRD_FrameStyle := FFrameStyle;
    FTheme.BRD_BoardStyle := FBoardStyle;
    FTheme.BRD_FrameHeight := FFrameHeight;
    FTheme.BRD_FrameDistance := FFrameDistance;
    FTheme.BRD_Altitude := FAltitude;
    FTheme.BRD_Rounding := FRounding;
  end;
end;

procedure TBCLeaBoard.ApplyTheme;
begin
  if Assigned(FTheme) then
  begin
    FFrameColor := FTheme.BRD_FrameColor;
    FBoardColor := FTheme.BRD_BoardColor;
    FBkgColor := FTheme.BRD_BkgColor;
    FFrameStyle := FTheme.BRD_FrameStyle;
    FBoardStyle := FTheme.BRD_BoardStyle;
    FFrameHeight := FTheme.BRD_FrameHeight;
    FFrameDistance := FTheme.BRD_FrameDistance;
    FAltitude := FTheme.BRD_Altitude;
    FRounding := FTheme.BRD_Rounding;
    FLightSourceIntensity := FTheme.COM_LightSourceIntensity;
    FLightSourceDistanceTerm := FTheme.COM_LightSourceDistanceTerm;
    FLightSourceDistanceFactor := FTheme.COM_LightSourceDistanceFactor;
    FLightDestFactor := FTheme.COM_LightDestFactor;
    FLightColor := FTheme.COM_LightColor;
    FSpecularFactor := FTheme.COM_SpecularFactor;
    FSpecularIndex := FTheme.COM_SpecularIndex;
    FAmbientFactor := FTheme.COM_AmbientFactor;
    FDiffusionFactor := FTheme.COM_DiffusionFactor;
    FNegativeDiffusionFactor := FTheme.COM_NegativeDiffusionFactor;
    FDiffuseSaturation := FTheme.COM_DiffuseSaturation;
    FLightPositionX := FTheme.COM_LightPositionX;
    FLightPositionY := FTheme.COM_LightPositionY;
    FLightPositionZ := FTheme.COM_LightPositionZ;
    Invalidate;
  end
  else
  begin
    ApplyDefaultTheme;
  end;
end;

procedure TBCLeaBoard.SaveThemeToFile(AFileName: string);
begin
  if Assigned(FTheme) then
    FTheme.SaveThemeToFile(AFileName);
end;

procedure TBCLeaBoard.LoadThemeFromFile(AFileName: string);
begin
  if Assigned(FTheme) then
    FTheme.LoadThemeFromFile(AFileName);
end;

procedure TBCLeaBoard.Redraw;
var
  EffectiveSize: integer;
  Blur: TBGRABitmap;
  Mask, TmpBitmap: TBGRABitmap;
  Phong: TPhongShading;
  ScaledPhongSize{, ScaledSize}: integer;

  procedure DoDrawFrame(AFrameColor, ABoardColor: TColor);
  var
    d: integer;
  begin
    d := FFrameDistance;
    FBitmap.FillRoundRectAntialias(d, d, Width - d, Height - d, FRounding, FRounding, AFrameColor);

    d := FFrameDistance + FFrameHeight + FAltitude;
    FBitmap.FillRoundRectAntialias(d, d, Width - d, Height - d, FRounding, FRounding, ABoardColor);

    if (FFrameStyle = zsRaised) or (FFrameStyle = zsLowered) then
    begin
      Mask := FBitmap.FilterGrayscale as TBGRABitmap;
      if (FFrameStyle = zsRaised) then
        Mask.Negative;
      Blur := Mask.FilterBlurRadial(ScaledPhongSize, ScaledPhongSize, rbFast) as TBGRABitmap;
      Blur.FillMask(0, 0, Mask, BGRAPixelTransparent, dmSet);
      Mask.Free;

      Phong := TPhongShading.Create;
      if assigned(FTheme) then
      begin
        Phong.AmbientFactor := FAmbientFactor;
        Phong.SpecularIndex := FSpecularIndex;
        Phong.LightDestFactor := FLightDestFactor;
        Phong.LightPosition := Point(FLightPositionX, FLightPositionY);
        Phong.LightPositionZ := FLightPositionZ;
        Phong.LightSourceIntensity := FLightSourceIntensity;
        Phong.LightSourceDistanceTerm := FLightSourceDistanceTerm;
        Phong.LightSourceDistanceFactor := FLightSourceDistanceFactor;
        Phong.NegativeDiffusionFactor := FNegativeDiffusionFactor;
        Phong.SpecularFactor := FSpecularFactor;
        Phong.DiffusionFactor := FDiffusionFactor;
        Phong.DiffuseSaturation := FDiffuseSaturation;
        Phong.LightColor := FLightColor;
      end;
      Mask := TBGRABitmap.Create(Width, Height, ColorToBGRA(ColorToRGB(BGRABlack)));
      d := FFrameDistance;
      Mask.FillRoundRectAntialias(d, d, Width - d, Height - d, FRounding, FRounding, BGRAWhite);
      d := FFrameDistance + FFrameHeight + FAltitude;
      FBitmap.FillRoundRectAntialias(d, d, Width - d, Height - d, FRounding, FRounding, BGRABlack);
      TmpBitmap := TBGRABitmap.Create(Width, Height, ColorToBGRA(ColorToRGB(BGRABlack)));
      Phong.Draw(TmpBitmap, Blur, FAltitude, 0, 0, FBitmap);
      Phong.Free;
      Blur.Free;
      TmpBitmap.ApplyMask(Mask);
      FBitmap.PutImage(0,0,TmpBitmap, dmDrawWithTransparency);
      TmpBitmap.Free;
      Mask.Free;
    end;
  end;

  procedure DoDrawBoard(AValue: TColor);
  var
    d: integer;
  begin
    d := FFrameDistance + FFrameHeight + FAltitude;
    FBitmap.FillRoundRectAntialias(d, d, Width - d, Height - d, FRounding, FRounding, AValue);
  end;

begin
  FBitmap.SetSize(Width, Height);
  FBitmap.Fill(FBkgColor);

  if Width < Height then
    EffectiveSize := Width
  else
    EffectiveSize := Height;
  if EffectiveSize < 2 then exit;
  //ScaledSize := Scale96ToForm(FSize);
  ScaledPhongSize := Scale96ToForm(5);

  if Enabled then
  begin
    DoDrawFrame(FFrameColor, FBoardColor);
    if FBoardStyle = zsFlat then
      DoDrawBoard(FBoardColor);
  end
  else
  begin
    DoDrawFrame(clDkGray, clGray);
    if FBoardStyle = zsFlat then
      DoDrawBoard(clGray);
  end;

  FBitmap.Draw(Canvas, 0, 0, True);
end;

end.
