// SPDX-License-Identifier: LGPL-3.0-linking-exception {******************************* CONTRIBUTOR(S) ****************************** - Edivando S. Santos Brasil | mailedivando@gmail.com (Compatibility with delphi VCL 11/2018) ***************************** END CONTRIBUTOR(S) *****************************} unit ColorSpeedButton; {$I bgracontrols.inc} {$ifdef windows} {$define overridepaint} {$endif} interface uses Classes, SysUtils, Types, {$IFDEF FPC}LCLType, LCLProc, LResources,{$ENDIF} {$IFNDEF FPC}BGRAGraphics, GraphType, FPImage, {$ENDIF} Forms, Controls, Graphics, Dialogs, Buttons, BGRASpeedButton, Themes {$ifdef overridepaint}, Math{$ENDIF}; type { TColorState } TColorState = class(TPersistent) private FOwner: TControl; FBorderColor: TColor; FBorderWidth: integer; FColor: TColor; procedure SetFBorderColor(AValue: TColor); procedure SetFBorderWidth(AValue: integer); procedure SetFColor(AValue: TColor); public constructor Create(AOwner: TControl); published property Color: TColor read FColor write SetFColor; property BorderColor: TColor read FBorderColor write SetFBorderColor; property BorderWidth: integer read FBorderWidth write SetFBorderWidth; end; { TColorSpeedButton } TColorSpeedButton = class(TBGRASpeedButton) private {$ifdef overridepaint} FLastDrawDetails: TThemedElementDetails; {$endif} FPopupMode: boolean; FPressed: boolean; FStateActive: TColorState; FStateDisabled: TColorState; FStateHover: TColorState; FStateNormal: TColorState; FTextAutoSize: boolean; FToggle: boolean; procedure SetFPopupMode(AValue: boolean); procedure SetFPressed(AValue: boolean); procedure SetFStateActive(AValue: TColorState); procedure SetFStateDisabled(AValue: TColorState); procedure SetFStateHover(AValue: TColorState); procedure SetFStateNormal(AValue: TColorState); procedure SetFTextAutoSize(AValue: boolean); procedure SetFToggle(AValue: boolean); protected {$ifdef overridepaint} procedure DrawText({%H-}ACanvas: TPersistent; {%H-}Details: TThemedElementDetails; const S: string; R: TRect; Flags, {%H-}Flags2: cardinal); procedure MeasureDraw(Draw: boolean; PaintRect: TRect; out PreferredWidth, PreferredHeight: integer); procedure Paint; override; procedure CalculatePreferredSize(var PreferredWidth, PreferredHeight: integer; {%H-}WithThemeSpace: boolean); override; {$endif} procedure PaintBackground(var PaintRect: TRect); {$IFDEF FPC}override;{$ENDIF} public constructor Create(TheOwner: TComponent); override; destructor Destroy; override; procedure Click; override; published property TextAutoSize: boolean read FTextAutoSize write SetFTextAutoSize; property Toggle: boolean read FToggle write SetFToggle; property Pressed: boolean read FPressed write SetFPressed; property PopupMode: boolean read FPopupMode write SetFPopupMode; property StateNormal: TColorState read FStateNormal write SetFStateNormal; property StateHover: TColorState read FStateHover write SetFStateHover; property StateActive: TColorState read FStateActive write SetFStateActive; property StateDisabled: TColorState read FStateDisabled write SetFStateDisabled; end; {$IFDEF FPC}procedure Register;{$ENDIF} implementation {$IFDEF FPC} procedure Register; begin RegisterComponents('BGRA Button Controls', [TColorSpeedButton]); end; {$ENDIF} { TColorSpeedButton } procedure TColorSpeedButton.SetFStateActive(AValue: TColorState); begin if FStateActive = AValue then Exit; FStateActive := AValue; Invalidate; end; procedure TColorSpeedButton.SetFPopupMode(AValue: boolean); begin if FPopupMode = AValue then Exit; FPopupMode := AValue; end; procedure TColorSpeedButton.SetFPressed(AValue: boolean); begin if FPressed = AValue then Exit; FPressed := AValue; Invalidate; end; procedure TColorSpeedButton.SetFStateDisabled(AValue: TColorState); begin if FStateDisabled = AValue then Exit; FStateDisabled := AValue; Invalidate; end; procedure TColorSpeedButton.SetFStateHover(AValue: TColorState); begin if FStateHover = AValue then Exit; FStateHover := AValue; Invalidate; end; procedure TColorSpeedButton.SetFStateNormal(AValue: TColorState); begin if FStateNormal = AValue then Exit; FStateNormal := AValue; Invalidate; end; procedure TColorSpeedButton.SetFTextAutoSize(AValue: boolean); begin if FTextAutoSize = AValue then Exit; FTextAutoSize := AValue; end; procedure TColorSpeedButton.SetFToggle(AValue: boolean); begin if FToggle = AValue then Exit; FToggle := AValue; Invalidate; end; {$ifdef overridepaint} procedure TColorSpeedButton.DrawText(ACanvas: TPersistent; Details: TThemedElementDetails; const S: string; R: TRect; Flags, Flags2: cardinal); var TXTStyle: TTextStyle; begin TXTStyle := Canvas.TextStyle; TXTStyle.Opaque := False; TXTStyle.Clipping := (Flags and DT_NOCLIP) = 0; TXTStyle.ShowPrefix := (Flags and DT_NOPREFIX) = 0; TXTStyle.SingleLine := (Flags and DT_SINGLELINE) <> 0; if (Flags and DT_CENTER) <> 0 then TXTStyle.Alignment := taCenter else if (Flags and DT_RIGHT) <> 0 then TXTStyle.Alignment := taRightJustify else TXTStyle.Alignment := taLeftJustify; if (Flags and DT_VCENTER) <> 0 then TXTStyle.Layout := tlCenter else if (Flags and DT_BOTTOM) <> 0 then TXTStyle.Layout := tlBottom else TXTStyle.Layout := tlTop; TXTStyle.RightToLeft := (Flags and DT_RTLREADING) <> 0; // set color here, otherwise SystemFont is wrong if the button was disabled before TXTStyle.SystemFont := Canvas.Font.IsDefault;//Match System Default Style TXTStyle.Wordbreak := (Flags and DT_WORDBREAK) <> 0; if not TXTStyle.Wordbreak then TXTStyle.EndEllipsis := (Flags and DT_END_ELLIPSIS) <> 0 else TXTStyle.EndEllipsis := False; Canvas.TextRect(R, R.Left, R.Top, S, TXTStyle); end; procedure TColorSpeedButton.MeasureDraw(Draw: boolean; PaintRect: TRect; out PreferredWidth, PreferredHeight: integer); var GlyphWidth, GlyphHeight: integer; Offset, OffsetCap: TPoint; ClientSize, TotalSize, TextSize, GlyphSize: TSize; M, S: integer; SIndex: longint; TMP: string; TextFlags: integer; DrawDetails: TThemedElementDetails; FixedWidth: boolean; FixedHeight: boolean; TextRect: TRect; HasGlyph: boolean; HasText: boolean; CurLayout: TButtonLayout; begin if Glyph = nil then exit; DrawDetails := GetDrawDetails; PreferredWidth := 0; PreferredHeight := 0; if Draw then begin FLastDrawDetails := DrawDetails; PaintBackground(PaintRect); FixedWidth := True; FixedHeight := True; end else begin FixedWidth := WidthIsAnchored; FixedHeight := HeightIsAnchored; end; ClientSize.cx := PaintRect.Right - PaintRect.Left; ClientSize.cy := PaintRect.Bottom - PaintRect.Top; //debugln(['TCustomSpeedButton.MeasureDraw Step1 ',DbgSName(Self),' PaintRect=',dbgs(PaintRect)]); // compute size of glyph GlyphSize := GetGlyphSize(Draw, PaintRect); GlyphWidth := GlyphSize.CX; if NumGlyphs > 1 then GlyphWidth := GlyphWidth div NumGlyphs; GlyphHeight := GlyphSize.CY; HasGlyph := (GlyphWidth <> 0) and (GlyphHeight <> 0); //debugln(['TCustomSpeedButton.MeasureDraw Step2 ',DbgSName(Self),' PaintRect=',dbgs(PaintRect),' GlyphSize=',GlyphWidth,'x',GlyphHeight]); // compute size of text CurLayout := BidiAdjustButtonLayout(UseRightToLeftReading, Layout); if ShowCaption and (Caption <> '') then begin TextRect := PaintRect; // for wordbreak compute the maximum size for the text if Margin > 0 then InflateRect(TextRect, -Margin, -Margin); if HasGlyph then begin if (Spacing >= 0) then if CurLayout in [blGlyphLeft, blGlyphRight] then Dec(TextRect.Right, Spacing) else Dec(TextRect.Bottom, Spacing); if CurLayout in [blGlyphLeft, blGlyphRight] then Dec(TextRect.Right, GlyphWidth) else Dec(TextRect.Bottom, GlyphHeight); end; if not FixedWidth then begin TextRect.Left := 0; TextRect.Right := High(TextRect.Right) div 2; end; if not FixedHeight then begin TextRect.Top := 0; TextRect.Bottom := High(TextRect.Bottom) div 2; end; TextSize := GetTextSize(Draw, TextRect); end else begin TextSize.cx := 0; TextSize.cy := 0; end; HasText := (TextSize.cx <> 0) or (TextSize.cy <> 0); if Caption <> '' then begin TMP := Caption; SIndex := DeleteAmpersands(TMP); if SIndex > 0 then if SIndex <= Length(TMP) then begin //FShortcut := Ord(TMP[SIndex]); end; end; if HasGlyph and HasText then S := Spacing else S := 0; M := Margin; if not Draw then begin if M < 0 then M := 2; if S < 0 then S := M; end; // Calculate caption and glyph layout if M = -1 then begin // auto compute margin to center content if S = -1 then begin // use the same value for Spacing and Margin TotalSize.cx := TextSize.cx + GlyphWidth; TotalSize.cy := TextSize.cy + GlyphHeight; if Layout in [blGlyphLeft, blGlyphRight] then M := (ClientSize.cx - TotalSize.cx) div 3 else M := (ClientSize.cy - TotalSize.cy) div 3; S := M; end else begin // fixed Spacing and center content TotalSize.cx := GlyphWidth + S + TextSize.cx; TotalSize.cy := GlyphHeight + S + TextSize.cy; if Layout in [blGlyphLeft, blGlyphRight] then M := (ClientSize.cx - TotalSize.cx) div 2 else M := (ClientSize.cy - TotalSize.cy) div 2; end; end else begin // fixed Margin if S = -1 then begin // use the rest for Spacing between Glyph and Caption TotalSize.cx := ClientSize.cx - (Margin + GlyphWidth); TotalSize.cy := ClientSize.cy - (Margin + GlyphHeight); if Layout in [blGlyphLeft, blGlyphRight] then S := (TotalSize.cx - TextSize.cx) div 2 else S := (TotalSize.cy - TextSize.cy) div 2; end; end; //debugln(['TCustomSpeedButton.MeasureDraw Step3 ',DbgSName(Self),' PaintRect=',dbgs(PaintRect),' GlyphSize=',GlyphWidth,'x',GlyphHeight,' TextSize=',TextSize.cx,'x',TextSize.cy,' S=',S,' M=',M]); if Draw then begin case CurLayout of blGlyphLeft: begin Offset.X := M; Offset.Y := (ClientSize.cy - GlyphHeight) div 2; OffsetCap.X := Offset.X + GlyphWidth + S; OffsetCap.Y := (ClientSize.cy - TextSize.cy) div 2; end; blGlyphRight: begin Offset.X := ClientSize.cx - M - GlyphWidth; Offset.Y := (ClientSize.cy - GlyphHeight) div 2; OffsetCap.X := Offset.X - S - TextSize.cx; OffsetCap.Y := (ClientSize.cy - TextSize.cy) div 2; end; blGlyphTop: begin Offset.X := (ClientSize.cx - GlyphWidth) div 2; Offset.Y := M; OffsetCap.X := (ClientSize.cx - TextSize.cx) div 2; OffsetCap.Y := Offset.Y + GlyphHeight + S; end; blGlyphBottom: begin Offset.X := (ClientSize.cx - GlyphWidth) div 2; Offset.Y := ClientSize.cy - M - GlyphHeight; OffsetCap.X := (ClientSize.cx - TextSize.cx) div 2; OffsetCap.Y := Offset.Y - S - TextSize.cy; end; end; DrawGlyph(Canvas, PaintRect, Offset, FState, Transparent, 0); if ShowCaption and (Caption <> '') then begin with PaintRect, OffsetCap do begin Left := Left + X; Top := Top + Y; end; TextFlags := DT_LEFT or DT_TOP; if UseRightToLeftReading then TextFlags := TextFlags or DT_RTLREADING; if Draw then DrawText(Canvas, DrawDetails, Caption, PaintRect, TextFlags, 0); end; end else begin // measuring, not drawing case CurLayout of blGlyphLeft, blGlyphRight: begin // use text size for autosize if FTextAutoSize then begin PreferredWidth := 2 * M + S + GlyphWidth + TextSize.cx; PreferredHeight := 2 * M + Max(GlyphHeight, TextSize.cy); end else begin // ignore text size width and height PreferredWidth := 2 * M + S + GlyphWidth; PreferredHeight := 2 * M + {Max(}GlyphHeight{, TextSize.cy)}; end; end; blGlyphTop, blGlyphBottom: begin if FTextAutoSize then begin PreferredWidth := 2 * M + Max(GlyphWidth, TextSize.cx); PreferredHeight := 2 * M + S + GlyphHeight + TextSize.cy; end else begin // ignore text size width and height PreferredWidth := 2 * M + S + GlyphWidth; PreferredHeight := 2 * M + S + GlyphHeight{ + TextSize.cy}; end; end; end; end; end; procedure TColorSpeedButton.Paint; var PaintRect: TRect; PreferredWidth: integer; PreferredHeight: integer; begin UpdateState(False); if Glyph = nil then exit; PaintRect := ClientRect; MeasureDraw(True, PaintRect, PreferredWidth, PreferredHeight); if Assigned(OnPaint) then OnPaint(Self); end; procedure TColorSpeedButton.CalculatePreferredSize( var PreferredWidth, PreferredHeight: integer; WithThemeSpace: boolean); var r: TRect; begin r := Rect(0, 0, 0, 0); MeasureDraw(False, r, PreferredWidth, PreferredHeight); end; {$endif} procedure TColorSpeedButton.PaintBackground(var PaintRect: TRect); var TempState: TButtonState; begin TempState := FState; if Toggle and Pressed then TempState := bsDown; Canvas.Pen.JoinStyle := pjsMiter; // remove rounded borders Canvas.Pen.Style := psInsideframe; // draws border width inside equally case TempState of bsUp: begin Canvas.Pen.Color := FStateNormal.BorderColor; Canvas.Pen.Width := FStateNormal.BorderWidth; Canvas.Brush.Color := FStateNormal.Color; end; bsDisabled: begin Canvas.Pen.Color := FStateDisabled.BorderColor; Canvas.Pen.Width := FStateDisabled.BorderWidth; Canvas.Brush.Color := FStateDisabled.Color; end; bsDown, bsExclusive: begin Canvas.Pen.Color := FStateActive.BorderColor; Canvas.Pen.Width := FStateActive.BorderWidth; Canvas.Brush.Color := FStateActive.Color; end; {$IFDEF FPC}//# bsHot: begin Canvas.Pen.Color := FStateHover.BorderColor; Canvas.Pen.Width := FStateHover.BorderWidth; Canvas.Brush.Color := FStateHover.Color; end; {$ENDIF} end; if Canvas.Pen.Width = 0 then Canvas.Pen.Color := Canvas.Brush.Color; Canvas.Rectangle(PaintRect); end; constructor TColorSpeedButton.Create(TheOwner: TComponent); begin inherited Create(TheOwner); FStateNormal := TColorState.Create(Self); FStateHover := TColorState.Create(Self); FStateActive := TColorState.Create(Self); FStateDisabled := TColorState.Create(Self); { Windows Style } FStateNormal.Color := RGBToColor(225, 225, 225); FStateNormal.BorderColor := RGBToColor(173, 173, 173); FStateHover.Color := RGBToColor(229, 241, 251); FStateHover.BorderColor := RGBToColor(0, 120, 215); FStateActive.Color := RGBToColor(204, 228, 247); FStateActive.BorderColor := RGBToColor(0, 84, 153); FStateDisabled.Color := RGBToColor(204, 204, 204); FStateDisabled.BorderColor := RGBToColor(191, 191, 191); Font.Color := clBlack; FTextAutoSize := True; end; destructor TColorSpeedButton.Destroy; begin FStateNormal.Free; FStateHover.Free; FStateActive.Free; FStateDisabled.Free; inherited Destroy; end; procedure TColorSpeedButton.Click; var p: TPoint; begin if Toggle then Pressed := not Pressed; if PopupMode then begin p := Parent.ClientToScreen(Point(Left, Top)); PopupMenu.PopUp(p.x, p.y + Height); end; inherited Click; end; { TColorState } procedure TColorState.SetFBorderColor(AValue: TColor); begin if FBorderColor = AValue then Exit; FBorderColor := AValue; FOwner.Perform(CM_CHANGED, 0, 0); FOwner.Invalidate; end; procedure TColorState.SetFBorderWidth(AValue: integer); begin if FBorderWidth = AValue then Exit; FBorderWidth := AValue; FOwner.Perform(CM_CHANGED, 0, 0); FOwner.Invalidate; end; procedure TColorState.SetFColor(AValue: TColor); begin if FColor = AValue then Exit; FColor := AValue; FOwner.Perform(CM_CHANGED, 0, 0); FOwner.Invalidate; end; constructor TColorState.Create(AOwner: TControl); begin inherited Create; FOwner := AOwner; BorderWidth := 1; BorderColor := clBlack; Color := clWhite; end; end.