unit SLColorPicker;

{$MODE DELPHI}

interface

uses
  LCLIntf, LCLType, LMessages,
  SysUtils, Classes, Controls, Graphics, Math, Forms,
  mbColorPickerControl;

type
  TSLColorPicker = class(TmbColorPickerControl)
  private
    FHue, FSat, FLum: Double;
    FMaxHue, FMaxSat, FMaxLum: integer;
    FChange: boolean;
    procedure DrawMarker(x, y: integer);
    procedure SelectionChanged(x, y: integer);
    procedure UpdateCoords;
    function GetHue: Integer;
    function GetLum: Integer;
    function GetSat: Integer;
    procedure SetHue(H: integer);
    procedure SetSat(S: integer);
    procedure SetLum(L: integer);
    procedure SetMaxHue(H: Integer);
    procedure SetMaxLum(L: Integer);
    procedure SetMaxSat(S: Integer);
  protected
    function GetGradientColor2D(X, Y: Integer): TColor; override;
    function GetSelectedColor: TColor; override;
    procedure SetSelectedColor(c: TColor); override;
    procedure Paint; override;
    procedure Resize; override;
    procedure CreateWnd; override;
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
    procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
  public
    constructor Create(AOwner: TComponent); override;
    function GetColorAtPoint(x, y: integer): TColor; override;
    property ColorUnderCursor;
  published
    property Hue: integer read GetHue write SetHue;
    property Saturation: integer read GetSat write SetSat;
    property Luminance: integer read GetLum write SetLum;
    property MaxHue: Integer read FMaxHue write SetMaxHue default 359;
    property MaxSaturation: Integer read FMaxSat write SetMaxSat default 240;
    property MaxLuminance: Integer read FMaxLum write SetMaxLum default 240;
    property SelectedColor default clWhite;
    property MarkerStyle default msCircle;
    property OnChange;
  end;

implementation

uses
  ScanLines, RGBHSLUtils, HTMLColors, mbUtils;

{ TSLColorPicker }

constructor TSLColorPicker.Create(AOwner: TComponent);
begin
  inherited;
  FMaxHue := 359;
  FMaxSat := 240;
  FMaxLum := 240;
  FGradientWidth := FMaxSat + 1;       // x --> Saturation
  FGradientHeight := FMaxLum + 1;      // y --> Luminance
  SetInitialBounds(0, 0, FGradientWidth, FGradientHeight);
  FHue := 0.0;
  FSat := 0.0;
  FLum := 1.0;
  FChange := true;
  MarkerStyle := msCircle;
end;

procedure TSLColorPicker.CreateWnd;
begin
  inherited;
  CreateGradient;
  UpdateCoords;
end;

procedure TSLColorPicker.DrawMarker(x, y: integer);
var
  c: TColor;
begin
  c := not GetColorAtPoint(x, y);     // "not" --> invert color bits
  InternalDrawMarker(x, y, c);
end;

function TSLColorPicker.GetColorAtPoint(x, y: integer): TColor;
begin
  Result := HSLToRGB(FHue, x/(Width - 1), (Height - 1 - y) / (Height - 1));
  if WebSafe then
    Result := GetWebSafe(Result);
end;

{ This picker has Saturation along the X and Luminance along the Y axis. }
function TSLColorPicker.GetGradientColor2D(X, Y: Integer): TColor;
begin
  Result := HSLtoColor(FHue, x / FMaxSat, (FMaxLum - y) / FMaxLum);
//  Result := HSLtoRGB(FHue, x / FMaxSat, (FMaxLum - y) / FMaxLum);
end;

function TSLColorPicker.GetHue: Integer;
begin
  Result := round(FHue * FMaxHue);
end;

function TSLColorPicker.GetLum: Integer;
begin
  Result := round(FLum * FMaxLum);
end;

function TSLColorPicker.GetSat: Integer;
begin
  Result := round(FSat * FMaxSat);
end;

function TSLColorPicker.GetSelectedColor: TColor;
begin
  Result := HSLtoRGB(FHue, FSat, FLum);
  if WebSafe then
    Result := GetWebSafe(Result);
end;

procedure TSLColorPicker.KeyDown(var Key: Word; Shift: TShiftState);
var
  eraseKey: Boolean;
  delta: Integer;
begin
  eraseKey := true;
  if ssCtrl in Shift then
    delta := 10
  else
    delta := 1;

  case Key of
    VK_LEFT:
      if (mdx - delta >= 0) then
      begin
        Dec(mdx, delta);
        SelectionChanged(mdx, mdy);
        FManual := true;
        if Assigned(FOnChange) then FOnChange(Self);
      end;
    VK_RIGHT:
      if (mdx + delta < Width) then
      begin
        Inc(mdx, delta);
        SelectionChanged(mdx, mdy);
        FManual := true;
        if Assigned(FOnChange) then FOnChange(Self);
      end;
    VK_UP:
      if (mdy - delta >= 0) then
      begin
        Dec(mdy, delta);
        SelectionChanged(mdx, mdy);
        FManual := true;
        if Assigned(FOnChange) then FOnChange(Self);
      end;
    VK_DOWN:
      if (mdy + delta < Height) then
      begin
        Inc(mdy, delta);
        SelectionChanged(mdx, mdy);
        FManual := true;
        if Assigned(FOnChange) then FOnChange(Self);
      end;
    else
      eraseKey := false;
  end;

  if eraseKey then
    Key := 0;

  inherited;
end;

procedure TSLColorPicker.MouseDown(Button: TMouseButton; Shift: TShiftState;
  X, Y: Integer);
begin
  inherited;
  if csDesigning in ComponentState then
    Exit;
  if (Button = mbLeft) and PtInRect(ClientRect, Point(x, y)) then
  begin
    mdx := x;
    mdy := y;
    SelectionChanged(X, Y);
  end;
  SetFocus;
end;

procedure TSLColorPicker.MouseMove(Shift: TShiftState; X, Y: Integer);
begin
  inherited;
  if csDesigning in ComponentState then
    Exit;
  if (ssLeft in Shift) and PtInRect(ClientRect, Point(x, y)) then
  begin
    mdx := x;
    mdy := y;
    SelectionChanged(X, Y);
  end;
end;

procedure TSLColorPicker.MouseUp(Button: TMouseButton; Shift: TShiftState;
  X, Y: Integer);
begin
  inherited;
  if csDesigning in ComponentState then Exit;
  if (Button = mbLeft) and PtInRect(ClientRect, Point(x, y)) then
  begin
    mdx := x;
    mdy := y;
    SelectionChanged(X, Y);
    FManual := true;
    if Assigned(FOnChange) then FOnChange(Self);
  end;
end;

procedure TSLColorPicker.Paint;
begin
  Canvas.StretchDraw(ClientRect, FBufferBMP);
  UpdateCoords;
  DrawMarker(mdx, mdy);
end;

procedure TSLColorPicker.Resize;
begin
  inherited;
  UpdateCoords;
end;

procedure TSLColorPicker.SelectionChanged(x, y: integer);
begin
  FChange := false;
  FSat := x / (Width - 1);
  FLum := (Height - y - 1) / (Height - 1);
  FManual := false;
  UpdateCoords;
  Invalidate;
  if FChange and Assigned(FOnChange) then FOnChange(Self);
  FChange := true;
end;

procedure TSLColorPicker.SetHue(H: integer);
begin
  Clamp(H, 0, FMaxHue);
  if GetHue() <> H then
  begin
    FHue := h / FMaxHue;
    FManual := false;
    CreateGradient;
    UpdateCoords;
    Invalidate;
    if FChange and Assigned(FOnChange) then FOnChange(Self);
  end;
end;

procedure TSLColorPicker.SetLum(L: integer);
begin
  Clamp(L, 0, FMaxLum);
  if GetLum() <> L then
  begin
    FLum := L / FMaxLum;
    FManual := false;
    UpdateCoords;
    Invalidate;
    if FChange and Assigned(FOnChange) then FOnChange(Self);
  end;
end;

procedure TSLColorPicker.SetMaxHue(H: Integer);
begin
  if H = FMaxHue then
    exit;
  FMaxHue := H;
  CreateGradient;
  if FChange and Assigned(OnChange) then OnChange(Self);
  Invalidate;
end;

procedure TSLColorPicker.SetMaxLum(L: Integer);
begin
  if L = FMaxLum then
    exit;
  FMaxLum := L;
  FGradientHeight := FMaxLum + 1;
  CreateGradient;
  if FChange and Assigned(OnChange) then OnChange(Self);
  Invalidate;
end;

procedure TSLColorPicker.SetMaxSat(S: Integer);
begin
  if S = FMaxSat then
    exit;
  FMaxSat := S;
  FGradientWidth := FMaxSat + 1;
  CreateGradient;
  if FChange and Assigned(OnChange) then OnChange(Self);
  Invalidate;
end;

procedure TSLColorPicker.SetSat(S: integer);
begin
  Clamp(S, 0, FMaxSat);
  if GetSat() <> S then
  begin
    FSat := S / FMaxSat;
    FManual := false;
    UpdateCoords;
    Invalidate;
    if FChange and Assigned(FOnChange) then FOnChange(Self);
  end;
end;

procedure TSLColorPicker.SetSelectedColor(c: TColor);
var
  h, s, l: Double;
begin
  if WebSafe then c := GetWebSafe(c);
  FManual := false;
  FChange := false;
  ColorToHSL(c, FHue, FSat, FLum);
  FManual := false;
  UpdateCoords;
  Invalidate;
  if FChange and Assigned(FOnChange) then FOnChange(Self);
  FChange := true;
end;

procedure TSLColorPicker.UpdateCoords;
begin
  mdx := round(FSat * (Width - 1));
  mdy := round((1.0 - FLum) * (Height - 1));
end;


end.