doublecmd/components/KASToolBar/kasbutton.pas
2026-05-15 17:40:43 +03:00

420 lines
11 KiB
ObjectPascal

{
Double Commander
-------------------------------------------------------------------------
Control like TButton which does not steal focus on click
Copyright (C) 2021-2026 Alexander Koblov (alexx2000@mail.ru)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
}
unit KASButton;
{$mode delphi}
interface
uses
Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, ExtCtrls,
Buttons, Themes, Types, ImgList, LMessages;
type
{ TKASButton }
TKASButton = class(TPanel)
private
FState: TButtonState;
FShowCaption: Boolean;
FMouseInControl: Boolean;
FButtonGlyph: TButtonGlyph;
FImageChangeLink: TChangeLink;
private
function GetGlyph: TBitmap;
function GetImageWidth: Integer;
function IsGlyphStored: Boolean;
procedure SetGlyph(AValue: TBitmap);
function GetImageIndex: TImageIndex;
function GetImages: TCustomImageList;
procedure SetImageWidth(AValue: Integer);
procedure SetShowCaption(AValue: Boolean);
procedure SetImageIndex(AValue: TImageIndex);
procedure SetImages(AValue: TCustomImageList);
function GetDrawDetails: TThemedElementDetails;
protected
procedure Paint; override;
procedure DoExit; override;
procedure DoEnter; override;
procedure MouseEnter; override;
procedure MouseLeave; override;
procedure KeyUp(var Key: Word; Shift: TShiftState); override;
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
protected
function GetGlyphSize: TSize;
procedure GlyphChanged(Sender: TObject);
procedure ImageListChange(Sender: TObject);
class function GetControlClassDefaultSize: TSize; override;
procedure ActionChange(Sender: TObject; CheckDefaults: Boolean); override;
procedure CMEnabledChanged(var Message: TLMessage); message CM_ENABLEDCHANGED;
procedure CalculatePreferredSize(var PreferredWidth, PreferredHeight: Integer; WithThemeSpace: Boolean); override;
public
constructor Create(TheOwner: TComponent); override;
destructor Destroy; override;
published
property Action;
property Images: TCustomImageList read GetImages write SetImages;
property ImageIndex: TImageIndex read GetImageIndex write SetImageIndex default -1;
property ImageWidth: Integer read GetImageWidth write SetImageWidth default 0;
property Glyph: TBitmap read GetGlyph write SetGlyph stored IsGlyphStored;
property ShowCaption: Boolean read FShowCaption write SetShowCaption default True;
end;
procedure Register;
implementation
uses
LCLType, LCLProc, LCLIntf, ActnList, GraphType;
procedure Register;
begin
RegisterComponents('KASComponents',[TKASButton]);
end;
const
UpState: array[Boolean] of TButtonState = (bsUp, bsHot);
{ TKASButton }
procedure TKASButton.DoEnter;
begin
inherited DoEnter;
FState:= bsExclusive;
Invalidate;
end;
procedure TKASButton.DoExit;
begin
inherited DoExit;
FState:= bsUp;
Invalidate;
end;
function TKASButton.GetDrawDetails: TThemedElementDetails;
var
Detail: TThemedButton;
begin
if not IsEnabled then
Detail := tbPushButtonDisabled
else if FState = bsDown then
Detail := tbPushButtonPressed
else if FState = bsHot then
Detail := tbPushButtonHot
else if FState = bsExclusive then
Detail := tbPushButtonDefaulted
else begin
Detail := tbPushButtonNormal;
end;
Result := ThemeServices.GetElementDetails(Detail)
end;
procedure TKASButton.SetShowCaption(AValue: Boolean);
begin
if FShowCaption = AValue then Exit;
FShowCaption:= AValue;
Invalidate;
end;
function TKASButton.GetGlyph: TBitmap;
begin
Result:= FButtonGlyph.Glyph;
end;
function TKASButton.GetImageIndex: TImageIndex;
begin
Result:= FButtonGlyph.ExternalImageIndex;
end;
function TKASButton.GetImages: TCustomImageList;
begin
Result:= FButtonGlyph.ExternalImages;
end;
function TKASButton.GetImageWidth: Integer;
begin
Result:= FButtonGlyph.ExternalImageWidth;
end;
function TKASButton.IsGlyphStored: Boolean;
var
Act: TCustomAction;
begin
if Action <> nil then
begin
Result:= True;
Act:= TCustomAction(Action);
if (Act.ActionList <> nil) and (Act.ActionList.Images <> nil) and
(Act.ImageIndex >= 0) and (Act.ImageIndex < Act.ActionList.Images.Count) then
Result := False;
end
else Result:= (FButtonGlyph.Glyph <> nil) and (not FButtonGlyph.Glyph.Empty) and
(FButtonGlyph.Glyph.Width > 0) and (FButtonGlyph.Glyph.Height > 0);
end;
procedure TKASButton.SetGlyph(AValue: TBitmap);
begin
FButtonGlyph.Glyph := AValue;
InvalidatePreferredSize;
AdjustSize;
end;
procedure TKASButton.SetImageIndex(AValue: TImageIndex);
begin
FButtonGlyph.ExternalImageIndex:= AValue;
end;
procedure TKASButton.SetImages(AValue: TCustomImageList);
begin
if FButtonGlyph.ExternalImages <> nil then
begin
FButtonGlyph.ExternalImages.UnRegisterChanges(FImageChangeLink);
FButtonGlyph.ExternalImages.RemoveFreeNotification(Self);
end;
FButtonGlyph.ExternalImages := AValue;
if FButtonGlyph.ExternalImages <> nil then
begin
FButtonGlyph.ExternalImages.FreeNotification(Self);
FButtonGlyph.ExternalImages.RegisterChanges(FImageChangeLink);
end;
InvalidatePreferredSize;
AdjustSize;
end;
procedure TKASButton.SetImageWidth(AValue: Integer);
begin
FButtonGlyph.ExternalImageWidth:= AValue;
InvalidatePreferredSize;
AdjustSize;
end;
procedure TKASButton.Paint;
var
APoint: TPoint;
SysFont: TFont;
PaintRect: TRect;
AGlyphSize: TSize;
TextFlags: Integer;
Details: TThemedElementDetails;
begin
PaintRect:= ClientRect;
Details:= GetDrawDetails;
ThemeServices.DrawElement(Canvas.Handle, Details, PaintRect);
PaintRect := ThemeServices.ContentRect(Canvas.Handle, Details, PaintRect);
if FShowCaption and (Caption <> EmptyStr) then
begin
TextFlags := DT_CENTER or DT_VCENTER;
if UseRightToLeftReading then begin
TextFlags := TextFlags or DT_RTLREADING;
end;
SysFont := Screen.SystemFont;
if (SysFont.Color = Font.Color) and
((SysFont.Name = Font.Name) or IsFontNameDefault(Font.Name)) and
(SysFont.Pitch = Font.Pitch) and (SysFont.Style = Font.Style) then
begin
ThemeServices.DrawText(Canvas, Details, Caption, PaintRect, TextFlags, 0);
end
else begin
Canvas.Brush.Style := bsClear;
DrawText(Canvas.Handle, PChar(Caption), Length(Caption), PaintRect, TextFlags);
end;
end
else begin
AGlyphSize:= GetGlyphSize;
if (AGlyphSize.CX > 0) and (AGlyphSize.CY > 0) then
begin
APoint.X:= (PaintRect.Width - AGlyphSize.CX) div 2;
APoint.Y:= (PaintRect.Height - AGlyphSize.CY) div 2;
FButtonGlyph.Draw(Canvas, PaintRect, APoint, FState, True, 0, Font.PixelsPerInch, GetCanvasScaleFactor);
end;
end;
end;
procedure TKASButton.MouseEnter;
begin
inherited MouseEnter;
FMouseInControl:= True;
FState:= bsHot;
Invalidate;
end;
procedure TKASButton.MouseLeave;
begin
inherited MouseLeave;
FMouseInControl:= False;
FState:= bsUp;
Invalidate;
end;
procedure TKASButton.KeyUp(var Key: Word; Shift: TShiftState);
begin
inherited KeyUp(Key, Shift);
if (Key in [VK_SPACE, VK_RETURN]) and (Shift = []) then
begin
FState:= bsUp;
Invalidate;
Click;
end;
end;
procedure TKASButton.KeyDown(var Key: Word; Shift: TShiftState);
begin
inherited KeyDown(Key, Shift);
if (Key in [VK_SPACE, VK_RETURN]) and (Shift = []) then
begin
FState:= bsDown;
Invalidate;
end;
end;
procedure TKASButton.MouseUp(Button: TMouseButton; Shift: TShiftState; X,
Y: Integer);
begin
inherited MouseUp(Button, Shift, X, Y);
FState:= bsUp;
Invalidate;
end;
procedure TKASButton.MouseDown(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer);
begin
inherited MouseDown(Button, Shift, X, Y);
FState:= bsDown;
Invalidate;
end;
function TKASButton.GetGlyphSize: TSize;
var
AIndex: Integer;
AEffect: TGraphicsDrawEffect;
AImageRes: TScaledImageListResolution;
begin
if (FButtonGlyph.Glyph.Empty) and ((Images = nil) or (ImageIndex = -1)) then
begin
Result.CX:= 0;
Result.CY:= 0;
Exit;
end;
FButtonGlyph.GetImageIndexAndEffect(Low(TButtonState), Font.PixelsPerInch,
GetCanvasScaleFactor, AImageRes, AIndex, AEffect);
Result.CX:= AImageRes.Width;
Result.CY:= AImageRes.Height;
end;
procedure TKASButton.GlyphChanged(Sender: TObject);
begin
InvalidatePreferredSize;
AdjustSize;
end;
procedure TKASButton.ImageListChange(Sender: TObject);
begin
if Sender = Images then Invalidate;
end;
class function TKASButton.GetControlClassDefaultSize: TSize;
begin
Result.CX := 23;
Result.CY := 22;
end;
procedure TKASButton.ActionChange(Sender: TObject; CheckDefaults: Boolean);
begin
inherited ActionChange(Sender, CheckDefaults);
if Sender is TCustomAction then
begin
with TCustomAction(Sender) do
begin
if (Glyph.Empty) and (ActionList <> nil) and (ActionList.Images <> nil) and
(ImageIndex >= 0) and (ImageIndex < ActionList.Images.Count) then
ActionList.Images.GetBitmap(ImageIndex, Glyph);
end;
end;
end;
procedure TKASButton.CMEnabledChanged(var Message: TLMessage);
begin
if Enabled then
FState:= UpState[FMouseInControl]
else begin
FState:= bsDisabled;
end;
inherited CMEnabledChanged(Message);
end;
procedure TKASButton.CalculatePreferredSize(var PreferredWidth,
PreferredHeight: Integer; WithThemeSpace: Boolean);
var
PaintRect: TRect;
ClientRect: TRect;
AGlyphSize: TSize;
Details: TThemedElementDetails;
begin
inherited CalculatePreferredSize(PreferredWidth, PreferredHeight, WithThemeSpace);
AGlyphSize:= GetGlyphSize;
if (AGlyphSize.CX > 0) and (AGlyphSize.CY > 0) then
begin
Details:= GetDrawDetails;
PaintRect:= TRect.Create(0, 0, 32, 32);
ClientRect:= ThemeServices.ContentRect(Canvas.Handle, Details, PaintRect);
PreferredWidth:= Abs(PaintRect.Width - ClientRect.Width) + AGlyphSize.CX;
PreferredHeight:= Abs(PaintRect.Height - ClientRect.Height) + AGlyphSize.CY;
end;
end;
constructor TKASButton.Create(TheOwner: TComponent);
begin
inherited Create(TheOwner);
FButtonGlyph := TButtonGlyph.Create;
FButtonGlyph.NumGlyphs := 1;
FButtonGlyph.OnChange := GlyphChanged;
FButtonGlyph.IsDesigning := csDesigning in ComponentState;
FImageChangeLink := TChangeLink.Create;
FImageChangeLink.OnChange := ImageListChange;
FShowCaption:= True;
TabStop:= True;
end;
destructor TKASButton.Destroy;
begin
FreeAndNil(FButtonGlyph);
FreeAndNil(FImageChangeLink);
inherited Destroy;
end;
end.