{
  Copyright (C)
  2026 - Uladzimir Karpenka, EW8BAK

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License
  as published by the Free Software Foundation; either version 2
  of the License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
}

unit FlatButton;

{ Кнопка с полным контролем цвета на Windows и Linux.
  Используй вместо TButton везде где нужна тёмная тема. }

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Controls, Graphics, LCLType, LMessages, Types;

type
  TFlatButton = class(TGraphicControl)
  private
    FActive:    Boolean;
    FHot:       Boolean;
    FClrNorm:   TColor;
    FClrActive: TColor;
    FClrHot:    TColor;
    FClrBorder: TColor;
    FClrText:   TColor;
    FClrTextAct:TColor;
    FOnClick:   TNotifyEvent;
    procedure SetActive(V: Boolean);
  protected
    procedure Paint; override;
    procedure MouseEnter; override;
    procedure MouseLeave; override;
    procedure CMTextChanged(var Msg: TLMessage); message CM_TEXTCHANGED;
    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;

    property Active:    Boolean read FActive  write SetActive;
    property ClrNorm:   TColor  read FClrNorm   write FClrNorm;
    property ClrActive: TColor  read FClrActive  write FClrActive;
    property ClrHot:    TColor  read FClrHot     write FClrHot;
    property ClrBorder: TColor  read FClrBorder  write FClrBorder;
    property ClrText:   TColor  read FClrText    write FClrText;
    property ClrTextAct:TColor  read FClrTextAct write FClrTextAct;
    property OnClick:    TNotifyEvent read FOnClick write FOnClick;
    property OnMouseDown;
    property OnMouseUp;
    property Caption;
    property Font;
    property Enabled;
    property Visible;
  end;

// Отрисовка кнопки в стиле TFlatButton на произвольном канвасе/прямоугольнике.
// Используется и TFlatButton.Paint, и оверлеями, которые рендерятся в битмап
// (VfoOverlay) — чтобы вид кнопок был одинаковым по всей программе. Шрифт
// берётся текущий у канваса (вызывающий выставляет Name/Size заранее).
procedure PaintFlatButton(C: TCanvas; const R: TRect; const ACaption: string;
  AActive, AHot: Boolean;
  ClrNorm, ClrActive, ClrHot, ClrBorder, ClrText, ClrTextAct: TColor);

// Фабрика — аналог MakeBtn, возвращает TFlatButton
function MakeFlatBtn(AParent: TWinControl; const ACap: string;
  ALeft, ATop, AW, AH: Integer;
  AHandler: TNotifyEvent;
  ClrNorm:    TColor = TColor($00202020);
  ClrActive:  TColor = TColor($00003300);
  ClrHot:     TColor = TColor($00303030);
  ClrBorder:  TColor = TColor($00404040);
  ClrText:    TColor = TColor($00E0E0E0);
  ClrTextAct: TColor = TColor($0000FF88)): TFlatButton;

implementation

constructor TFlatButton.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FActive     := False;
  FHot        := False;
  FClrNorm    := TColor($00202020);
  FClrActive  := TColor($00003300);
  FClrHot     := TColor($00303030);
  FClrBorder  := TColor($00404040);
  FClrText    := TColor($00E0E0E0);
  FClrTextAct := TColor($0000FF88);
  Cursor      := crHandPoint;
end;

procedure TFlatButton.SetActive(V: Boolean);
begin
  if FActive = V then Exit;
  FActive := V;
  Invalidate;
end;

procedure PaintFlatButton(C: TCanvas; const R: TRect; const ACaption: string;
  AActive, AHot: Boolean;
  ClrNorm, ClrActive, ClrHot, ClrBorder, ClrText, ClrTextAct: TColor);
const
  CORNER = 3;
var
  BG: TColor;
  TW, TH: Integer;
begin
  if AActive then      BG := ClrActive
  else if AHot then    BG := ClrHot
  else                 BG := ClrNorm;

  // Фон со скруглёнными углами
  C.Brush.Color := BG;
  C.Brush.Style := bsSolid;
  C.Pen.Color   := BG;
  C.Pen.Style   := psSolid;
  C.RoundRect(R.Left, R.Top, R.Right, R.Bottom, CORNER * 2, CORNER * 2);

  // Рамка
  C.Pen.Color   := ClrBorder;
  C.Brush.Style := bsClear;
  C.RoundRect(R.Left, R.Top, R.Right - 1, R.Bottom - 1, CORNER * 2, CORNER * 2);

  // Текст
  if AActive then C.Font.Color := ClrTextAct
  else            C.Font.Color := ClrText;
  C.Brush.Style := bsClear;
  TW := C.TextWidth(ACaption);
  TH := C.TextHeight('A');
  C.TextOut(R.Left + (R.Right - R.Left - TW) div 2,
            R.Top  + (R.Bottom - R.Top - TH) div 2, ACaption);
end;

function BlendCol(A, B: TColor; T: Double): TColor;
// Линейная смесь A→B на долю T (0..1); системные цвета резолвим через ColorToRGB.
var ca, cb: LongInt; r, g, bl: Integer;
begin
  ca := ColorToRGB(A);  cb := ColorToRGB(B);
  r  := Round((ca and $FF)         + ((cb and $FF)         - (ca and $FF))         * T);
  g  := Round(((ca shr 8) and $FF) + (((cb shr 8) and $FF) - ((ca shr 8) and $FF)) * T);
  bl := Round(((ca shr 16) and $FF)+ (((cb shr 16) and $FF)- ((ca shr 16) and $FF))* T);
  Result := TColor(r or (g shl 8) or (bl shl 16));
end;

procedure TFlatButton.Paint;
begin
  if Enabled then
    PaintFlatButton(Canvas, ClientRect, Caption, FActive, FHot,
      FClrNorm, FClrActive, FClrHot, FClrBorder, FClrText, FClrTextAct)
  else
    // disabled: не active/не hot, текст и рамка приглушены к фону.
    PaintFlatButton(Canvas, ClientRect, Caption, False, False,
      FClrNorm, FClrActive, FClrHot,
      BlendCol(FClrBorder, FClrNorm, 0.6),
      BlendCol(FClrText,   FClrNorm, 0.55), FClrTextAct);
end;

procedure TFlatButton.MouseEnter;
begin
  inherited;
  FHot := True;
  Invalidate;
end;

procedure TFlatButton.MouseLeave;
begin
  inherited;
  FHot := False;
  Invalidate;
end;

procedure TFlatButton.CMTextChanged(var Msg: TLMessage);
begin
  Invalidate;
end;

procedure TFlatButton.MouseDown(Button: TMouseButton; Shift: TShiftState;
  X, Y: Integer);
begin
  inherited;
end;

procedure TFlatButton.MouseUp(Button: TMouseButton; Shift: TShiftState;
  X, Y: Integer);
begin
  inherited;
  if (Button = mbLeft) and PtInRect(ClientRect, Point(X, Y)) then
    if Assigned(FOnClick) then FOnClick(Self);
end;

function MakeFlatBtn(AParent: TWinControl; const ACap: string;
  ALeft, ATop, AW, AH: Integer;
  AHandler: TNotifyEvent;
  ClrNorm, ClrActive, ClrHot, ClrBorder, ClrText, ClrTextAct: TColor): TFlatButton;
begin
  Result := TFlatButton.Create(AParent);
  Result.Parent      := AParent;
  Result.Caption     := ACap;
  Result.Left        := ALeft;
  Result.Top         := ATop;
  Result.Width       := AW;
  Result.Height      := AH;
  Result.OnClick     := AHandler;
  Result.ClrNorm     := ClrNorm;
  Result.ClrActive   := ClrActive;
  Result.ClrHot      := ClrHot;
  Result.ClrBorder   := ClrBorder;
  Result.ClrText     := ClrText;
  Result.ClrTextAct  := ClrTextAct;
  Result.Font.Size   := 8;
  Result.Font.Color  := ClrText;
end;

end.
