{ Double Commander ------------------------------------------------------------------------- Control like TButton which does not steal focus on click Copyright (C) 2021-2023 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 . } unit KASButton; {$mode delphi} interface uses Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, ExtCtrls, Buttons, Themes, Types; type { TKASButton } TKASButton = class(TPanel) private FState: TButtonState; FShowCaption: Boolean; FButtonGlyph: TButtonGlyph; function GetGlyph: TBitmap; function IsGlyphStored: Boolean; procedure SetGlyph(AValue: TBitmap); procedure SetShowCaption(AValue: Boolean); 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 procedure GlyphChanged(Sender: TObject); class function GetControlClassDefaultSize: TSize; override; procedure ActionChange(Sender: TObject; CheckDefaults: Boolean); override; procedure CalculatePreferredSize(var PreferredWidth, PreferredHeight: Integer; WithThemeSpace: Boolean); override; public constructor Create(TheOwner: TComponent); override; destructor Destroy; override; published property Action; 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; procedure Register; begin RegisterComponents('KASComponents',[TKASButton]); end; { 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.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.Paint; var APoint: TPoint; SysFont: TFont; PaintRect: TRect; 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 if not FButtonGlyph.Glyph.Empty then begin APoint.X:= (PaintRect.Width - FButtonGlyph.Width) div 2; APoint.Y:= (PaintRect.Height - FButtonGlyph.Height) div 2; FButtonGlyph.Draw(Canvas, PaintRect, APoint, FState, True, 0); end; end; procedure TKASButton.MouseEnter; begin inherited MouseEnter; FState:= bsHot; Invalidate; end; procedure TKASButton.MouseLeave; begin inherited MouseLeave; 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; procedure TKASButton.GlyphChanged(Sender: TObject); begin InvalidatePreferredSize; AdjustSize; 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.CalculatePreferredSize(var PreferredWidth, PreferredHeight: Integer; WithThemeSpace: Boolean); var PaintRect: TRect; ClientRect: TRect; Details: TThemedElementDetails; begin inherited CalculatePreferredSize(PreferredWidth, PreferredHeight, WithThemeSpace); if (not FButtonGlyph.Glyph.Empty) then begin Details:= GetDrawDetails; PaintRect:= TRect.Create(0, 0, 32, 32); ClientRect:= ThemeServices.ContentRect(Canvas.Handle, Details, PaintRect); PreferredWidth:= Abs(PaintRect.Width - ClientRect.Width) + FButtonGlyph.Width; PreferredHeight:= Abs(PaintRect.Height - ClientRect.Height) + FButtonGlyph.Height; 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; FShowCaption:= True; TabStop:= True; end; destructor TKASButton.Destroy; begin FreeAndNil(FButtonGlyph); inherited Destroy; end; end.