Files
ewsdr/FlatRadioButton.pas
T

286 lines
6.8 KiB
ObjectPascal

unit FlatRadioButton;
{ Custom radio button with canvas rendering.
Use instead of TRadioButton where native widgetset styling is undesirable. }
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Controls, Graphics, Forms, LCLType, LMessages, Types, Math,
AppTheme, DpiUtils;
type
TFlatRadioButton = class(TCustomControl)
private
FChecked: Boolean;
FHot: Boolean;
FDown: Boolean;
FOnChange: TNotifyEvent;
FClrBG: TColor;
FClrCircle: TColor;
FClrCircleChecked: TColor;
FClrCircleHot: TColor;
FClrBorder: TColor;
FClrBorderHot: TColor;
FClrDot: TColor;
FClrText: TColor;
FClrTextDisabled: TColor;
procedure SetCheckedSilently(V: Boolean);
procedure SetChecked(V: Boolean);
procedure DoChange;
procedure UncheckSiblings;
procedure CMTextChanged(var Msg: TLMessage); message CM_TEXTCHANGED;
protected
procedure Paint; override;
procedure MouseEnter; override;
procedure MouseLeave; override;
procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer); override;
procedure MouseUp(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer); override;
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
public
constructor Create(AOwner: TComponent); override;
procedure SetAppTheme(const T: TAppTheme);
property Checked: Boolean read FChecked write SetChecked;
property OnChange: TNotifyEvent read FOnChange write FOnChange;
property Align;
property Anchors;
property Caption;
property Enabled;
property Font;
property ParentShowHint;
property PopupMenu;
property ShowHint;
property TabOrder;
property TabStop;
property Tag;
property Visible;
property OnClick;
end;
implementation
const
BASE_CIRCLE_SIZE = 16;
BASE_CIRCLE_LEFT = 1;
BASE_TEXT_GAP = 8;
constructor TFlatRadioButton.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
ControlStyle := ControlStyle + [csOpaque, csCaptureMouse, csClickEvents];
Width := 160;
Height := 22;
TabStop := True;
Cursor := crHandPoint;
Font.Size := 9;
FClrBG := TColor($00181818);
FClrCircle := TColor($001A1A1A);
FClrCircleChecked := TColor($000C3010);
FClrCircleHot := TColor($00282828);
FClrBorder := TColor($00444444);
FClrBorderHot := TColor($00387838);
FClrDot := TColor($0000FF88);
FClrText := TColor($00CCCCCC);
FClrTextDisabled := TColor($00666666);
end;
procedure TFlatRadioButton.SetAppTheme(const T: TAppTheme);
begin
FClrBG := T.Panel;
FClrCircle := T.BtnNorm;
FClrCircleChecked := T.BtnActive;
FClrCircleHot := T.BtnHot;
FClrBorder := T.BtnBorderNorm;
FClrBorderHot := T.BtnBorderActive;
FClrDot := T.BtnTextActive;
FClrText := T.Text;
FClrTextDisabled := T.TextDim;
Font.Color := T.Text;
Invalidate;
end;
procedure TFlatRadioButton.SetChecked(V: Boolean);
begin
if FChecked = V then Exit;
if V then
UncheckSiblings;
FChecked := V;
Invalidate;
DoChange;
end;
procedure TFlatRadioButton.SetCheckedSilently(V: Boolean);
begin
if FChecked = V then Exit;
FChecked := V;
Invalidate;
end;
procedure TFlatRadioButton.DoChange;
begin
if Assigned(FOnChange) then
FOnChange(Self);
end;
procedure TFlatRadioButton.UncheckSiblings;
var
i: Integer;
Ctrl: TControl;
begin
if Parent = nil then Exit;
for i := 0 to Parent.ControlCount - 1 do
begin
Ctrl := Parent.Controls[i];
if (Ctrl <> Self) and (Ctrl is TFlatRadioButton) then
TFlatRadioButton(Ctrl).SetCheckedSilently(False);
end;
end;
procedure TFlatRadioButton.CMTextChanged(var Msg: TLMessage);
begin
Invalidate;
end;
procedure TFlatRadioButton.Paint;
var
CircleSize, CircleLeft, CircleTop, DotSize, TextGap, TextTop, TX: Integer;
R: TRect;
CircleColor, BorderColor, TextColor: TColor;
procedure FillCircle(CX, CY, Radius: Integer; AColor: TColor);
var
DY, DX: Integer;
begin
Canvas.Brush.Color := AColor;
Canvas.Brush.Style := bsSolid;
Canvas.Pen.Style := psClear;
for DY := -Radius to Radius do
begin
DX := Trunc(Sqrt(Sqr(Radius) - Sqr(DY)));
Canvas.FillRect(Rect(CX - DX, CY + DY, CX + DX + 1, CY + DY + 1));
end;
end;
begin
Canvas.Brush.Color := FClrBG;
Canvas.Brush.Style := bsSolid;
Canvas.Pen.Style := psClear;
Canvas.FillRect(ClientRect);
CircleSize := DpiScale(BASE_CIRCLE_SIZE);
if CircleSize > Height - DpiScale(4) then
CircleSize := Height - DpiScale(4);
if CircleSize < DpiScale(10) then
CircleSize := DpiScale(10);
CircleLeft := DpiScale(BASE_CIRCLE_LEFT);
CircleTop := (Height - CircleSize) div 2;
TextGap := DpiScale(BASE_TEXT_GAP);
R := Rect(CircleLeft, CircleTop, CircleLeft + CircleSize, CircleTop + CircleSize);
if FChecked then
CircleColor := FClrCircleChecked
else if FHot or Focused then
CircleColor := FClrCircleHot
else
CircleColor := FClrCircle;
if FHot or Focused then
BorderColor := FClrBorderHot
else
BorderColor := FClrBorder;
if not Enabled then
begin
CircleColor := FClrBG;
BorderColor := FClrBorder;
TextColor := FClrTextDisabled;
end
else
TextColor := FClrText;
FillCircle((R.Left + R.Right) div 2, (R.Top + R.Bottom) div 2,
CircleSize div 2, BorderColor);
FillCircle((R.Left + R.Right) div 2, (R.Top + R.Bottom) div 2,
Max(1, CircleSize div 2 - DpiScale(1)), CircleColor);
if FChecked then
begin
DotSize := Max(DpiScale(5), CircleSize div 2 - DpiScale(2));
if DotSize mod 2 = 0 then
Dec(DotSize);
FillCircle((R.Left + R.Right) div 2, (R.Top + R.Bottom) div 2,
DotSize div 2, FClrDot);
end;
if Caption <> '' then
begin
Canvas.Font.Assign(Font);
Canvas.Font.Color := TextColor;
Canvas.Brush.Style := bsClear;
TX := R.Right + TextGap;
TextTop := (Height - Canvas.TextHeight('Ag')) div 2;
Canvas.TextOut(TX, TextTop, Caption);
end;
end;
procedure TFlatRadioButton.MouseEnter;
begin
inherited;
FHot := True;
Invalidate;
end;
procedure TFlatRadioButton.MouseLeave;
begin
inherited;
FHot := False;
FDown := False;
Invalidate;
end;
procedure TFlatRadioButton.MouseDown(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer);
begin
inherited;
if (Button <> mbLeft) or not Enabled then Exit;
SetFocus;
FDown := True;
Invalidate;
end;
procedure TFlatRadioButton.MouseUp(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer);
begin
inherited;
if Button <> mbLeft then Exit;
FDown := False;
if Enabled and PtInRect(ClientRect, Point(X, Y)) then
begin
if not Checked then
Checked := True;
Click;
end;
Invalidate;
end;
procedure TFlatRadioButton.KeyDown(var Key: Word; Shift: TShiftState);
begin
inherited;
if not Enabled then Exit;
if Key in [VK_SPACE, VK_RETURN] then
begin
if not Checked then
Checked := True;
Click;
Key := 0;
end;
end;
end.