Files
ewsdr/FlatCheckBox.pas
ew8bakandClaude Sonnet 5 c95620fb23 fix(ui): DPI-масштабирование SettingsForm/DeviceForm + общий DpiScale
Вся раскладка окон, построенных кодом (SettingsForm, DeviceForm), была
захардкожена в пикселях под неявные 96 DPI — на Windows 125-200% или
HiDPI-десктопах контролы физически не росли вместе с укрупнившимся
шрифтом. Добавлено масштабирование SetBounds через MulDiv(V,
Screen.PixelsPerInch, 96) на каждой листовой точке потребления
(const-блоки раскладки не трогались).

Общий DpiScale вынесен в новый юнит DpiUtils.pas — убрана дублированная
копия одноимённого приватного метода в 9 классах (FlatCheckBox,
FlatComboBox, FlatListBox, FlatSpinEdit, FlatFloatSpinEdit, FlatEdit,
FlatRadioButton, FlatPopupMenu, SettingsForm, DeviceForm) и инлайн-MulDiv
без обёртки в MainForm.pas/PanafallPanel.pas.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 16:05:39 +03:00

278 lines
6.3 KiB
ObjectPascal

unit FlatCheckBox;
{ Custom checkbox with canvas rendering.
Use instead of TCheckBox where native widgetset styling is undesirable. }
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Controls, Graphics, Forms, LCLType, LMessages, Types,
AppTheme, DpiUtils;
type
TFlatCheckBox = class(TCustomControl)
private
FChecked: Boolean;
FHot: Boolean;
FDown: Boolean;
FOnChange: TNotifyEvent;
FClrBG: TColor;
FClrBox: TColor;
FClrBoxChecked: TColor;
FClrBoxHot: TColor;
FClrBorder: TColor;
FClrBorderHot: TColor;
FClrCheck: TColor;
FClrText: TColor;
FClrTextDisabled: TColor;
procedure SetChecked(V: Boolean);
procedure DoChange;
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_BOX_SIZE = 16;
BASE_BOX_LEFT = 1;
BASE_TEXT_GAP = 8;
BASE_CORNER = 2;
constructor TFlatCheckBox.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
ControlStyle := ControlStyle + [csOpaque, csCaptureMouse, csClickEvents];
Width := 160;
Height := 22;
TabStop := True;
Cursor := crHandPoint;
Font.Name := 'Courier New';
Font.Size := 9;
FClrBG := TColor($00181818);
FClrBox := TColor($001A1A1A);
FClrBoxChecked := TColor($000C3010);
FClrBoxHot := TColor($00282828);
FClrBorder := TColor($00444444);
FClrBorderHot := TColor($00387838);
FClrCheck := TColor($0000FF88);
FClrText := TColor($00CCCCCC);
FClrTextDisabled := TColor($00666666);
end;
procedure TFlatCheckBox.SetAppTheme(const T: TAppTheme);
begin
FClrBG := T.Panel;
FClrBox := T.BtnNorm;
FClrBoxChecked := T.BtnActive;
FClrBoxHot := T.BtnHot;
FClrBorder := T.BtnBorderNorm;
FClrBorderHot := T.BtnBorderActive;
FClrCheck := T.BtnTextActive;
FClrText := T.Text;
FClrTextDisabled := T.TextDim;
Font.Color := T.Text;
Invalidate;
end;
procedure TFlatCheckBox.SetChecked(V: Boolean);
begin
if FChecked = V then Exit;
FChecked := V;
Invalidate;
DoChange;
end;
procedure TFlatCheckBox.DoChange;
begin
if Assigned(FOnChange) then
FOnChange(Self);
end;
procedure TFlatCheckBox.CMTextChanged(var Msg: TLMessage);
begin
Invalidate;
end;
procedure TFlatCheckBox.Paint;
var
BoxSize, BoxLeft, BoxTop, TextGap, TextTop, TX, Corner: Integer;
R: TRect;
BoxColor, BorderColor, TextColor: TColor;
procedure DrawCheckMark(const AR: TRect);
var
P: array[0..6] of TPoint;
W, H: Integer;
function PX(V: Integer): Integer;
begin
Result := AR.Left + MulDiv(V, W, BASE_BOX_SIZE);
end;
function PY(V: Integer): Integer;
begin
Result := AR.Top + MulDiv(V, H, BASE_BOX_SIZE);
end;
begin
W := AR.Right - AR.Left;
H := AR.Bottom - AR.Top;
P[0] := Point(PX(4), PY(8));
P[1] := Point(PX(6), PY(8));
P[2] := Point(PX(7), PY(10));
P[3] := Point(PX(12), PY(4));
P[4] := Point(PX(13), PY(6));
P[5] := Point(PX(8), PY(12));
P[6] := Point(PX(6), PY(12));
Canvas.Brush.Color := FClrCheck;
Canvas.Brush.Style := bsSolid;
Canvas.Pen.Style := psClear;
Canvas.Polygon(P);
end;
begin
Canvas.Brush.Color := FClrBG;
Canvas.Brush.Style := bsSolid;
Canvas.Pen.Style := psClear;
Canvas.FillRect(ClientRect);
BoxSize := DpiScale(BASE_BOX_SIZE);
if BoxSize > Height - DpiScale(4) then
BoxSize := Height - DpiScale(4);
if BoxSize < DpiScale(10) then
BoxSize := DpiScale(10);
BoxLeft := DpiScale(BASE_BOX_LEFT);
BoxTop := (Height - BoxSize) div 2;
TextGap := DpiScale(BASE_TEXT_GAP);
Corner := DpiScale(BASE_CORNER);
R := Rect(BoxLeft, BoxTop, BoxLeft + BoxSize, BoxTop + BoxSize);
if FChecked then
BoxColor := FClrBoxChecked
else if FHot or Focused then
BoxColor := FClrBoxHot
else
BoxColor := FClrBox;
if FHot or Focused then
begin
BorderColor := FClrBorderHot;
end
else
BorderColor := FClrBorder;
if not Enabled then
begin
BoxColor := FClrBG;
BorderColor := FClrBorder;
TextColor := FClrTextDisabled;
end
else
TextColor := FClrText;
Canvas.Brush.Color := BoxColor;
Canvas.Brush.Style := bsSolid;
Canvas.Pen.Color := BorderColor;
Canvas.Pen.Style := psSolid;
Canvas.Pen.Width := 1;
Canvas.RoundRect(R.Left, R.Top, R.Right - 1, R.Bottom - 1, CORNER * 2, CORNER * 2);
if FChecked then
DrawCheckMark(R);
TX := R.Right + TextGap;
TextTop := (Height - Canvas.TextHeight('Ag')) div 2;
Canvas.Brush.Style := bsClear;
Canvas.Font.Assign(Font);
Canvas.Font.Color := TextColor;
Canvas.TextOut(TX, TextTop, Caption);
end;
procedure TFlatCheckBox.MouseEnter;
begin
inherited;
FHot := True;
Invalidate;
end;
procedure TFlatCheckBox.MouseLeave;
begin
inherited;
FHot := False;
FDown := False;
Invalidate;
end;
procedure TFlatCheckBox.MouseDown(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer);
begin
inherited;
if (Button = mbLeft) and Enabled then
begin
SetFocus;
FDown := True;
Invalidate;
end;
end;
procedure TFlatCheckBox.MouseUp(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer);
begin
inherited;
if (Button = mbLeft) and FDown and Enabled then
begin
FDown := False;
if PtInRect(ClientRect, Point(X, Y)) then
begin
Checked := not Checked;
Click;
end
else
Invalidate;
end;
end;
procedure TFlatCheckBox.KeyDown(var Key: Word; Shift: TShiftState);
begin
inherited;
if Enabled and ((Key = VK_SPACE) or (Key = VK_RETURN)) then
begin
Checked := not Checked;
Click;
Key := 0;
end;
end;
end.