unit ClockCtl;
interface
uses
Windows, SysUtils, Types, UITypes, Classes, Controls, Graphics, Generics.Defaults,
Generics.Collections, Forms, Messages, Direct2D, D2D1, ExtCtrls;
type
TClockCtl = class(TCustomControl)
strict private
FCanvas: TDirect2DCanvas;
FTimer: TTimer;
FOnNewSec: TNotifyEvent;
FPrevSec: Word;
function CanvasWidth: Integer;
function CanvasHeight: Integer;
function CanvasRect: TRect;
private
FShowSeconds: Boolean;
procedure CreateDeviceResources;
procedure TimerTimer(Sender: TObject);
function Vector(const Rho, Phi: Double): TPoint;
function HourPos(const Rho, Hour: Double): TPoint;
procedure SetShowSeconds(const Value: Boolean);
protected
procedure Paint; override;
procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
procedure WMSize(var Message: TWMSize); message WM_SIZE;
procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
procedure CreateWnd; override;
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X: Integer; Y: Integer); override;
procedure ChangeScale(M: Integer; D: Integer; isDpiChange: Boolean); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
property Canvas: TDirect2DCanvas read FCanvas;
published
property Align;
property AlignWithMargins;
property Anchors;
property Cursor;
property Font;
property Hint;
property PopupMenu;
property ShowSeconds: Boolean read FShowSeconds write SetShowSeconds default True;
property TabOrder;
property TabStop default True;
property OnNewSec: TNotifyEvent read FOnNewSec write FOnNewSec;
end;
procedure Register;
implementation
uses
Math, DateUtils, Rux;
procedure Register;
begin
RegisterComponents('Rejbrand 2024', [TClockCtl]);
end;
constructor TClockCtl.Create(AOwner: TComponent);
begin
inherited;
FShowSeconds := True;
FTimer := TTimer.Create(Self);
FTimer.Interval := 100;
FTimer.OnTimer := TimerTimer;
end;
procedure TClockCtl.CreateDeviceResources;
begin
FreeAndNil(FCanvas);
FCanvas := TDirect2DCanvas.Create(Handle);
ID2D1HwndRenderTarget(FCanvas.RenderTarget).SetDpi(PixelsPerInch, PixelsPerInch);
end;
procedure TClockCtl.CreateWnd;
begin
inherited;
CreateDeviceResources;
end;
destructor TClockCtl.Destroy;
begin
FreeAndNil(FCanvas);
inherited;
end;
function TClockCtl.HourPos(const Rho, Hour: Double): TPoint;
begin
Result := Vector(Rho, Hour / 12.0);
end;
procedure TClockCtl.MouseDown(Button: TMouseButton; Shift: TShiftState; X,
Y: Integer);
begin
inherited;
if CanFocus then
SetFocus;
end;
procedure TClockCtl.Paint;
begin
Canvas.RenderTarget.Clear(D2D1ColorF(clWhite));
const C = CanvasRect.CenterPoint;
const R = Round(0.8 * Min(CanvasWidth, CanvasHeight) / 2);
Canvas.Pen.Width := EnsureRange(Round(2 * R / 120), 1, 20);
Canvas.Pen.Color := TUx.ThemeData.ActiveCaptionColor;
Canvas.Brush.Color := TUx.ThemeData.InactiveCaptionColor;
begin
const TL = C - Point(r, r);
const BR = C + Point(r, r);
Canvas.Ellipse(TRect.Create(TL, BR));
end;
for var h := 1 to 12 do
begin
const P1 = HourPos(0.90 * R, h);
const P2 = HourPos(0.99 * R, h);
with P1 do Canvas.MoveTo(X, Y);
with P2 do Canvas.LineTo(X, Y);
end;
var LNow: TDateTime := Time;
begin
const P1 = Vector(0.7 * R, 2 * LNow);
Canvas.Pen.Width := EnsureRange(Round(5 * R / 120), 1, 20);
Canvas.Pen.Color := TUx.ThemeData.InactiveCaptionTextColor;
Canvas.MoveTo(C.X, C.Y);
Canvas.LineTo(P1.X, P1.Y);
end;
begin
const P1 = Vector(0.9 * R, 24 * LNow);
Canvas.Pen.Width := EnsureRange(Round(3 * R / 120), 1, 20);
Canvas.Pen.Color := TUx.ThemeData.InactiveCaptionTextColor;
Canvas.MoveTo(C.X, C.Y);
Canvas.LineTo(P1.X, P1.Y);
end;
if FShowSeconds then
begin
const P1 = Vector(0.9 * R, 24 * 60 * LNow);
Canvas.Pen.Width := EnsureRange(Round(1 * R / 120), 1, 20);
Canvas.Pen.Color := TUx.ThemeData.InactiveCaptionTextColor;
Canvas.MoveTo(C.X, C.Y);
Canvas.LineTo(P1.X, P1.Y);
end;
end;
procedure TClockCtl.SetShowSeconds(const Value: Boolean);
begin
if FShowSeconds <> Value then
begin
FShowSeconds := Value;
Invalidate;
if Assigned(FOnNewSec) then
FOnNewSec(Self);
end;
end;
procedure TClockCtl.TimerTimer(Sender: TObject);
begin
Invalidate;
if Assigned(FOnNewSec) then
begin
const LNewSec = SecondOf(Now);
if LNewSec <> FPrevSec then
begin
FOnNewSec(Self);
FPrevSec := LNewSec;
end;
end;
end;
function TClockCtl.Vector(const Rho, Phi: Double): TPoint;
begin
var S, C: Double;
SinCos(Pi/2 - 2*Pi*Phi, S, C);
Result := CanvasRect.CenterPoint + Point(Round(Rho * C), -Round(Rho * S));
end;
function TClockCtl.CanvasHeight: Integer;
begin
Result := MulDiv(ClientHeight, 96, Self.PixelsPerInch);
end;
function TClockCtl.CanvasRect: TRect;
begin
Result := TRect.Create(0, 0, CanvasWidth, CanvasHeight);
end;
function TClockCtl.CanvasWidth: Integer;
begin
Result := MulDiv(ClientWidth, 96, Self.PixelsPerInch);
end;
procedure TClockCtl.ChangeScale(M, D: Integer; isDpiChange: Boolean);
begin
inherited;
if Assigned(FCanvas) then
begin
ID2D1HwndRenderTarget(FCanvas.RenderTarget).SetDpi(PixelsPerInch, PixelsPerInch);
Invalidate;
end;
end;
procedure TClockCtl.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
Message.Result := 1;
end;
procedure TClockCtl.WMPaint(var Message: TWMPaint);
var
PaintStruct: TPaintStruct;
res: HRESULT;
begin
BeginPaint(Handle, PaintStruct);
try
if Assigned(FCanvas) then
begin
FCanvas.BeginDraw;
try
Paint;
finally
res := FCanvas.RenderTarget.EndDraw;
if res = D2DERR_RECREATE_TARGET then
CreateDeviceResources;
end;
end;
finally
EndPaint(Handle, PaintStruct);
end;
end;
procedure TClockCtl.WMSize(var Message: TWMSize);
begin
if Assigned(FCanvas) then
begin
var S := D2D1SizeU(ClientWidth, ClientHeight);
ID2D1HwndRenderTarget(FCanvas.RenderTarget).Resize(S);
end;
Invalidate;
inherited;
end;
end.