unit IcoButtons;

interface

uses windows, KOL;

const
  AST_NORMAL  = 0;
  AST_HOVERED = 1;
  AST_PRESSED = 2;

type
  tGetIconProc = function(action:integer;stat:integer=AST_NORMAL):cardinal;
  tActionProc  = function(action:integer):integer;

type
  pIcoButton = ^tIcoButton;
  tIcoButton = object(TControl)
  private
    function  GetGetIconProc:tGetIconProc;
    procedure SetGetIconProc (val:tGetIconProc);
    procedure SetDoActionProc(val:tActionProc);
    procedure SetCheckFlag(val:boolean);
    function  GetCheckFlag:boolean;
    procedure SetAction(val:integer);
    function  GetAction:integer;
    function  GetState:integer;
    procedure myPaint(Sender: PControl; DC: HDC);
    procedure myMouseDown (Sender:PControl; var Mouse:TMouseEventData);
    procedure myMouseUp   (Sender:PControl; var Mouse:TMouseEventData);
    procedure myMouseEnter(Sender: PObj);
    procedure myMouseLeave(Sender: PObj);
    procedure myCtrlBtnClick(Sender: PObj);
  public

    procedure RefreshIcon;
    property GetIconProc : tGetIconProc read GetGetIconProc write SetGetIconProc;
    property DoActionProc: tActionProc  write SetDoActionProc;

    property AsCheckbox: boolean read GetCheckFlag write SetCheckFlag;
    property Action    : integer read GetAction    write SetAction;
    property State     : integer read GetState;
  end;

function CreateIcoButton(AOwner: PControl; pGetIconProc:tGetIconProc;
         pActionProc:tActionProc; action:integer=0; repeattime:integer=0):pIcoButton;

function CreateIcoButtonHandle(AOwner: PControl; pActionProc:tActionProc;
         ico_normal:HICON; ico_hovered:HICON=0; ico_pressed:HICON=0;
         action:integer=0; repeattime:integer=0):pIcoButton;

implementation

uses messages;

type
  pIcoBtnData = ^tIcoBtnData;
  tIcoBtnData = record
    rptvalue:cardinal;
    rpttimer:cardinal;
    checking: boolean;

    ico_normal :PIcon;
    ico_hovered:PIcon;
    ico_pressed:PIcon;
    active     :PIcon; // one of ico_*

    Action:integer;

    GetIcon : tGetIconProc;
    DoAction: tActionProc;
  end;

function tIcoButton.GetGetIconProc:tGetIconProc;
begin
  result:=pIcoBtnData(CustomData).GetIcon;
end;

procedure tIcoButton.SetGetIconProc(val:tGetIconProc);
begin
  pIcoBtnData(CustomData).GetIcon:=val;
end;

procedure tIcoButton.SetDoActionProc(val:tActionProc);
begin
  pIcoBtnData(CustomData).DoAction:=val;
end;

procedure tIcoButton.SetCheckFlag(val:boolean);
begin
  pIcoBtnData(CustomData).checking:=val;
end;

function tIcoButton.GetCheckFlag:boolean;
begin
  result:=pIcoBtnData(CustomData).checking;
end;

procedure tIcoButton.SetAction(val:integer);
begin
  pIcoBtnData(CustomData).Action:=val;
end;

function tIcoButton.GetAction:integer;
begin
  result:=pIcoBtnData(CustomData).Action;
end;

function tIcoButton.GetState:integer;
begin
  with pIcoBtnData(CustomData)^ do
  if      active=ico_pressed then result:=AST_PRESSED
  else if active=ico_hovered then result:=AST_HOVERED
  else {if active=ico_normal then}result:=AST_NORMAL;
end;

procedure tIcoButton.myCtrlBtnClick(Sender: PObj);
var
  D: PIcoBtnData;
begin
  D:=PControl(Sender).CustomData;
  if @D.DoAction<>nil then
    D.DoAction(D.action);
end;

procedure tIcoButton.myMouseEnter(Sender: PObj);
var
  D: PIcoBtnData;
begin
  D:=PControl(Sender).CustomData;
  if D.ico_hovered<>nil then
  begin
    D.active:=D.ico_hovered;
    PControl(Sender).Update;
//    PControl(Sender).Parent.Update; //??
  end;
end;

procedure tIcoButton.myMouseLeave(Sender: PObj);
var
  D: PIcoBtnData;
begin
  D:=PControl(Sender).CustomData;
  if D.active=D.ico_hovered then //!!!! for case when mouse button pressed and mouse moved
    D.active:=D.ico_normal;
  PControl(Sender).Update;
//  PControl(Sender).Parent.Update; //??
end;

procedure TimerProc(wnd:HWND;uMsg:uint;idEvent:uint_ptr;dwTime:dword); stdcall;
begin
  PControl(IdEvent).OnClick(PControl(IdEvent));
end;

procedure tIcoButton.myMouseDown(Sender:PControl; var Mouse:TMouseEventData);
var
  D: PIcoBtnData;
begin
  if Mouse.Button<>mbLeft then exit;
  D:=Sender.CustomData;
  if D.checking then
  begin
    if D.active=D.ico_pressed then
      D.active:=D.ico_normal
    else
      D.active:=D.ico_pressed;
  end
  else
  begin
    if D.ico_pressed<>nil then
      D.active:=D.ico_pressed
    else
      Sender.SetPosition(Sender.Position.X-2,Sender.Position.Y-2);

    if D.rptvalue<>0 then
    begin
      D.rpttimer:=SetTimer(Sender.Handle,uint(Sender),D.rptvalue,@TimerProc);
//      D.rpttimer:=SetTimer(Sender.GetWindowHandle,1,D.rptvalue,nil);
    end;
  end;
  Sender.Update;
end;

procedure tIcoButton.myMouseUp(Sender:PControl; var Mouse:TMouseEventData);
var
  D: PIcoBtnData;
  tp:TPOINT;
begin
  if Mouse.Button<>mbLeft then exit;
  D:=Sender.CustomData;
  if not D.checking then
  begin
    if D.rpttimer<>0 then
    begin
      KillTimer(Sender.Handle,D.rpttimer);
      D.rpttimer:=0;
    end;

    if D.ico_pressed<>nil then
    begin
      tp.X:=Mouse.X;
      tp.Y:=Mouse.Y;
      // mouse still above button?
      if (D.ico_hovered<>nil) and PtInRect(Sender.BoundsRect,tp) then
        D.active:=D.ico_hovered
      else
        D.active:=D.ico_normal;
    end
    else
      Sender.SetPosition(Sender.Position.X+2,Sender.Position.Y+2);
    Sender.Update;
  end;
end;

procedure Destroy(dummy:PControl;sender:PObj);
var
  D: PIcoBtnData;
begin
  D:=pIcoButton(sender).CustomData;
  D.ico_normal.Free;
  if D.ico_hovered<>nil then D.ico_hovered.Free;
  if D.ico_pressed<>nil then D.ico_pressed.Free;

  if D.rpttimer<>0 then
  begin
    KillTimer(0,D.rpttimer);
    D.rpttimer:=0;
  end;
end;

procedure tIcoButton.RefreshIcon;
var
  D: PIcoBtnData;
begin
  D:=CustomData;
  if @D.GetIcon=nil then exit;

  D.ico_normal.Handle:=D.GetIcon(D.action,AST_NORMAL);
  D.ico_normal.ShareIcon:=true;
  if D.ico_hovered<>nil then
  begin
    D.ico_hovered.Handle:=D.GetIcon(D.action,AST_HOVERED);
    D.ico_hovered.ShareIcon:=true;
  end;
  if D.ico_pressed<>nil then
  begin
    D.ico_pressed.Handle:=D.GetIcon(D.action,AST_PRESSED);
    D.ico_pressed.ShareIcon:=true;
  end;
end;

procedure tIcoButton.myPaint(Sender: PControl; DC: HDC);
var
  D: PIcoBtnData;
begin
  D:=Sender.CustomData;
  D.active.Draw(DC,0,0);
end;

function CreateIcoButton(AOwner: PControl; pGetIconProc:tGetIconProc;
         pActionProc:tActionProc; action:integer=0; repeattime:integer=0):pIcoButton;
var
  ico:HICON;
  D: PIcoBtnData;
begin
  // first, checking what icons are available
  ico:=pGetIconProc(action,AST_NORMAL);
  if ico=0 then
  begin
    result:=nil;
    exit;
  end;

  Result:=pIcoButton(NewBitBtn(AOwner,'',[bboNoBorder,bboNoCaption],glyphOver,0,0));
  if result=nil then exit;

  Result.LikeSpeedButton.Flat:=true;
  Result.Transparent:=true;

  GetMem(D,SizeOf(TIcoBtnData));
  Result.CustomData:=D;

  Result.OnMouseDown :=Result.myMouseDown;
  Result.OnMouseUp   :=Result.myMouseUp;
  Result.OnMouseEnter:=Result.myMouseEnter;
  Result.OnMouseLeave:=Result.myMouseLeave;
  Result.OnClick     :=Result.myCtrlBtnClick;
  Result.OnPaint     :=Result.myPaint;

  Result.AsCheckbox:=false;
  Result.action:=action;

  D.rptvalue:=repeattime;
  D.rpttimer:=0;

  Result.DoActionProc:=pActionProc;
  Result.GetIconProc :=pGetIconProc;

  D.ico_normal:=NewIcon;
  D.ico_normal.Handle   :=ico;
  D.ico_normal.ShareIcon:=true;
  D.active:=D.ico_normal;

  ico:=D.GetIcon(action,AST_HOVERED);
  if ico<>0 then
  begin
    D.ico_hovered:=NewIcon;
    D.ico_hovered.Handle   :=ico;
    D.ico_hovered.ShareIcon:=true;
  end
  else
    D.ico_hovered:=nil;
  ico:=D.GetIcon(action,AST_PRESSED);
  if ico<>0 then
  begin
    D.ico_pressed:=NewIcon;
    D.ico_pressed.Handle   :=ico;
    D.ico_pressed.ShareIcon:=true;
  end
  else
    D.ico_pressed:=nil;

  Result.SetSize(16,16);
  Result.SetPosition(0,0);
  Result.OnDestroy:=TOnEvent(MakeMethod(nil,@DEstroy));
end;

function CreateIcoButtonHandle(AOwner: PControl; pActionProc:tActionProc;
         ico_normal:HICON; ico_hovered:HICON=0; ico_pressed:HICON=0;
         action:integer=0; repeattime:integer=0):pIcoButton;
var
  D: PIcoBtnData;
begin
  if ico_normal=0 then
  begin
    result:=nil;
    exit;
  end;

  Result:=pIcoButton(NewBitBtn(AOwner,'',[bboNoBorder,bboNoCaption],glyphOver,0,0));
  if result=nil then exit;
  Result.LikeSpeedButton.Flat:=true;
  Result.Transparent:=true;

  GetMem(D,SizeOf(TIcoBtnData));
  Result.CustomData:=D;

  Result.OnMouseDown :=Result.myMouseDown;
  Result.OnMouseUp   :=Result.myMouseUp;
  Result.OnMouseEnter:=Result.myMouseEnter;
  Result.OnMouseLeave:=Result.myMouseLeave;
  Result.OnClick     :=Result.myCtrlBtnClick;
  Result.OnPaint     :=Result.myPaint;

  Result.AsCheckbox:=false;
  Result.action:=action;

  D.rptvalue:=repeattime;
  D.rpttimer:=0;

  Result.GetIconProc :=nil;
  Result.DoActionProc:=pActionProc;

  D.ico_normal:=NewIcon;
  D.ico_normal.Handle   :=ico_normal;
  D.ico_normal.ShareIcon:=true;
  D.active:=D.ico_normal;

  if ico_hovered<>0 then
  begin
    D.ico_hovered:=NewIcon;
    D.ico_hovered.Handle   :=ico_hovered;
    D.ico_hovered.ShareIcon:=true;
  end
  else
    D.ico_hovered:=nil;

  if ico_pressed<>0 then
  begin
    D.ico_pressed:=NewIcon;
    D.ico_pressed.Handle   :=ico_pressed;
    D.ico_pressed.ShareIcon:=true;
  end
  else
    D.ico_pressed:=nil;

  Result.SetSize(16,16);
  Result.SetPosition(0,0);
  Result.OnDestroy:=TOnEvent(MakeMethod(nil,@Destroy));
end;

end.