lazarus-ccr/components/exctrls/source/exbuttons.pas
2021-03-28 21:19:47 +00:00

742 lines
19 KiB
ObjectPascal

unit ExButtons;
{$mode objfpc}{$H+}
interface
uses
Graphics, Classes, SysUtils, LMessages, Types, Controls, StdCtrls, Forms;
type
TButtonExState = (bxsNormal, bxsHot, bxsDown, bxsFocused, bxsDisabled);
TButtonExBorderWidth = 1..10;
TButtonEx = class;
TButtonExBorder = class(TPersistent)
private
FButton: TButtonEx;
FColorNormal: TColor;
FColorHot: TColor;
FColorDown: TColor;
FColorDisabled: TColor;
FColorFocused: TColor;
FWidthNormal: TButtonExBorderWidth;
FWidthHot: TButtonExBorderWidth;
FWidthDown: TButtonExBorderWidth;
FWidthDisabled: TButtonExBorderWidth;
FWidthFocused: TButtonExBorderWidth;
procedure SetWidthNormal(const Value: TButtonExBorderWidth);
procedure SetColorNormal(const Value: TColor);
public
constructor Create(AButton: TButtonEx);
published
property ColorNormal: TColor read FColorNormal write SetColorNormal;
property ColorHot: TColor read FColorHot write FColorHot;
property ColorDown: TColor read FColorDown write FColorDown;
property ColorDisabled: TColor read FColorDisabled write FColorDisabled;
property ColorFocused: TColor read FColorFocused write FColorFocused;
property WidthNormal: TButtonExBorderWidth read FWidthNormal write SetWidthNormal;
property WidthHot: TButtonExBorderWidth read FWidthHot write FWidthHot;
property WidthDown: TButtonExBorderWidth read FWidthDown write FWidthDown;
property WidthDisabled: TButtonExBorderWidth read FWidthDisabled write FWidthDisabled;
property WidthFocused: TButtonExBorderWidth read FWidthFocused write FWidthFocused;
end;
TButtonExColors = class(TPersistent)
private
FButton: TButtonEx;
FColorNormalFrom: TColor;
FColorNormalTo: TColor;
FColorHotFrom: TColor;
FColorHotTo: TColor;
FColorDownFrom: TColor;
FColorDownTo: TColor;
FColorDisabledFrom: TColor;
FColorDisabledTo: TColor;
FColorFocusedFrom: TColor;
FColorFocusedTo: TColor;
procedure SetColorNormalFrom(const Value: TColor);
procedure SetColorNormalTo(const Value: TColor);
public
constructor Create(AButton: TButtonEx);
published
property ColorNormalFrom: TColor read FColorNormalFrom write SetColorNormalFrom;
property ColorNormalTo: TColor read FColorNormalTo write SetColorNormalTo;
property ColorHotFrom: TColor read FColorHotFrom write FColorHotFrom;
property ColorHotTo: TColor read FColorHotTo write FColorHotTo;
property ColorDownFrom: TColor read FColorDownFrom write FColorDownFrom;
property ColorDownTo: TColor read FColorDownTo write FColorDownTo;
property ColorDisabledFrom: TColor read FColorDisabledFrom write FColorDisabledFrom;
property ColorDisabledTo: TColor read FColorDisabledTo write FColorDisabledTo;
property ColorFocusedFrom: TColor read FColorFocusedFrom write FColorFocusedFrom;
property ColorFocusedTo: TColor read FColorFocusedTo write FColorFocusedTo;
end;
TButtonEx = class(TCustomButton)
private
FAlignment: TAlignment;
FBorder: TButtonExBorder;
FCanvas: TCanvas;
FColors: TButtonExColors;
FDefaultDrawing: Boolean;
FFontDisabled: TFont;
FFontDown: TFont;
FFontFocused: TFont;
FFontHot: TFont;
FGradient: Boolean;
FMargin: integer;
FShowFocusRect: Boolean;
FState: TButtonExState;
FWordwrap: Boolean;
procedure SetAlignment(const Value: TAlignment);
procedure SetDefaultDrawing(const Value: Boolean);
procedure SetGradient(const Value: Boolean);
procedure SetShowFocusRect(const Value: Boolean);
procedure SetMargin(const Value: integer);
procedure SetWordWrap(const Value: Boolean);
protected
procedure CalculatePreferredSize(var PreferredWidth, PreferredHeight: Integer;
WithThemeSpace: Boolean); override;
class function GetControlClassDefaultSize: TSize; override;
function GetDrawTextFlags: Cardinal;
procedure MouseEnter; override;
procedure MouseLeave; override;
procedure PaintButton;
procedure PaintCustomButton;
procedure PaintThemedButton;
procedure WMKillFocus(var Message: TLMKillFocus); message LM_KILLFOCUS;
procedure WMPaint(var Msg: TLMPaint); message LM_PAINT;
procedure WMSetFocus(var Message: TLMSetFocus); message LM_SETFOCUS;
procedure WndProc(var Message: TLMessage); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Align;
property Anchors;
property AutoSize;
property BorderSpacing;
property Cancel;
property Caption;
//property Color; removed for new property Colors
property Constraints;
property Cursor;
property Default;
//property DoubleBuffered; // PaintButton is not called when this is set.
property DragCursor;
property DragKind;
property DragMode;
property Enabled;
property Font;
property Height;
property HelpContext;
property HelpKeyword;
property HelpType;
property Hint;
property Left;
property ModalResult;
property ParentBiDiMode;
//property ParentDoubleBuffered;
property ParentFont;
property ParentShowHint;
property PopupMenu;
property ShowHint;
property TabOrder;
property TabStop default True;
property Tag;
property Top;
property Visible;
property Width;
property OnChangeBounds;
property OnClick;
property OnContextPopup;
property OnDragDrop;
property OnDragOver;
property OnEndDrag;
property OnEnter;
property OnExit;
property OnKeyDown;
property OnKeyPress;
property OnKeyUp;
property OnMouseDown;
property OnMouseEnter;
property OnMouseLeave;
property OnMouseMove;
property OnMouseUp;
property OnResize;
property OnStartDrag;
property OnUTF8KeyPress;
property Alignment: TAlignment read FAlignment write SetAlignment default taCenter;
property Border: TButtonExBorder read FBorder write FBorder;
property Colors: TButtonExColors read FColors write FColors;
property DefaultDrawing: Boolean read FDefaultDrawing write SetDefaultDrawing default true;
property FontDisabled: TFont read FFontDisabled write FFontDisabled;
property FontDown: TFont read FFontDown write FFontDown;
property FontFocused: TFont read FFontFocused write FFontFocused;
property FontHot: TFont read FFontHot write FFontHot;
property Gradient: Boolean read FGradient write SetGradient default true;
property Margin: integer read FMargin write SetMargin;
property ShowFocusRect: Boolean read FShowFocusRect write SetShowFocusRect default true;
property Wordwrap: Boolean read FWordWrap write SetWordWrap default false;
end;
implementation
uses
LCLType, LCLIntf, Themes;
{ TButtonExBorder }
constructor TButtonExBorder.Create(AButton: TButtonEx);
begin
inherited Create;
FButton := AButton;
end;
procedure TButtonExBorder.SetColorNormal(const Value: TColor);
begin
if FColorNormal = Value then exit;
FColorNormal := Value;
FButton.Invalidate;
end;
procedure TButtonExBorder.SetWidthNormal(const Value: TButtonExBorderWidth);
begin
if FWidthNormal = Value then exit;
FWidthNormal := Value;
FButton.Invalidate;
end;
{ TButtonExColors }
constructor TButtonExColors.Create(AButton: TButtonEx);
begin
inherited Create;
FButton := AButton;
end;
procedure TButtonExColors.SetColorNormalFrom(const Value: TColor);
begin
if FColorNormalFrom = Value then
exit;
FColorNormalFrom := Value;
FButton.Invalidate;
end;
procedure TButtonExColors.SetColorNormalTo(const Value: TColor);
begin
if FColorNormalTo = Value then
exit;
FColorNormalTo := Value;
FButton.Invalidate;
end;
{ TButtonEx }
constructor TButtonEx.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FCanvas := TControlCanvas.Create;
TControlCanvas(FCanvas).Control := Self;
FDefaultDrawing := true;
FGradient := true;
FShowFocusRect := true;
// Background colors
FColors := TButtonExColors.Create(Self);
FColors.ColorNormalFrom := $00FCFCFC;
FColors.ColorNormalTo := $00CFCFCF;
FColors.ColorHotFrom := $00FCFCFC;
FColors.ColorHotTo := $00F5D9A7;
FColors.ColorDownFrom := $00FCFCFC;
FColors.ColorDownTo := $00DBB368;
FColors.ColorDisabledFrom := $00F4F4F4;
FColors.ColorDisabledTo := $00F4F4F4;
FColors.ColorFocusedFrom := $00FCFCFC;
FColors.ColorFocusedTo := $00CFCFCF;
// Fonts
FFontDisabled := TFont.Create;
FFontDisabled.Assign(Font);
FFontDisabled.Color := clGrayText;
FFontDisabled.OnChange := @FontChanged;
FFontDown := TFont.Create;
FFontDown.Assign(Font);
FFontDown.OnChange := @FontChanged;
FFontFocused := TFont.Create;
FFontFocused.Assign(Font);
FFontFocused.OnChange := @FontChanged;
FFontHot := TFont.Create;
FFontHot.Assign(Font);
FFontHot.OnChange := @FontChanged;
// Border
FBorder := TButtonExBorder.Create(Self);
FBorder.ColorNormal := $00707070;
FBorder.ColorHot := $00B17F3C;
FBorder.ColorDown := $008B622C;
FBorder.ColorDisabled := $00B5B2AD;
FBorder.ColorFocused := $00B17F3C;
FBorder.WidthNormal := 1;
FBorder.WidthHot := 1;
FBorder.WidthDown := 1;
FBorder.WidthDisabled := 1;
FBorder.WidthFocused := 1;
// Other
FMargin := 5;
FAlignment := taCenter;
FState := bxsNormal;
end;
destructor TButtonEx.Destroy;
begin
FFontHot.Free;
FFontDown.Free;
FFontDisabled.Free;
FFontFocused.Free;
FColors.Free;
FBorder.Free;
FCanvas.Free;
inherited;
end;
procedure TButtonEx.CalculatePreferredSize(var PreferredWidth,
PreferredHeight: Integer; WithThemeSpace: Boolean);
var
flags: Cardinal;
txtSize: TSize;
R: TRect;
details: TThemedElementDetails;
begin
FCanvas.Font.Assign(Font);
R := ClientRect;
InflateRect(R, -FMargin, 0);
R.Bottom := MaxInt; // Max height possible
flags := GetDrawTextFlags + DT_CALCRECT;
// rectangle available for text
details := ThemeServices.GetElementDetails(tbPushButtonNormal);
if FWordWrap then
begin
with ThemeServices.GetTextExtent(FCanvas.Handle, details, Caption, flags, @R) do begin
txtSize.CX := Right;
txtSize.CY := Bottom;
end;
end else
with ThemeServices.GetTextExtent(FCanvas.Handle, details, Caption, flags, nil) do begin
txtSize.CX := Right;
txtSize.CY := Bottom;
end;
PreferredHeight := txtSize.CY + 2 * FMargin;
PreferredWidth := txtSize.CX + 2 * FMargin;
if not FWordWrap then
PreferredHeight := 0;
end;
class function TButtonEx.GetControlClassDefaultSize: TSize;
begin
Result.CX := 75;
Result.CY := 25;
end;
function TButtonEx.GetDrawTextFlags: Cardinal;
begin
Result := DT_VCENTER or DT_NOPREFIX;
case FAlignment of
taLeftJustify:
if IsRightToLeft then Result := Result or DT_RIGHT else Result := Result or DT_LEFT;
taRightJustify:
if IsRightToLeft then Result := Result or DT_LEFT else Result := Result or DT_RIGHT;
taCenter:
Result := Result or DT_CENTER;
end;
if IsRightToLeft then
result := Result or DT_RTLREADING;
if FWordWrap then
Result := Result or DT_WORDBREAK and not DT_SINGLELINE
else
Result := Result or DT_SINGLELINE and not DT_WORDBREAK;;
end;
procedure TButtonEx.MouseEnter;
begin
if FState <> bxsDisabled then
begin
FState := bxsHot;
Invalidate;
end;
inherited;
end;
procedure TButtonEx.MouseLeave;
begin
if (FState <> bxsDisabled) then
begin
if Focused then
FState := bxsFocused
else
FState := bxsNormal;
Invalidate;
end;
inherited;
end;
procedure TButtonEx.PaintButton;
begin
if FDefaultDrawing then
PaintThemedButton
else
PaintCustomButton;
end;
procedure TButtonEx.PaintCustomButton;
var
lCanvas: TCanvas;
lBitmap: TBitmap;
lRect, R: TRect;
lBorderColor: TColor;
lBorderWidth: integer;
lColorFrom: TColor;
lColorTo: TColor;
lTextFont: TFont;
lAlignment: TAlignment;
flags: Cardinal;
i: integer;
txtSize: TSize;
txtPt: TPoint;
ts: TTextStyle;
begin
if (csDestroying in ComponentState) or not HandleAllocated then
exit;
// Bitmap
lBitmap := TBitmap.Create;
lBitmap.Width := Width;
lBitmap.Height := Height;
lCanvas := lBitmap.Canvas;
// State
lBorderColor := Border.ColorNormal;
lColorFrom := Colors.ColorNormalFrom;
lColorTo := Colors.ColorNormalTo;
lTextFont := Font;
lBorderWidth := Border.WidthNormal;
if not (csDesigning in ComponentState) then
begin
case FState of
bxsFocused:
begin
lBorderColor := FBorder.ColorFocused;
lColorFrom := FColors.ColorFocusedFrom;
lColorTo := FColors.ColorFocusedTo;
lTextFont := FFontFocused;
lBorderWidth := FBorder.WidthFocused;
end;
bxsHot:
begin
lBorderColor := FBorder.ColorHot;
lColorFrom := FColors.ColorHotFrom;
lColorTo := FColors.ColorHotTo;
lTextFont := FFontHot;
lBorderWidth := FBorder.WidthHot;
end;
bxsDown:
begin
lBorderColor := FBorder.ColorDown;
lColorFrom := FColors.ColorDownFrom;
lColorTo := FColors.ColorDownTo;
lTextFont := FFontDown;
lBorderWidth := FBorder.WidthDown;
end;
bxsDisabled:
begin
lBorderColor := FBorder.ColorDisabled;
lColorFrom := FColors.ColorDisabledFrom;
lColorTo := FColors.ColorDisabledTo;
lTextFont := FFontDisabled;
lBorderWidth := FBorder.WidthDisabled;
end;
end;
end;
// Background
lRect := Rect(0, 0, Width, Height);
if FGradient then
lCanvas.GradientFill(lRect, lColorFrom, lColorTo, gdVertical)
else
begin
lCanvas.Brush.Style := bsSolid;
lCanvas.Brush.Color := lColorFrom;
lCanvas.FillRect(lRect);
end;
// Border
lCanvas.Pen.Width := 1;
lCanvas.Pen.Color := lBorderColor;
for i := 1 to lBorderWidth do
begin
lCanvas.MoveTo(i - 1, i - 1);
lCanvas.LineTo(Width - i, i - 1);
lCanvas.LineTo(Width - i, Height - i);
lCanvas.LineTo(i - 1, Height - i);
lCanvas.LineTo(i - 1, i - 1);
end;
// Caption
lCanvas.Pen.Width := 1;
lCanvas.Brush.Style := bsClear;
lCanvas.Font.Assign(lTextFont);
flags := GetDrawTextFlags;
R := lRect;
DrawText(FCanvas.Handle, PChar(Caption), Length(Caption), R, flags + DT_CALCRECT);
txtSize.CX := R.Right - R.Left;
txtSize.CY := R.Bottom - R.Top;
lAlignment := FAlignment;
if IsRightToLeft then begin
if lAlignment = taLeftJustify then
lAlignment := taRightJustify
else if lAlignment = taRightJustify then
lAlignment := taLeftJustify;
end;
case lAlignment of
taLeftJustify:
txtPt.X := FMargin;
taRightJustify:
txtPt.X := Width - txtSize.CX - FMargin;
taCenter:
txtPt.X := (Width - txtSize.CX) div 2;
end;
txtPt.Y := (Height - txtSize.CY + 1) div 2;
R := Rect(txtPt.X, txtPt.Y, txtPt.X + txtSize.CX, txtPt.Y + txtSize.CY);
// Do not draw the text with DrawText(...) because it requires SingleLine=true
// for vertical centering.
ts := lCanvas.TextStyle;
ts.Alignment := lAlignment;
ts.Layout := tlCenter;
ts.SingleLine := not FWordWrap;
ts.Wordbreak := FWordWrap;
lCanvas.TextRect(R, txtPt.X, txtPt.Y, Caption, ts);
// Draw focus rectangle
if FShowFocusRect and Focused then
begin
InflateRect(lRect, -2, -2);
DrawFocusRect(lCanvas.Handle, lRect);
end;
// Draw the button
FCanvas.Draw(0, 0, lBitmap);
lBitmap.Free;
end;
procedure TButtonEx.PaintThemedButton;
var
btn: TThemedButton;
details: TThemedElementDetails;
lRect: TRect;
flags: Cardinal;
txtSize: TSize;
txtPt: TPoint;
begin
if (csDestroying in ComponentState) or not HandleAllocated then
exit;
lRect := Rect(0, 0, Width, Height);
if FState = bxsDisabled then
btn := tbPushButtonDisabled
else if FState = bxsDown then
btn := tbPushButtonPressed
else if FState = bxsHot then
btn := tbPushButtonHot
else
if Focused or Default then
btn := tbPushButtonDefaulted
else
btn := tbPushButtonNormal;
// Background
details := ThemeServices.GetElementDetails(btn);
InflateRect(lRect, 1, 1);
ThemeServices.DrawElement(FCanvas.Handle, details, lRect);
InflateRect(lRect, -1, -1);
// Text
FCanvas.Font.Assign(Font);
flags := GetDrawTextFlags;
with ThemeServices.GetTextExtent(FCanvas.Handle, details, Caption, flags, @lRect) do begin
txtSize.CX := Right;
txtSize.CY := Bottom;
end;
case FAlignment of
taLeftJustify:
if IsRightToLeft then
txtPt.X := Width - txtSize.CX - FMargin
else
txtPt.X := FMargin;
taRightJustify:
if IsRightToLeft then
txtPt.X := FMargin
else
txtPt.X := Width - txtSize.CX - FMargin;
taCenter:
txtPt.X := (Width - txtSize.CX) div 2;
end;
txtPt.Y := (Height + 1 - txtSize.CY) div 2;
lRect := Rect(txtPt.X, txtPt.Y, txtPt.X + txtSize.CX, txtPt.Y + txtSize.CY);
ThemeServices.DrawText(FCanvas, details, Caption, lRect, flags, 0);
end;
procedure TButtonEx.SetAlignment(const Value: TAlignment);
begin
if FAlignment = Value then
exit;
FAlignment := Value;
Invalidate;
end;
procedure TButtonEx.SetDefaultDrawing(const Value: Boolean);
begin
if FDefaultDrawing = Value then
exit;
FDefaultDrawing := Value;
Invalidate;
end;
procedure TButtonEx.SetGradient(const Value: Boolean);
begin
if FGradient = Value then
exit;
FGradient := Value;
Invalidate;
end;
procedure TButtonEx.SetShowFocusRect(const Value: Boolean);
begin
if FShowFocusRect = Value then
exit;
FShowFocusRect := Value;
if Focused then Invalidate;
end;
procedure TButtonEx.SetMargin(const Value: integer);
begin
if FMargin = Value then
exit;
FMargin := Value;
Invalidate;
end;
procedure TButtonEx.SetWordWrap(const Value: Boolean);
begin
if FWordWrap = Value then
exit;
FWordWrap := Value;
Invalidate;
end;
procedure TButtonEx.WMKillFocus(var Message: TLMKillFocus);
begin
inherited WMKillFocus(Message);
if (FState = bxsFocused) then
begin
FState := bxsNormal;
Invalidate;
end;
end;
procedure TButtonEx.WMSetFocus(var Message: TLMSetFocus);
begin
inherited WMSetFocus(Message);
if (FState = bxsNormal) then
begin
FState := bxsFocused;
Invalidate;
end;
end;
procedure TButtonEx.WMPaint(var Msg: TLMPaint);
begin
inherited;
PaintButton;
end;
procedure TButtonEx.WndProc(var Message: TLMessage);
begin
if not (csDesigning in ComponentState) then
begin
case Message.Msg of
LM_KEYDOWN:
begin
if (Message.WParam = VK_RETURN) or (Message.WParam = VK_SPACE) then
if FState <> bxsDisabled then
FState := bxsDown;
Invalidate;
end;
LM_KEYUP:
begin
if (Message.WParam = VK_RETURN) or (Message.WParam = VK_SPACE) then
if FState <> bxsDisabled then
FState := bxsFocused;
Invalidate;
end;
CM_DIALOGKEY:
begin
if (Message.WParam = VK_RETURN) and Default and (not Focused) and (FState <> bxsDisabled) then
Click;
if (Message.WParam = VK_ESCAPE) and Cancel and (FState <> bxsDisabled) then
Click;
end;
CM_ENABLEDCHANGED:
begin
if not Enabled then
FState := bxsDisabled
else
FState := bxsNormal;
Invalidate;
end;
LM_LBUTTONDOWN:
begin
FState := bxsDown;
Invalidate;
end;
LM_LBUTTONUP:
begin
if (FState <> bxsNormal) and (FState <> bxsFocused) and (FState <> bxsDisabled) then
begin
FState := bxsHot;
Invalidate;
end;
end;
end;
end;
inherited;
end;
end.