2023-02-23 14:56:14 +00:00
|
|
|
(*******************************************************************************
|
|
|
|
* *
|
|
|
|
* File: SpkPopup.pas *
|
|
|
|
* Description: Popup menu for Lazarus port of TSpkToolbar *
|
|
|
|
* Copyright: (c) 2023 by W. Pamler *
|
|
|
|
* License: Modified LGPL (with linking exception, like Lazarus LCL) *
|
|
|
|
' See "license.txt" in this installation *
|
|
|
|
* *
|
|
|
|
* Issues: *
|
|
|
|
* - The popup menu is not borderless - looks strange in Metro Dark *
|
|
|
|
* - Checkbox and radio button are drawn with theme services --> hardly *
|
|
|
|
* hardly visible in Metro Dark *
|
|
|
|
* - Submenu indicator triangle is black --> hardly visible in Metro Dark *
|
|
|
|
* *
|
|
|
|
*******************************************************************************)
|
|
|
|
|
2023-02-21 21:47:21 +00:00
|
|
|
unit SpkPopup;
|
|
|
|
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
|
|
|
|
interface
|
|
|
|
|
|
|
|
uses
|
2023-02-22 13:44:18 +00:00
|
|
|
LCLIntf, LCLType, LCLProc,
|
|
|
|
Types, SysUtils, Classes, Controls, Graphics, Menus, StdCtrls,
|
2023-02-21 21:47:21 +00:00
|
|
|
spkt_Const, SpkGUITools, SpkMath, SpkGraphTools, spkt_Appearance;
|
|
|
|
|
|
|
|
type
|
|
|
|
TSpkPopupMenu = class(TPopupMenu)
|
|
|
|
private
|
|
|
|
FAppearance: TSpkToolbarAppearance;
|
|
|
|
procedure SetAppearance(AValue: TSpkToolbarAppearance);
|
|
|
|
function GetIconSize: TSize;
|
|
|
|
function GetPPI: Integer;
|
|
|
|
|
|
|
|
protected
|
|
|
|
procedure DrawItemHandler(Sender: TObject; ACanvas: TCanvas;
|
|
|
|
ARect: TRect; AState: TOwnerDrawState); virtual;
|
|
|
|
procedure MeasureItemHandler(Sender: TObject; ACanvas: TCanvas;
|
|
|
|
var AWidth, AHeight: Integer); virtual;
|
|
|
|
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
|
|
|
|
|
|
|
|
public
|
|
|
|
//constructor Create(AOwner: TComponent); override;
|
|
|
|
property Appearance: TSpkToolbarAppearance read FAppearance write SetAppearance;
|
|
|
|
end;
|
|
|
|
|
|
|
|
implementation
|
|
|
|
|
2023-02-21 23:18:12 +00:00
|
|
|
uses
|
|
|
|
Themes;
|
|
|
|
|
2023-02-21 21:47:21 +00:00
|
|
|
procedure TSpkPopupMenu.DrawItemHandler(Sender: TObject; ACanvas: TCanvas;
|
|
|
|
ARect: TRect; AState: TOwnerDrawState);
|
2023-02-21 23:18:12 +00:00
|
|
|
const
|
|
|
|
CHECKBOX_STYLES: array[boolean] of TSpkCheckboxStyle = (cbsCheckbox, cbsRadioButton);
|
2023-02-21 21:47:21 +00:00
|
|
|
var
|
|
|
|
menuItem: TMenuItem;
|
|
|
|
FrameColor: TColor = clNone;
|
|
|
|
ColorFrom: TColor;
|
|
|
|
ColorTo: TColor;
|
|
|
|
TextColor: TColor;
|
|
|
|
GradientType: TBackgroundKind;
|
|
|
|
P: T2DIntPoint;
|
2023-02-21 23:18:12 +00:00
|
|
|
R, Rgutter, Rcheck: T2DIntRect;
|
2023-02-27 15:09:47 +00:00
|
|
|
Radius: Integer = 0;
|
2023-02-22 13:44:18 +00:00
|
|
|
x, y, wGutter, hText, wText: Integer;
|
2023-02-21 21:47:21 +00:00
|
|
|
iconSize: TSize;
|
2023-02-21 23:18:12 +00:00
|
|
|
checkboxSize: TSize;
|
2023-02-21 21:47:21 +00:00
|
|
|
isHot: Boolean;
|
2023-02-21 23:18:12 +00:00
|
|
|
te: TThemedElementDetails;
|
2023-02-22 13:44:18 +00:00
|
|
|
shortCutText: String;
|
2023-02-21 21:47:21 +00:00
|
|
|
begin
|
|
|
|
if FAppearance = nil then
|
|
|
|
exit;
|
|
|
|
|
|
|
|
if (Sender is TMenuItem) then
|
|
|
|
begin
|
|
|
|
menuItem := TMenuItem(Sender);
|
|
|
|
|
|
|
|
{$IFDEF EnhancedRecordSupport}
|
|
|
|
R := T2DIntRect.Create(
|
|
|
|
{$ELSE}
|
|
|
|
R := Create2DIntRect(
|
|
|
|
{$ENDIF}
|
|
|
|
ARect.Left,
|
|
|
|
ARect.Top,
|
|
|
|
ARect.Right,
|
2023-02-22 13:44:18 +00:00
|
|
|
ARect.Bottom - 1
|
2023-02-21 21:47:21 +00:00
|
|
|
);
|
|
|
|
isHot := AState * [odSelected, odHotLight] <>[];
|
|
|
|
if not menuItem.Enabled then isHot := false;
|
|
|
|
|
|
|
|
// Draw background
|
|
|
|
if isHot then
|
|
|
|
begin
|
|
|
|
FrameColor := FAppearance.Popup.HotTrackFrameColor;
|
|
|
|
ColorFrom := FAppearance.Popup.HotTrackGradientFromColor;
|
|
|
|
ColorTo := FAppearance.Popup.HotTrackGradientToColor;
|
|
|
|
GradientType := FAppearance.Popup.HotTrackGradientType;
|
2023-02-27 15:09:47 +00:00
|
|
|
if FAppearance.Popup.SelectionShape = ssRounded then
|
|
|
|
Radius := DropdownSelectionRadius
|
2023-02-21 21:47:21 +00:00
|
|
|
end else
|
|
|
|
begin
|
|
|
|
ColorFrom := FAppearance.Popup.IdleGradientFromColor;
|
|
|
|
ColorTo := FAppearance.Popup.IdleGradientToColor;
|
|
|
|
GradientType := FAppearance.Popup.IdleGradientType;
|
|
|
|
end;
|
|
|
|
TGUITools.DrawPopupItemRect(ACanvas, R, Radius, ColorFrom, ColorTo, GradientType);
|
|
|
|
if isHot and (FrameColor <> clNone) then
|
|
|
|
begin
|
2023-02-27 15:09:47 +00:00
|
|
|
if FAppearance.Popup.SelectionShape = ssRounded then
|
|
|
|
TGUITools.DrawRoundRectBorder(ACanvas, R, Radius, FrameColor)
|
|
|
|
else
|
|
|
|
begin
|
|
|
|
TGUITools.DrawHLine(ACanvas, R.Left, R.Right-1, R.Top, FrameColor);
|
|
|
|
TGUITools.DrawHLine(ACanvas, R.Left, R.Right-1, R.Bottom-1, FrameColor);
|
|
|
|
TGUITools.DrawVLine(ACanvas, R.Left, R.Top, R.Bottom-1, FrameColor);
|
|
|
|
TGUITools.DrawVLine(ACanvas, R.Right-1, R.Top, R.Bottom-1, FrameColor);
|
|
|
|
end;
|
2023-02-21 21:47:21 +00:00
|
|
|
end;
|
|
|
|
|
2023-02-23 19:14:35 +00:00
|
|
|
// Gutter
|
|
|
|
|
2023-02-21 21:47:21 +00:00
|
|
|
iconSize := GetIconSize;
|
|
|
|
wGutter := iconSize.CX + 2*DropdownMenuMargin;
|
|
|
|
{$IFDEF EnhancedRecordSupport}
|
|
|
|
Rgutter := T2DIntRect.Create(
|
|
|
|
{$ELSE}
|
|
|
|
Rgutter := Create2DIntRect(
|
|
|
|
{$ENDIF}
|
|
|
|
ARect.Left,
|
|
|
|
ARect.Top,
|
|
|
|
ARect.Left + wGutter - 1,
|
|
|
|
ARect.Bottom
|
|
|
|
);
|
|
|
|
|
|
|
|
if not IsHot and (FAppearance.Popup.Style = psGutter) then
|
|
|
|
begin
|
2023-02-23 19:14:35 +00:00
|
|
|
FrameColor := FAppearance.Popup.GutterFrameColor;
|
2023-02-21 21:47:21 +00:00
|
|
|
ColorFrom := FAppearance.Popup.GutterGradientFromColor;
|
|
|
|
ColorTo := FAppearance.Popup.GutterGradientToColor;
|
|
|
|
GradientType := FAppearance.Popup.GutterGradientType;
|
|
|
|
TGUITools.DrawPopupItemRect(ACanvas, Rgutter, 0, ColorFrom, ColorTo, GradientType);
|
|
|
|
if FrameColor <> clNone then
|
|
|
|
TGUITools.DrawVLine(ACanvas, Rgutter.Right+1, R.Top, R.Bottom, FrameColor);
|
|
|
|
end;
|
|
|
|
|
2023-02-21 23:18:12 +00:00
|
|
|
// Checkbox
|
|
|
|
if menuItem.Checked then
|
|
|
|
begin
|
|
|
|
{$IFDEF EnhancedRecordSupport}
|
|
|
|
Rcheck := T2DIntRect.Create(
|
|
|
|
{$ELSE}
|
|
|
|
Rcheck := Create2DIntRect(
|
|
|
|
{$ENDIF}
|
|
|
|
ARect.Left,
|
|
|
|
ARect.Top,
|
|
|
|
ARect.Left + wGutter,
|
2023-02-22 13:44:18 +00:00
|
|
|
ARect.Bottom - 1
|
2023-02-21 23:18:12 +00:00
|
|
|
);
|
|
|
|
FrameColor := FAppearance.Popup.CheckedFrameColor;
|
|
|
|
ColorFrom := FAppearance.Popup.CheckedGradientFromColor;
|
|
|
|
ColorTo := FAppearance.Popup.CheckedGradientToColor;
|
|
|
|
GradientType := FAppearance.Popup.CheckedGradientType;
|
|
|
|
TGUITools.DrawPopupItemRect(ACanvas, Rcheck, Radius, ColorFrom, ColorTo, GradientType);
|
|
|
|
TGUITools.DrawHLine(ACanvas, Rcheck.Left, Rcheck.Right-1, Rcheck.Top, FrameColor);
|
|
|
|
TGUITools.DrawHLine(ACanvas, Rcheck.Left, Rcheck.Right-1, Rcheck.Bottom-1, FrameColor);
|
|
|
|
TGUITools.DrawVLine(ACanvas, Rcheck.Left, Rcheck.Top, Rcheck.Bottom-1, FrameColor);
|
|
|
|
TGUITools.DrawVLine(ACanvas, Rcheck.Right-1, Rcheck.Top, Rcheck.Bottom-1, FrameColor);
|
|
|
|
|
|
|
|
if not Assigned(Images) or (menuItem.ImageIndex = -1) then
|
|
|
|
begin
|
|
|
|
if ThemeServices.ThemesEnabled then
|
|
|
|
begin
|
|
|
|
if menuItem.Enabled then
|
|
|
|
begin
|
|
|
|
if menuItem.RadioItem then
|
|
|
|
te := ThemeServices.GetElementDetails(tmPopupBulletNormal)
|
|
|
|
else
|
|
|
|
te := ThemeServices.GetElementDetails(tmPopupCheckmarkNormal);
|
|
|
|
end else
|
|
|
|
begin
|
|
|
|
if menuItem.RadioItem then
|
|
|
|
te := ThemeServices.GetElementDetails(tmPopupBulletDisabled)
|
|
|
|
else
|
|
|
|
te := ThemeServices.GetElementDetails(tmPopupCheckmarkDisabled);
|
|
|
|
end;
|
|
|
|
ThemeServices.DrawElement(ACanvas.Handle, te, Rect(Rcheck.Left, Rcheck.Top, Rcheck.Right, Rcheck.Bottom));
|
|
|
|
end else
|
|
|
|
begin
|
|
|
|
checkboxSize.CX := GetSystemMetrics(SM_CYMENUCHECK);
|
|
|
|
checkboxSize.CY := GetSystemMetrics(SM_CXMENUCHECK);
|
|
|
|
x := (Rcheck.Left + Rcheck.Right - checkboxSize.CX) div 2;
|
|
|
|
y := (Rcheck.Top + Rcheck.Bottom - checkboxSize.CY) div 2;
|
|
|
|
TGUITools.DrawCheckbox(
|
|
|
|
ACanvas,
|
|
|
|
x, y,
|
|
|
|
cbChecked,
|
|
|
|
bsIdle,
|
|
|
|
CHECKBOX_STYLES[menuItem.RadioItem]
|
|
|
|
);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2023-02-21 21:47:21 +00:00
|
|
|
// Draw icon
|
|
|
|
if Assigned(Images) and (menuItem.ImageIndex > -1) then
|
|
|
|
begin
|
|
|
|
P := {$IFDEF EnhancedRecordSupport}T2DIntPoint.Create{$ELSE}Create2DIntPoint{$ENDIF}(
|
|
|
|
ARect.Left + DropdownMenuMargin,
|
|
|
|
(ARect.Top + ARect.Bottom - iconSize.CY) div 2
|
|
|
|
);
|
|
|
|
TGUITools.DrawImage(ACanvas, Images, menuItem.ImageIndex, P, RGutter, ImagesWidth, GetPPI, 1.0, menuItem.Enabled);
|
|
|
|
end;
|
|
|
|
|
|
|
|
// Draw text
|
|
|
|
if menuItem.Enabled then
|
|
|
|
begin
|
|
|
|
if isHot then
|
|
|
|
TextColor := FAppearance.Popup.HotTrackCaptionColor
|
|
|
|
else
|
|
|
|
TextColor := FAppearance.Popup.IdleCaptionColor;
|
|
|
|
end else
|
|
|
|
TextColor := FAppearance.Popup.DisabledCaptionColor;
|
|
|
|
|
|
|
|
ACanvas.Font.Assign(FAppearance.Popup.CaptionFont);
|
|
|
|
ACanvas.Font.Color := TextColor;
|
|
|
|
hText := ACanvas.TextHeight('Tg');
|
|
|
|
|
|
|
|
x := wGutter;
|
|
|
|
inc(x, DropdownMenuMargin*2);
|
|
|
|
|
2023-02-22 13:44:18 +00:00
|
|
|
if menuItem.IsLine then
|
2023-02-21 21:47:21 +00:00
|
|
|
begin
|
2023-02-23 19:14:35 +00:00
|
|
|
// Menu dividing lines
|
2023-02-21 21:47:21 +00:00
|
|
|
if FAppearance.Popup.Style <> psGutter then
|
|
|
|
x := DropDownMenuMargin;
|
|
|
|
y := (ARect.Top + ARect.Bottom) div 2;
|
2023-02-23 19:14:35 +00:00
|
|
|
FrameColor := FAppearance.Popup.DividerLineColor;
|
2023-02-23 14:56:14 +00:00
|
|
|
if FrameColor <> clNone then
|
|
|
|
TGUITools.DrawHLine(ACanvas, x, ARect.Right-DropdownMenuMargin, y, FrameColor);
|
2023-02-21 21:47:21 +00:00
|
|
|
end else
|
|
|
|
begin
|
|
|
|
y := (ARect.Top + ARect.Bottom - hText) div 2;
|
2023-02-22 14:27:25 +00:00
|
|
|
if menuItem.ShortCut <> scNone then
|
2023-02-22 13:44:18 +00:00
|
|
|
begin
|
2023-02-23 19:14:35 +00:00
|
|
|
// Shortcut text
|
2023-02-22 13:44:18 +00:00
|
|
|
shortCutText := ShortCutToText(menuItem.ShortCut);
|
2023-02-22 14:27:25 +00:00
|
|
|
if menuItem.ShortCutKey2 <> scNone then
|
|
|
|
shortCutText := ShortCutText + ', ' + ShortCutToText(menuItem.ShortCutKey2);
|
2023-02-23 19:14:35 +00:00
|
|
|
wText := ACanvas.TextWidth(shortCutText) + DropdownMenuMargin;
|
|
|
|
TGUITools.DrawText(ACanvas, ARect.Right - wText, y, shortcutText, TextColor);
|
2023-02-22 13:44:18 +00:00
|
|
|
end else
|
|
|
|
wText := 0;
|
|
|
|
|
2023-02-23 19:14:35 +00:00
|
|
|
// Caption
|
|
|
|
R.Right := ARect.Right - wText; // ClipRect to avoid painting into shortcut text
|
2023-02-22 14:19:19 +00:00
|
|
|
TGUITools.DrawText(ACanvas, x, y, menuItem.Caption, TextColor, R, true);
|
2023-02-21 21:47:21 +00:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
function TSpkPopupMenu.GetIconSize: TSize;
|
|
|
|
begin
|
|
|
|
if Assigned(Images) then
|
|
|
|
Result := Images.SizeForPPI[ImagesWidth, GetPPI]
|
|
|
|
else
|
|
|
|
begin
|
|
|
|
Result.CX := ScaleX(16, 96);
|
|
|
|
Result.CY := Result.CY;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
function TSpkPopupMenu.GetPPI: Integer;
|
|
|
|
begin
|
|
|
|
if Parent is TControl then
|
|
|
|
Result := TControl(Parent).Font.PixelsPerInch
|
|
|
|
else
|
|
|
|
Result := ScreenInfo.PixelsPerInchX;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TSpkPopupMenu.MeasureItemHandler(Sender: TObject; ACanvas: TCanvas;
|
|
|
|
var AWidth, AHeight: Integer);
|
|
|
|
begin
|
|
|
|
//
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TSpkPopupMenu.Notification(AComponent: TComponent; Operation: TOperation);
|
|
|
|
begin
|
|
|
|
inherited Notification(AComponent, Operation);
|
|
|
|
if Operation = opRemove then
|
|
|
|
begin
|
|
|
|
(* FAppearance does not inherit from TComponent !!!!!!!!!!!
|
|
|
|
if AComponent = FAppearance then
|
|
|
|
FAppearance := nil;
|
|
|
|
*)
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TSpkPopupMenu.SetAppearance(AValue: TSpkToolbarAppearance);
|
|
|
|
var
|
|
|
|
i: Integer;
|
|
|
|
begin
|
2023-02-21 22:03:57 +00:00
|
|
|
// if FAppearance = AValue then
|
|
|
|
// exit;
|
2023-02-21 21:47:21 +00:00
|
|
|
FAppearance := AValue;
|
|
|
|
OwnerDraw := FAppearance <> nil;
|
|
|
|
OnDrawItem := @DrawItemHandler;
|
|
|
|
//OnMeasureItem := @MeasureItemHandler;
|
|
|
|
|
|
|
|
for i := 0 to Items.Count-1 do
|
|
|
|
begin
|
|
|
|
Items[i].OnDrawItem := @DrawItemHandler;
|
|
|
|
//Items[i].OnMeasureItem := @MeasureItemHandler;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
end.
|
|
|
|
|