{
  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 FlatSlider;

{ Кастомный горизонтальный ползунок — замена TTrackBar.
  Полностью отрисован через Canvas, одинаково выглядит на Windows и Linux.
  Цвета настраиваемые: применяй SetThemeColors(T: TAppTheme) для смены темы. }

{$mode objfpc}{$H+}

interface

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

type
  TFlatSlider = class(TGraphicControl)
  private
    FMin:      Integer;
    FMax:      Integer;
    FPosition: Integer;
    FReversed: Boolean;
    FDragging: Boolean;
    FHot:      Boolean;
    FOnChange: TNotifyEvent;
    // Цвета (настраиваются через SetThemeColors или напрямую)
    FClrBG:         TColor;
    FClrTrackEmpty: TColor;
    FClrTrackFill:  TColor;
    FClrThumbNorm:  TColor;
    FClrThumbHot:   TColor;
    FClrThumbDrag:  TColor;
    FClrThumbBdr:   TColor;
    procedure SetPosition(V: Integer);
    procedure SetMin(V: Integer);
    procedure SetMax(V: Integer);
    function  PosToX(APos: Integer): Integer;
    function  XToPos(AX: Integer): Integer;
  protected
    procedure Paint; override;
    procedure MouseEnter; override;
    procedure MouseLeave; override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
                        X, Y: Integer); override;
    procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState;
                      X, Y: Integer); override;
    function DoMouseWheelUp(Shift: TShiftState; MousePos: TPoint): Boolean; override;
    function DoMouseWheelDown(Shift: TShiftState; MousePos: TPoint): Boolean; override;
  public
    constructor Create(AOwner: TComponent); override;

    // Применить набор цветов из темы
    procedure SetThemeColors(
      ABG, ATrackEmpty, ATrackFill,
      AThumbNorm, AThumbHot, AThumbDrag, AThumbBdr: TColor);

    property Min:      Integer read FMin      write SetMin;
    property Max:      Integer read FMax      write SetMax;
    property Position: Integer read FPosition write SetPosition;
    property Reversed: Boolean read FReversed write FReversed;
    property OnChange: TNotifyEvent read FOnChange write FOnChange;
    property Enabled;
    property Visible;
  end;

implementation

const
  THUMB_R = 6;
  TRACK_H = 4;
  PAD     = THUMB_R + 2;

constructor TFlatSlider.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FMin      := 0;
  FMax      := 100;
  FPosition := 0;
  FReversed := False;
  FDragging := False;
  FHot      := False;
  Cursor    := crHandPoint;
  // Тёмная тема по умолчанию
  FClrBG         := TColor($00181818);
  FClrTrackEmpty := TColor($00323232);
  FClrTrackFill  := TColor($00205018);
  FClrThumbNorm  := TColor($00407840);
  FClrThumbHot   := TColor($0066BB66);
  FClrThumbDrag  := TColor($0099EE99);
  FClrThumbBdr   := TColor($00122012);
end;

procedure TFlatSlider.SetThemeColors(
  ABG, ATrackEmpty, ATrackFill,
  AThumbNorm, AThumbHot, AThumbDrag, AThumbBdr: TColor);
begin
  FClrBG         := ABG;
  FClrTrackEmpty := ATrackEmpty;
  FClrTrackFill  := ATrackFill;
  FClrThumbNorm  := AThumbNorm;
  FClrThumbHot   := AThumbHot;
  FClrThumbDrag  := AThumbDrag;
  FClrThumbBdr   := AThumbBdr;
  Invalidate;
end;

procedure TFlatSlider.SetPosition(V: Integer);
var C: Integer;
begin
  C := EnsureRange(V, FMin, FMax);
  if FPosition = C then Exit;
  FPosition := C;
  Invalidate;
  if Assigned(FOnChange) then FOnChange(Self);
end;

procedure TFlatSlider.SetMin(V: Integer);
begin
  if FMin = V then Exit;
  FMin := V;
  if FPosition < FMin then FPosition := FMin;
  Invalidate;
end;

procedure TFlatSlider.SetMax(V: Integer);
begin
  if FMax = V then Exit;
  FMax := V;
  if FPosition > FMax then FPosition := FMax;
  Invalidate;
end;

function TFlatSlider.PosToX(APos: Integer): Integer;
var Range, TrackW: Integer;
begin
  Range  := FMax - FMin;
  TrackW := Width - PAD * 2;
  if (Range <= 0) or (TrackW <= 0) then begin Result := PAD; Exit; end;
  if FReversed then
    Result := PAD + Round((FMax - APos) / Range * TrackW)
  else
    Result := PAD + Round((APos - FMin) / Range * TrackW);
end;

function TFlatSlider.XToPos(AX: Integer): Integer;
var Range, TrackW, RawX: Integer;
begin
  Range  := FMax - FMin;
  TrackW := Width - PAD * 2;
  RawX   := EnsureRange(AX - PAD, 0, TrackW);
  if (TrackW <= 0) or (Range <= 0) then begin Result := FMin; Exit; end;
  if FReversed then
    Result := FMax - Round(RawX / TrackW * Range)
  else
    Result := FMin + Round(RawX / TrackW * Range);
  Result := EnsureRange(Result, FMin, FMax);
end;

procedure TFlatSlider.Paint;
var
  CY, TX, FL, FR: Integer;
  TR: TRect;
  ThC: TColor;
begin
  CY := Height div 2;
  TX := PosToX(FPosition);

  Canvas.Brush.Color := FClrBG;
  Canvas.Brush.Style := bsSolid;
  Canvas.Pen.Style   := psClear;
  Canvas.FillRect(ClientRect);

  TR := Rect(PAD, CY - TRACK_H div 2,
             Width - PAD, CY + (TRACK_H + 1) div 2);
  Canvas.Brush.Color := FClrTrackEmpty;
  Canvas.RoundRect(TR.Left, TR.Top, TR.Right, TR.Bottom, TRACK_H, TRACK_H);

  if not FReversed then begin FL := PAD; FR := TX; end
  else                  begin FL := TX;  FR := Width - PAD; end;
  if FR > FL then
  begin
    Canvas.Brush.Color := FClrTrackFill;
    Canvas.RoundRect(FL, TR.Top, FR, TR.Bottom, TRACK_H, TRACK_H);
  end;

  if      FDragging then ThC := FClrThumbDrag
  else if FHot      then ThC := FClrThumbHot
  else                   ThC := FClrThumbNorm;
  Canvas.Brush.Color := ThC;
  Canvas.Pen.Color   := FClrThumbBdr;
  Canvas.Pen.Style   := psSolid;
  Canvas.Pen.Width   := 1;
  Canvas.Ellipse(TX - THUMB_R, CY - THUMB_R,
                 TX + THUMB_R + 1, CY + THUMB_R + 1);
end;

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

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

procedure TFlatSlider.MouseDown(Button: TMouseButton; Shift: TShiftState;
  X, Y: Integer);
begin
  inherited;
  if Button = mbLeft then
  begin
    FDragging := True;
    SetPosition(XToPos(X));
  end;
end;

procedure TFlatSlider.MouseMove(Shift: TShiftState; X, Y: Integer);
begin
  inherited;
  if FDragging then
    SetPosition(XToPos(X));
end;

procedure TFlatSlider.MouseUp(Button: TMouseButton; Shift: TShiftState;
  X, Y: Integer);
begin
  inherited;
  if Button = mbLeft then
  begin
    FDragging := False;
    Invalidate;
  end;
end;

function TFlatSlider.DoMouseWheelUp(Shift: TShiftState; MousePos: TPoint): Boolean;
begin
  SetPosition(FPosition + 1);
  Result := True;
end;

function TFlatSlider.DoMouseWheelDown(Shift: TShiftState; MousePos: TPoint): Boolean;
begin
  SetPosition(FPosition - 1);
  Result := True;
end;

end.
