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.