You've already forked lazarus-ccr
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@9030 8e941d3f-bd1b-0410-a28a-d453659cc2b4
1050 lines
30 KiB
ObjectPascal
1050 lines
30 KiB
ObjectPascal
{
|
|
/***************************************************************************
|
|
switches.pp
|
|
|
|
License: Modified LGPL (with linking exception)
|
|
|
|
See the file COPYING.modifiedLGPL.txt, included in the Lazarus distribution,
|
|
for details about the license.
|
|
|
|
Autho: Werner Pamler
|
|
*****************************************************************************
|
|
}
|
|
|
|
unit Switches;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
LCLIntf, LCLType, LMessages, LCLVersion,
|
|
Graphics, GraphType, Classes, SysUtils, Types, Controls, ExtCtrls, ImgList;
|
|
|
|
type
|
|
TSwitchBorderStyle = (
|
|
bsNone, bsThin, bsThick, bsThin3D, bsThick3D,
|
|
bsNoneRounded, bsThinRounded, bsThickRounded
|
|
);
|
|
TSwitchMode = (smClick, smDblClick, smSlide, smSlideDblClick);
|
|
TSwitchOrientation = (soHorizontal, soVertical);
|
|
TSwitchAnimationEffect = (saeBlendColors, saeBlendImages, saeRotateImages);
|
|
TSwitchAnimationEffects = set of TSwitchAnimationEffect;
|
|
|
|
TCustomOnOffSwitch = class(TCustomControl)
|
|
private
|
|
const
|
|
DEFAULT_BUTTON_SIZE = 24;
|
|
private
|
|
FAnimated: Boolean;
|
|
FAnimationEffects: TSwitchAnimationEffects;
|
|
FAnimationTimer: TTimer;
|
|
FBorderStyle: TSwitchBorderStyle;
|
|
FButtonPos: Integer;
|
|
FButtonSize: Integer;
|
|
FCaptions: array[boolean] of string;
|
|
FChecked: Boolean;
|
|
FCheckedAfterAnimation: Boolean;
|
|
FColors: array [0..2] of TColor;
|
|
FInverse: Boolean;
|
|
FDragging: Boolean;
|
|
FMaxDistance: Integer;
|
|
FFlippedColors: Boolean;
|
|
FTogglePending: Boolean;
|
|
FMousePt: TPoint;
|
|
FButtonRect: TRect;
|
|
FReadOnly: Boolean;
|
|
FShowButtonBorder: Boolean;
|
|
FShowCaption: Boolean;
|
|
FShowFocusRect: Boolean;
|
|
FSwitchMode: TSwitchMode;
|
|
FOnChange: TNotifyEvent;
|
|
FDblClickTimer: TTimer;
|
|
FPicture: array[boolean] of TPicture;
|
|
FImageIndex: array[boolean] of TImageIndex;
|
|
FImageListChangeLink: TChangeLink;
|
|
FImages: TCustomImagelist;
|
|
FImagesWidth: Integer;
|
|
function GetBorderWidth: Integer;
|
|
function GetCaptions(AChecked: Boolean): String;
|
|
function GetColors(AIndex: Integer): TColor;
|
|
function GetImageIndex(AChecked: Boolean): TImageIndex;
|
|
function GetOrientation: TSwitchOrientation;
|
|
function GetPicture(AChecked: Boolean): TPicture;
|
|
procedure SetAnimated(AValue: Boolean);
|
|
procedure SetBorderStyle(AValue: TSwitchBorderStyle); reintroduce;
|
|
procedure SetButtonSize(AValue: Integer);
|
|
procedure SetCaptions(AChecked: Boolean; AValue: string);
|
|
procedure SetChecked(AValue: Boolean);
|
|
procedure SetColors(AIndex: Integer; AValue: TColor);
|
|
procedure SetFlippedColors(AValue: Boolean);
|
|
procedure SetImageIndex(AChecked: Boolean; AValue: TImageIndex);
|
|
procedure SetImages(const AValue: TCustomImageList);
|
|
procedure SetInverse(AValue: Boolean);
|
|
procedure SetPicture(AChecked: Boolean; AValue: TPicture);
|
|
procedure SetShowButtonBorder(AValue: Boolean);
|
|
procedure SetShowCaption(AValue: Boolean);
|
|
procedure SetShowFocusRect(AValue: Boolean);
|
|
procedure UpdateButtonPos;
|
|
procedure UpdateMaxDistance;
|
|
procedure UpdateShape;
|
|
protected
|
|
function CalcButtonRect: TRect;
|
|
function CalcMargin: Integer;
|
|
function CanChange: Boolean; virtual;
|
|
procedure DblClick; override;
|
|
procedure DblClickTimerHandler(Sender: TObject);
|
|
procedure DoAnimation(Sender: TObject);
|
|
procedure DoAutoAdjustLayout(const AMode: TLayoutAdjustmentPolicy;
|
|
const AXProportion, AYProportion: Double); override;
|
|
procedure DoChange; virtual;
|
|
procedure DoEnter; override;
|
|
procedure DoExit; override;
|
|
procedure DoImageListChange(Sender: TObject);
|
|
function DraggedToON: Boolean;
|
|
procedure DrawButton(ARect: TRect); virtual;
|
|
procedure DrawCaption(ARect: TRect; AChecked: Boolean); virtual;
|
|
procedure DrawFocusRect(ARect: TRect);
|
|
class function GetControlClassDefaultSize: TSize; override;
|
|
function HasPicture: Boolean;
|
|
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
|
|
procedure MouseDown(Button: TMouseButton; Shift:TShiftState; X,Y:Integer); override;
|
|
procedure MouseMove(Shift: TShiftState; X,Y: Integer); override;
|
|
function MouseOnButton(X, Y: Integer): Boolean; virtual;
|
|
procedure MouseUp(Button: TMouseButton; Shift:TShiftState; X,Y:Integer); override;
|
|
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
|
|
procedure Paint; override;
|
|
property Animated: Boolean read FAnimated write SetAnimated default false;
|
|
property AnimationEffects: TSwitchAnimationEffects read FAnimationEffects write FAnimationEffects default [saeBlendColors, saeBlendImages];
|
|
property BorderColor: TColor index 2 read GetColors write SetColors default clGray;
|
|
property BorderStyle: TSwitchBorderStyle read FBorderStyle write SetBorderStyle default bsThin;
|
|
property ButtonSize: Integer read FButtonSize write SetButtonSize default DEFAULT_BUTTON_SIZE;
|
|
property CaptionOFF: String index false read GetCaptions write SetCaptions;
|
|
property CaptionON: String index true read GetCaptions write SetCaptions;
|
|
property Checked: Boolean read FChecked write SetChecked default false;
|
|
property Color default clWindow;
|
|
property ColorOFF: TColor index 0 read GetColors write SetColors default clMaroon;
|
|
property ColorON: TColor index 1 read GetColors write SetColors default clGreen;
|
|
property FlippedColors: Boolean read FFlippedColors write SetFlippedColors default false;
|
|
property ImageIndexOFF: TImageIndex index false read GetImageIndex write SetImageIndex default -1;
|
|
property ImageIndexON: TImageIndex index true read GetImageIndex write SetImageIndex default -1;
|
|
property Images: TCustomImageList read FImages write SetImages;
|
|
property ImagesWidth: Integer read FImagesWidth write FImagesWidth default 0;
|
|
property Inverse: Boolean read FInverse write SetInverse default false;
|
|
property PictureOFF: TPicture index false read GetPicture write SetPicture;
|
|
property PictureON: TPicture index true read GetPicture write SetPicture;
|
|
property ReadOnly: boolean read FReadOnly write FReadOnly default false;
|
|
property ShowButtonBorder: Boolean read FShowButtonBorder write SetShowButtonBorder default true;
|
|
property ShowFocusRect: Boolean read FShowFocusRect write SetShowFocusRect default true;
|
|
property ShowCaption: Boolean read FShowCaption write SetShowCaption default true;
|
|
property SwitchMode: TSwitchMode read FSwitchMode write FSwitchMode default smSlideDblClick;
|
|
property OnChange: TNotifyEvent read FOnChange write FOnChange;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override;
|
|
property Orientation: TSwitchOrientation read GetOrientation;
|
|
end;
|
|
|
|
TOnOffSwitch = class(TCustomOnOffSwitch)
|
|
published
|
|
property Animated;
|
|
property AnimationEffects;
|
|
property BorderColor;
|
|
property BorderStyle;
|
|
property ButtonSize;
|
|
property CaptionOFF;
|
|
property CaptionON;
|
|
property Checked;
|
|
property Color;
|
|
property ColorOFF;
|
|
property ColorON;
|
|
property Enabled;
|
|
property FlippedColors;
|
|
property ImageIndexON;
|
|
property ImageIndexOFF;
|
|
property Images;
|
|
property ImagesWidth;
|
|
property Inverse;
|
|
property PictureOFF;
|
|
property PictureON;
|
|
property ReadOnly;
|
|
property ShowButtonBorder;
|
|
property ShowCaption;
|
|
property ShowFocusRect;
|
|
property SwitchMode;
|
|
|
|
// inherited
|
|
property Action;
|
|
property Align;
|
|
property Anchors;
|
|
property BorderSpacing;
|
|
property Constraints;
|
|
property DoubleBuffered;
|
|
property Font;
|
|
property PopupMenu;
|
|
property ShowHint;
|
|
property TabOrder;
|
|
property TabStop default true;
|
|
property Visible;
|
|
|
|
// new
|
|
property OnChange;
|
|
|
|
// inherited
|
|
property OnChangeBounds;
|
|
property OnClick;
|
|
property OnEnter;
|
|
property OnExit;
|
|
property OnKeyDown;
|
|
property OnKeyPress;
|
|
property OnKeyUp;
|
|
property OnMouseDown;
|
|
property OnMouseEnter;
|
|
property OnMouseLeave;
|
|
property OnMouseUp;
|
|
property OnResize;
|
|
property OnShowHint;
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
Math, FPImage, GraphMath, IntfGraphics;
|
|
|
|
function BlendColors(AFactor: Double; AColor1, AColor2: TColor): TColor;
|
|
var
|
|
f1, f2: Double;
|
|
r, g, b: Byte;
|
|
begin
|
|
f1 := 1.0 - AFactor;
|
|
f2 := AFactor;
|
|
r := EnsureRange(round(GetRValue(AColor1) * f1 + GetRValue(AColor2) * f2), 0, 255);
|
|
g := EnsureRange(round(GetGValue(AColor1) * f1 + GetGValue(AColor2) * f2), 0, 255);
|
|
b := EnsureRange(round(GetBValue(AColor1) * f1 + GetBValue(AColor2) * f2), 0, 255);
|
|
Result := RGBToColor(r, g, b);
|
|
end;
|
|
|
|
procedure BlendImages(Img, Img1, Img2: TFPCustomImage; AFactor: Double);
|
|
var
|
|
x, y: Integer;
|
|
r,g,b,a: Word;
|
|
f1, f2: Double;
|
|
begin
|
|
f1 := 1.0 - AFactor;
|
|
f2 := AFactor;
|
|
for y := 0 to Img1.Height-1 do
|
|
for x := 0 to Img1.Width-1 do
|
|
begin
|
|
r := round(f1 * Img1.Colors[x, y].Red + f2 * Img2.Colors[x, y].Red);
|
|
g := round(f1 * Img1.Colors[x, y].Green + f2 * Img2.Colors[x, y].Green);
|
|
b := round(f1 * Img1.Colors[x, y].Blue + f2 * Img2.Colors[x, y].Blue);
|
|
a := round(f1 * Img1.Colors[x, y].Alpha + f2 * Img2.Colors[x, y].Alpha);
|
|
Img.Colors[x, y] := FPColor(r, g, b, a);
|
|
end;
|
|
end;
|
|
|
|
{$IF LCL_FullVersion < 3000000}
|
|
function RotatePoint(const APoint: TPoint; AAngle: Double): TPoint;
|
|
var
|
|
sa, ca: Double;
|
|
begin
|
|
SinCos(AAngle, sa, ca);
|
|
Result.X := Round( ca * APoint.X + sa * APoint.Y);
|
|
Result.Y := Round(-sa * APoint.X + ca * APoint.Y);
|
|
end;
|
|
{$ENDIF}
|
|
|
|
procedure RotateImage(Img: TFPCustomImage; Angle: Double);
|
|
var
|
|
Buffer: TFPCustomImage;
|
|
x, y: Integer;
|
|
C, P: TPoint;
|
|
begin
|
|
Buffer := TFPMemoryImage.Create(Img.Width, Img.Height);
|
|
try
|
|
C := Point(Img.Width div 2, Img.Height div 2);
|
|
for y := 0 to Buffer.Height-1 do
|
|
for x := 0 to Buffer.Width-1 do
|
|
begin
|
|
P := RotatePoint(Point(x, y) - C, Angle) + C;
|
|
if (P.X >= 0) and (P.Y >= 0) and (P.X < Img.Width) and (P.Y < Img.Height) then
|
|
Buffer.Colors[x, y] := Img.Colors[P.X, P.Y]
|
|
else
|
|
Buffer.Colors[x, y] := colTransparent;
|
|
end;
|
|
for y := 0 to Img.Height-1 do
|
|
for x := 0 to Img.Width-1 do
|
|
Img.Colors[x, y] := Buffer[x, y];
|
|
finally
|
|
Buffer.Free;
|
|
end;
|
|
end;
|
|
|
|
function TintedColor(AColor: TColor; ADelta: Integer): TColor;
|
|
var
|
|
r, g, b: Byte;
|
|
begin
|
|
AColor := ColorToRGB(AColor);
|
|
r := GetRValue(AColor);
|
|
g := GetGValue(AColor);
|
|
b := GetBValue(AColor);
|
|
if r + g + b < 3*128 then
|
|
// Dark color --> make it brighter
|
|
ADelta := abs(ADelta)
|
|
else
|
|
// Bright color --> make it darker
|
|
ADelta := -abs(ADelta);
|
|
r := EnsureRange(r + ADelta, 0, 255);
|
|
g := EnsureRange(g + ADelta, 0, 255);
|
|
b := EnsureRange(b + ADelta, 0, 255);
|
|
Result := RGBToColor(r, g, b);
|
|
end;
|
|
|
|
|
|
{ TOnOffSwitch }
|
|
|
|
constructor TCustomOnOffSwitch.Create(AOwner: TComponent);
|
|
begin
|
|
inherited;
|
|
TabStop := true;
|
|
Color := clWindow;
|
|
FAnimationEffects := [saeBlendColors, saeBlendImages];
|
|
FBorderStyle := bsThin;
|
|
FButtonSize := DEFAULT_BUTTON_SIZE;
|
|
FColors[0] := clMaroon; // unchecked color
|
|
FColors[1] := clGreen; // checked color
|
|
FColors[2] := clGray; // Border color
|
|
FCaptions[false] := 'OFF';
|
|
FCaptions[true] := 'ON';
|
|
FImageListChangeLink := TChangeLink.Create;
|
|
FImageListChangeLink.OnChange := @DoImageListChange;
|
|
FImageIndex[false] := -1;
|
|
FImageIndex[true] := -1;
|
|
FPicture[false] := TPicture.Create;
|
|
FPicture[true] := TPicture.Create;
|
|
FShowCaption := true;
|
|
FShowButtonBorder := true;
|
|
FShowFocusRect := true;
|
|
FSwitchMode := smSlideDblClick;
|
|
FDblClickTimer := TTimer.Create(self);
|
|
FDblClickTimer.Interval := 500;
|
|
FDblClickTimer.Enabled := false;
|
|
FDblClickTimer.OnTimer := @DblClickTimerHandler;
|
|
with GetControlClassDefaultSize do
|
|
SetInitialBounds(0, 0, CX, CY);
|
|
end;
|
|
|
|
destructor TCustomOnOffSwitch.Destroy;
|
|
begin
|
|
FPicture[false].Free;
|
|
FPicture[true].Free;
|
|
FImageListChangeLink.Free;
|
|
inherited;
|
|
end;
|
|
|
|
function TCustomOnOffSwitch.CalcButtonRect: TRect;
|
|
begin
|
|
Result := FButtonRect;
|
|
case Orientation of
|
|
soHorizontal : OffsetRect(Result, FButtonPos + CalcMargin, 0);
|
|
soVertical : OffsetRect(Result, 0, FButtonPos + CalcMargin);
|
|
end;
|
|
end;
|
|
|
|
function TCustomOnOffSwitch.CalcMargin: Integer;
|
|
begin
|
|
Result := 3 + GetBorderWidth;
|
|
end;
|
|
|
|
function TCustomOnOffSwitch.CanChange: Boolean;
|
|
begin
|
|
Result := Enabled and (not FReadOnly);
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.DblClick;
|
|
begin
|
|
inherited;
|
|
FDblClickTimer.Enabled := false;
|
|
if CanChange and FTogglePending then begin
|
|
FTogglePending := false;
|
|
SetChecked(not FChecked);
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.DblClickTimerHandler(Sender: TObject);
|
|
begin
|
|
FTogglePending := true;
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.DoAutoAdjustLayout(
|
|
const AMode: TLayoutAdjustmentPolicy;
|
|
const AXProportion, AYProportion: Double);
|
|
begin
|
|
inherited DoAutoAdjustLayout(AMode, AXProportion, AYProportion);
|
|
if AMode in [lapAutoAdjustWithoutHorizontalScrolling, lapAutoAdjustForDPI] then
|
|
begin
|
|
case Orientation of
|
|
soHorizontal : FButtonSize := Round(FButtonSize * AXProportion);
|
|
soVertical : FButtonSize := Round(FButtonSize * AYProportion);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ Handler for the animation timer }
|
|
procedure TCustomOnOffSwitch.DoAnimation(Sender: TObject);
|
|
const
|
|
SPEED = 3;
|
|
begin
|
|
if FCheckedAfterAnimation xor FInverse then
|
|
begin
|
|
inc(FButtonPos, SPEED);
|
|
if FButtonPos >= FMaxDistance then
|
|
begin
|
|
FAnimationTimer.Enabled := false;
|
|
FChecked := FCheckedAfterAnimation;
|
|
UpdateButtonPos;
|
|
DoChange;
|
|
end;
|
|
end else
|
|
begin
|
|
dec(FButtonPos, SPEED);
|
|
if FButtonPos <= 0 then
|
|
begin
|
|
FAnimationTimer.Enabled := false;
|
|
FChecked := FCheckedAfterAnimation;
|
|
UpdateButtonPos;
|
|
DoChange;
|
|
end;
|
|
end;
|
|
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.DoChange;
|
|
begin
|
|
if Assigned(FOnChange) then FOnChange(self);
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.DoEnter;
|
|
begin
|
|
inherited;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.DoExit;
|
|
begin
|
|
inherited;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.DoImageListChange(Sender: TObject);
|
|
begin
|
|
Invalidate;
|
|
end;
|
|
|
|
{ Determines whether the dragged distance lands in the part of the
|
|
ON (Result = true) or OFF state (Result = false) }
|
|
function TCustomOnOffSwitch.DraggedToON: Boolean;
|
|
begin
|
|
Result := FButtonPos > FMaxDistance div 2;
|
|
if FInverse then Result := not Result;
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.DrawButton(ARect: TRect);
|
|
var
|
|
rawON, rawOFF: TRawImage;
|
|
Img, ImgOn, ImgOff: TLazIntfImage;
|
|
bmp: TBitmap;
|
|
ImgSize: TSize;
|
|
x, y: Integer;
|
|
idx: Integer;
|
|
ppi: Integer;
|
|
begin
|
|
if not Enabled then begin
|
|
Canvas.Brush.Color := clGrayText;
|
|
Canvas.Pen.Color := clGrayText;
|
|
end else begin
|
|
if FFlippedColors then
|
|
Canvas.Brush.Color := Color
|
|
else
|
|
if (saeBlendColors in FAnimationEffects) then
|
|
Canvas.Brush.Color := BlendColors(FButtonPos/FMaxDistance, ColorOFF, ColorON)
|
|
else
|
|
if FChecked then
|
|
Canvas.Brush.Color := ColorON
|
|
else
|
|
Canvas.Brush.Color := ColorOFF;
|
|
Canvas.Pen.Color := clBlack;
|
|
end;
|
|
if not FShowButtonBorder then
|
|
Canvas.Pen.Color := Canvas.Brush.Color;
|
|
Canvas.Pen.Width := 1;
|
|
Canvas.Pen.Style := psSolid;
|
|
if FBorderStyle in [bsNoneRounded, bsThinRounded, bsThickRounded] then
|
|
begin
|
|
dec(ARect.Bottom);
|
|
dec(ARect.Right);
|
|
Canvas.Ellipse(ARect);
|
|
end else
|
|
Canvas.Rectangle(ARect);
|
|
|
|
if FImages <> nil then
|
|
begin
|
|
ppi := Font.PixelsPerInch;
|
|
imgSize := FImages.SizeForPPI[FImagesWidth, ppi];
|
|
x := (ARect.Left + ARect.Right - imgSize.CX) div 2;
|
|
y := (ARect.Top + ARect.Bottom - imgSize.CY) div 2;
|
|
idx := FImageIndex[FChecked];
|
|
if idx <> -1 then
|
|
begin
|
|
if ([saeBlendImages, saeRotateImages] * FAnimationEffects <> []) then
|
|
begin
|
|
bmp := TBitmap.Create;
|
|
try
|
|
bmp.PixelFormat := pf32Bit;
|
|
rawOFF.Init;
|
|
rawON.Init;
|
|
FImages.GetRawImage(FImageIndex[false], rawOFF);
|
|
FImages.GetRawImage(FImageIndex[true], rawON);
|
|
imgON := TLazIntfImage.Create(rawON, false);
|
|
imgOFF := TLazIntfImage.Create(rawOFF, false);
|
|
img := TLazIntfImage.CreateCompatible(imgOFF, imgOFF.Width, imgOFF.Height);
|
|
try
|
|
if saeBlendImages in FAnimationEffects then
|
|
BlendImages(img, imgOFF, imgON, FButtonPos/FMaxDistance)
|
|
else
|
|
if FChecked then
|
|
img.Assign(imgON)
|
|
else
|
|
img.Assign(imgOFF);
|
|
if saeRotateImages in FAnimationEffects then
|
|
RotateImage(img, 2*pi * FButtonPos/FMaxDistance);
|
|
bmp.Assign(img);
|
|
Canvas.Draw(x, y, bmp);
|
|
finally
|
|
img.Free;
|
|
imgON.Free;
|
|
imgOFF.Free;
|
|
end;
|
|
finally
|
|
bmp.Free;
|
|
end;
|
|
end else
|
|
FImages.DrawForPPI(Canvas, x, y, idx, FImagesWidth, ppi, GetCanvasScaleFactor, Enabled);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.DrawCaption(ARect: TRect; AChecked: Boolean);
|
|
var
|
|
ts: TTextStyle;
|
|
begin
|
|
Canvas.Font.Assign(Font);
|
|
if not Enabled then
|
|
Canvas.Font.Color := clGrayText;
|
|
ts := Canvas.TextStyle;
|
|
ts.Alignment := taCenter;
|
|
ts.Layout := tlCenter;
|
|
Canvas.TextStyle := ts;
|
|
if AChecked then
|
|
Canvas.TextRect(ARect, ARect.Left, ARect.Top, CaptionON)
|
|
else
|
|
Canvas.TextRect(ARect, ARect.Left, ARect.Top, CaptionOFF);
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.DrawFocusRect(ARect: TRect);
|
|
var
|
|
m: TPenMode;
|
|
c: Boolean;
|
|
begin
|
|
m := Canvas.Pen.Mode;
|
|
c := Canvas.Pen.Cosmetic;
|
|
try
|
|
Canvas.Pen.Color := clBlack;
|
|
Canvas.Pen.Mode := pmXOR;
|
|
Canvas.Pen.Color := clWhite;
|
|
Canvas.Pen.Style := psDot;
|
|
Canvas.Brush.Style := bsClear;
|
|
if FBorderStyle in [bsNoneRounded, bsThinRounded, bsThickRounded] then
|
|
begin
|
|
Canvas.Pen.Cosmetic := true;
|
|
Canvas.Ellipse(ARect);
|
|
end else
|
|
begin
|
|
Canvas.Pen.Cosmetic := false;
|
|
Canvas.Rectangle(ARect);
|
|
end;
|
|
finally
|
|
Canvas.Pen.Mode := m;
|
|
Canvas.Pen.Cosmetic := c;
|
|
end;
|
|
end;
|
|
|
|
function TCustomOnOffSwitch.GetBorderWidth: Integer;
|
|
begin
|
|
case FBorderStyle of
|
|
bsNone, bsThin, bsThin3D, bsNoneRounded, bsThinRounded:
|
|
Result := 1;
|
|
bsThick, bsThick3D, bsThickRounded:
|
|
Result := 2;
|
|
end;
|
|
end;
|
|
|
|
function TCustomOnOffSwitch.GetCaptions(AChecked: Boolean): string;
|
|
begin
|
|
Result := FCaptions[AChecked];
|
|
end;
|
|
|
|
function TCustomOnOffSwitch.GetColors(AIndex: Integer): TColor;
|
|
begin
|
|
Result := FColors[AIndex];
|
|
end;
|
|
|
|
class function TCustomOnOffSwitch.GetControlClassDefaultSize: TSize;
|
|
begin
|
|
Result.CX := 60;
|
|
Result.CY := 30;
|
|
end;
|
|
|
|
function TCustomOnOffSwitch.GetImageIndex(AChecked: Boolean): TImageIndex;
|
|
begin
|
|
Result := FImageIndex[AChecked];
|
|
end;
|
|
|
|
function TCustomOnOffSwitch.GetOrientation: TSwitchOrientation;
|
|
begin
|
|
if Width > Height then Result := soHorizontal else Result := soVertical;
|
|
end;
|
|
|
|
function TCustomOnOffSwitch.GetPicture(AChecked: Boolean): TPicture;
|
|
begin
|
|
Result := FPicture[AChecked];
|
|
end;
|
|
|
|
function TCustomOnOffSwitch.HasPicture: Boolean;
|
|
begin
|
|
Result := (FPicture[false].Width <> 0) or (FPicture[true].Width <> 0);
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.KeyDown(var Key: Word; Shift: TShiftState);
|
|
begin
|
|
inherited;
|
|
if CanChange and ((Key = VK_SPACE) or (Key = VK_RETURN)) then
|
|
Checked := not Checked;
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.MouseDown(Button: TMouseButton; Shift: TShiftState;
|
|
X, Y: Integer);
|
|
begin
|
|
inherited;
|
|
SetFocus;
|
|
if CanChange and (Button = mbLeft) and MouseOnButton(X, Y) then
|
|
begin
|
|
if (FSwitchMode in [smSlide, smSlideDblClick]) then FDragging := true;
|
|
FMousePt := Point(X, Y);
|
|
FDblClickTimer.Enabled := FSwitchMode in [smDblClick, smSlideDblClick];
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.MouseMove(Shift: TShiftState; X,Y: Integer);
|
|
var
|
|
delta: Integer;
|
|
begin
|
|
inherited;
|
|
if FDragging then begin
|
|
case Orientation of
|
|
soHorizontal : delta := X - FMousePt.X;
|
|
soVertical : delta := Y - FMousePt.Y;
|
|
end;
|
|
FMousePt := Point(X, Y);
|
|
FButtonPos := EnsureRange(FButtonPos + delta, 0, FMaxDistance);
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
function TCustomOnOffSwitch.MouseOnButton(X, Y: Integer): Boolean;
|
|
var
|
|
R: TRect;
|
|
begin
|
|
R := CalcButtonRect;
|
|
Result := PtInRect(R, Point(X, Y));
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.MouseUp(Button: TMouseButton; Shift:TShiftState; X,Y:Integer);
|
|
var
|
|
oldChecked: Boolean;
|
|
begin
|
|
inherited;
|
|
if (Button = mbLeft) and CanChange then begin
|
|
oldChecked := FChecked;
|
|
if FDragging then begin
|
|
FDragging := false;
|
|
FChecked := DraggedToON;
|
|
UpdateButtonPos;
|
|
end else
|
|
begin
|
|
if (FSwitchMode = smClick) or (HasPicture and (FSwitchMode = smSlide)) then
|
|
SetChecked(not FChecked);
|
|
if FChecked <> oldChecked then
|
|
DoChange
|
|
else
|
|
if (FSwitchMode in [smDblClick, smSlideDblClick]) then
|
|
FTogglePending := true;
|
|
end;
|
|
if FChecked <> oldChecked then
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.Notification(AComponent: TComponent;
|
|
Operation: TOperation);
|
|
begin
|
|
inherited Notification(AComponent, Operation);
|
|
if (Operation = opRemove) and (AComponent = Images) then
|
|
SetImages(nil);
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.Paint;
|
|
var
|
|
R: TRect;
|
|
margin: Integer;
|
|
diam: Integer;
|
|
newChecked: Boolean;
|
|
begin
|
|
R := Rect(0, 0, Width, Height);
|
|
|
|
if HasPicture then
|
|
begin
|
|
Canvas.StretchDraw(R, FPicture[FChecked].Graphic);
|
|
exit;
|
|
end;
|
|
|
|
if Enabled then begin
|
|
if FFlippedColors then begin
|
|
if (saeBlendColors in FAnimationEffects) then
|
|
Canvas.Brush.Color := BlendColors(FButtonPos/FMaxDistance, ColorOFF, ColorON)
|
|
else
|
|
if FChecked then
|
|
Canvas.Brush.Color := ColorON
|
|
else
|
|
Canvas.Brush.Color := ColorOFF;
|
|
end else
|
|
Canvas.Brush.Color := Color;
|
|
Canvas.Pen.Color := BorderColor;
|
|
end else begin
|
|
Canvas.Brush.Color := clInactiveBorder;
|
|
Canvas.Pen.Color := clGrayText;
|
|
end;
|
|
Canvas.Brush.Style := bsSolid;
|
|
Canvas.Pen.Style := psSolid;
|
|
Canvas.Pen.Width := GetBorderWidth;
|
|
case FBorderStyle of
|
|
bsNone:
|
|
begin
|
|
Canvas.Pen.Style := psClear;
|
|
Canvas.Rectangle(R);
|
|
end;
|
|
bsThin:
|
|
Canvas.Rectangle(R);
|
|
bsThick:
|
|
Canvas.Rectangle(1, 1, Width, Height);
|
|
bsThin3D, bsThick3D:
|
|
begin
|
|
Canvas.Pen.Width := 1;
|
|
Canvas.Pen.Color := clBtnShadow;
|
|
Canvas.Line(R.Left, R.Top, R.Right, R.Top);
|
|
Canvas.Line(R.Left, R.Top, R.Left, R.Bottom);
|
|
if FBorderStyle = bsThick3D then begin
|
|
Canvas.Line(R.Left+1, R.Top+1, R.Right-1, R.Top+1);
|
|
Canvas.Line(R.Left+1, R.Top+1, R.Left+1, R.Bottom-1);
|
|
end;
|
|
Canvas.Pen.Color := clBtnHighlight;
|
|
Canvas.Line(R.Left, R.Bottom-1, R.Right, R.Bottom-1);
|
|
Canvas.Line(R.Right-1, R.Top, R.Right-1, R.Bottom);
|
|
if FBorderStyle = bsThin3D then
|
|
begin
|
|
InflateRect(R, -1, -1);
|
|
Canvas.FillRect(R)
|
|
end else
|
|
begin
|
|
InflateRect(R, -2, -2);
|
|
Canvas.Line(R.Left-1, R.Bottom, R.Right+1, R.Bottom);
|
|
Canvas.Line(R.Right, R.Top-1, R.Right, R.Bottom);
|
|
Canvas.FillRect(R);
|
|
end;
|
|
end;
|
|
bsNoneRounded, bsThinRounded, bsThickRounded:
|
|
begin
|
|
case FBorderStyle of
|
|
bsNoneRounded: Canvas.Pen.Style := psClear;
|
|
bsThickRounded: InflateRect(R, -1, -1);
|
|
end;
|
|
if Orientation = soHorizontal then
|
|
diam := R.Height
|
|
else
|
|
diam := R.Width;
|
|
Canvas.RoundRect(R, diam, diam);
|
|
end;
|
|
end;
|
|
margin := CalcMargin;
|
|
|
|
case Orientation of
|
|
soHorizontal:
|
|
FButtonRect := Rect(0, margin, FButtonSize, Height - margin);
|
|
soVertical:
|
|
FButtonRect := Rect(margin, 0, Width - margin, FButtonSize);
|
|
end;
|
|
|
|
if FShowCaption then begin
|
|
if FDragging then
|
|
newChecked := DraggedToOn //DraggingToON(FMovedDistance)
|
|
else
|
|
newchecked := FChecked;
|
|
case Orientation of
|
|
soHorizontal:
|
|
if FChecked xor FInverse then begin
|
|
// Drag begins from button at right
|
|
if FDragging and not (FInverse xor newChecked) then
|
|
DrawCaption(Rect(margin + FButtonSize, margin, Width, Height - margin), FInverse)
|
|
else
|
|
DrawCaption(Rect(0, margin, Width - margin - FButtonSize, Height - margin), not FInverse);
|
|
end else begin
|
|
// Drag begins from button at left
|
|
if FDragging and (FInverse xor newChecked) then
|
|
DrawCaption(Rect(0, margin, Width - margin - FButtonSize, Height - margin), not FInverse)
|
|
else
|
|
DrawCaption(Rect(margin + FButtonSize, margin, Width, Height - margin), FInverse);
|
|
end;
|
|
soVertical:
|
|
if FChecked xor FInverse then begin
|
|
// Drag begins from button at bottom
|
|
if FDragging and not (FInverse xor newChecked) then
|
|
DrawCaption(Rect(margin, margin + FButtonSize, Width-margin, Height), FInverse)
|
|
else
|
|
DrawCaption(Rect(margin, 0, Width - margin, Height - margin - FButtonSize), not FInverse);
|
|
end else begin
|
|
// Drag begins from button at top
|
|
if FDragging and (FInverse xor newChecked) then
|
|
DrawCaption(Rect(margin, 0, Width - margin, Height - margin - FButtonSize), not FInverse)
|
|
else
|
|
DrawCaption(Rect(margin, margin + FButtonsize, Width - margin, Height), FInverse);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
R := CalcButtonRect;
|
|
DrawButton(R);
|
|
if Focused and FShowFocusRect then begin
|
|
InflateRect(R, 2, 2);
|
|
DrawFocusRect(R);
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.SetAnimated(AValue: Boolean);
|
|
begin
|
|
if AValue = FAnimated then exit;
|
|
FAnimated := AValue;
|
|
if FAnimated then
|
|
begin
|
|
if (FAnimationTimer = nil) then
|
|
begin
|
|
FAnimationTimer := TTimer.Create(self);
|
|
FAnimationTimer.Interval := 10;
|
|
FAnimationTimer.Enabled := false;
|
|
FAnimationTimer.OnTimer := @DoAnimation;
|
|
end
|
|
end else
|
|
FreeAndNil(FAnimationTimer);
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.SetBorderStyle(AValue: TSwitchBorderStyle);
|
|
begin
|
|
if AValue = FBorderStyle then exit;
|
|
FBorderStyle := AValue;
|
|
UpdateShape;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
|
|
begin
|
|
inherited;
|
|
UpdateShape;
|
|
UpdateMaxDistance;
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.SetButtonSize(AValue: Integer);
|
|
begin
|
|
if (AValue = FButtonSize) and (AValue > 0) then
|
|
exit;
|
|
FButtonSize := AValue;
|
|
UpdateMaxDistance;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.SetCaptions(AChecked: Boolean; AValue: String);
|
|
begin
|
|
if AValue = FCaptions[AChecked] then exit;
|
|
FCaptions[AChecked] := AValue;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.SetChecked(AValue: Boolean);
|
|
begin
|
|
if AValue = FChecked then exit;
|
|
if FDblClickTimer.Enabled then exit;
|
|
if FAnimated then
|
|
begin
|
|
case Orientation of
|
|
soHorizontal: FMaxDistance := Width - FButtonSize - 2*CalcMargin;
|
|
soVertical: FMaxDistance := Height - FButtonSize - 2*CalcMargin;
|
|
end;
|
|
FAnimationTimer.Enabled := true;
|
|
FCheckedAfterAnimation := AValue;
|
|
end else
|
|
begin
|
|
FChecked := AValue;
|
|
UpdateButtonPos;
|
|
DoChange;
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.SetColors(AIndex: Integer; AValue: TColor);
|
|
begin
|
|
if AValue = FColors[AIndex] then exit;
|
|
FColors[AIndex] := AValue;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.SetFlippedColors(AValue: Boolean);
|
|
begin
|
|
if AValue = FFlippedColors then exit;
|
|
FFlippedColors := AValue;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.SetImageIndex(AChecked: Boolean; AValue: TImageIndex);
|
|
begin
|
|
if FImageIndex[AChecked] = AValue then Exit;
|
|
FImageIndex[AChecked] := AValue;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.SetImages(const AValue: TCustomImageList);
|
|
begin
|
|
if FImages = AValue then Exit;
|
|
if FImages <> nil then
|
|
begin
|
|
FImages.UnRegisterChanges(FImageListChangeLink);
|
|
FImages.RemoveFreeNotification(Self);
|
|
end;
|
|
FImages := AValue;
|
|
if FImages <> nil then
|
|
begin
|
|
FImages.FreeNotification(Self);
|
|
FImages.RegisterChanges(FImageListChangeLink);
|
|
end;
|
|
DoImageListChange(Self);
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.SetInverse(AValue: boolean);
|
|
begin
|
|
if AValue = FInverse then exit;
|
|
FInverse := AValue;
|
|
UpdateButtonPos;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.SetPicture(AChecked: Boolean; AValue: TPicture);
|
|
begin
|
|
if AValue = FPicture[AChecked] then
|
|
exit;
|
|
if AValue = nil then
|
|
FPicture[AChecked].Clear
|
|
else
|
|
FPicture[AChecked].Assign(AValue);
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.SetShowButtonBorder(AValue: Boolean);
|
|
begin
|
|
if AValue = FShowButtonBorder then exit;
|
|
FShowButtonBorder := AValue;
|
|
DoChange;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.SetShowCaption(AValue: Boolean);
|
|
begin
|
|
if AValue = FShowCaption then exit;
|
|
FShowCaption := AValue;
|
|
DoChange;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.SetShowFocusRect(AValue: Boolean);
|
|
begin
|
|
if AValue = FShowFocusRect then exit;
|
|
FShowFocusRect := AValue;
|
|
DoChange;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.UpdateButtonPos;
|
|
begin
|
|
if FChecked xor FInverse then
|
|
FButtonPos := FMaxDistance
|
|
else
|
|
FButtonPos := 0;
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.UpdateMaxDistance;
|
|
begin
|
|
case Orientation of
|
|
soHorizontal: FMaxDistance := Width - FButtonSize - 2*CalcMargin;
|
|
soVertical: FMaxDistance := Height - FButtonSize - 2*CalcMargin;
|
|
end;
|
|
UpdateButtonPos;
|
|
end;
|
|
|
|
procedure TCustomOnOffSwitch.UpdateShape;
|
|
var
|
|
R: TRect;
|
|
bmp: TBitmap;
|
|
diam: Integer;
|
|
begin
|
|
R := Rect(0, 0, Width, Height);
|
|
bmp := TBitmap.Create;
|
|
try
|
|
bmp.Monochrome := true;
|
|
bmp.SetSize(R.Width, R.Height);
|
|
if FBorderStyle in [bsNoneRounded, bsThinRounded, bsThickRounded] then
|
|
begin
|
|
if Orientation = soHorizontal then
|
|
diam := R.Height
|
|
else
|
|
diam := R.Width;
|
|
bmp.Canvas.Brush.Color := clBlack;
|
|
bmp.Canvas.FillRect(R);
|
|
bmp.Canvas.Brush.Color := clWhite;
|
|
// bmp.Canvas.Pen.Style := psClear;
|
|
bmp.Canvas.Pen.Color := clWhite;
|
|
bmp.Canvas.Pen.Width := GetBorderWidth;
|
|
if FBorderStyle = bsThickRounded then
|
|
InflateRect(R, -1, -1);
|
|
bmp.Canvas.RoundRect(R.Left, R.Top, R.Right, R.Bottom, diam, diam);
|
|
end else
|
|
begin
|
|
bmp.Canvas.Brush.Color := clWhite;
|
|
bmp.Canvas.FillRect(R);
|
|
end;
|
|
SetShape(bmp);
|
|
finally
|
|
bmp.Free;
|
|
end;
|
|
end;
|
|
|
|
end.
|
|
|