diff --git a/Components/ModernColors.pas b/Components/ModernColors.pas index e9f21fa56..ce25d50aa 100644 --- a/Components/ModernColors.pas +++ b/Components/ModernColors.pas @@ -30,6 +30,7 @@ TTheme = class property Dark: Boolean read FGetDark; property Modern: Boolean read FGetModern; property Typ: TThemeType read FType write FType; + class function DarkType(Typ: TThemeType): Boolean; end; implementation @@ -100,9 +101,14 @@ function TTheme.FGetColor(Color: TThemeColor): TColor; Result := Colors[FType, Color]; end; +class function TTheme.DarkType(Typ: TThemeType): Boolean; +begin + Result := Typ = ttModernDark; +end; + function TTheme.FGetDark: Boolean; begin - Result := FType = ttModernDark; + Result := DarkType(FType); end; function TTheme.FGetModern: Boolean; diff --git a/Components/NewUxTheme.pas b/Components/NewUxTheme.pas index cfe8dd8cc..6af996570 100644 --- a/Components/NewUxTheme.pas +++ b/Components/NewUxTheme.pas @@ -44,11 +44,13 @@ { } {******************************************************************************} -{ Simplified by Martijn Laan for Inno Setup } +{ Simplified by Martijn Laan for Inno Setup -{ Cannot be replaced by Delphi's built in Winapi.UxTheme.pas even though it has + Cannot be replaced by Delphi's built in Winapi.UxTheme.pas even though it has the same functions: see the comment at the bottom of this file. For this - reason this unit has been renamed to NewUxTheme. } + reason this unit has been renamed to NewUxTheme. + + Additionally this unit includes SetPreferredAppMode. } unit NewUxTheme; @@ -1012,6 +1014,14 @@ _INTLIST = record var EnableTheming: function(fEnable: BOOL): HRESULT; stdcall; + +//---------------------------------------------------------------------------------------------------------------------- + +type + TPreferredAppMode = (pamDefault, pamAllowDark, pamForceDark, pamForceLight, pamMax); + +var + SetPreferredAppMode: function(appMode: TPreferredAppMode): TPreferredAppMode; stdcall; implementation @@ -1085,6 +1095,7 @@ procedure FreeThemeLibrary; GetThemeDocumentationProperty := nil; DrawThemeParentBackground := nil; EnableTheming := nil; + SetPreferredAppMode := nil; end; end; @@ -1100,6 +1111,15 @@ function InitThemeLibrary: Boolean; Result := StrPas(Buf); end; + function WindowsVersionAtLeast(const AMajor, AMinor: Byte; const ABuild: Word): Boolean; + begin + var OSVersionInfo: TOSVersionInfoEx; + OSVersionInfo.dwOSVersionInfoSize := SizeOf(OSVersionInfo); + GetVersionEx(OSVersionInfo); + var WindowsVersion := (Byte(OSVersionInfo.dwMajorVersion) shl 24) or (Byte(OSVersionInfo.dwMinorVersion) shl 16) or Word(OSVersionInfo.dwBuildNumber); + Result := WindowsVersion >= Cardinal((AMajor shl 24) or (AMinor shl 16) or ABuild); + end; + begin Inc(ReferenceCount); @@ -1153,6 +1173,10 @@ function InitThemeLibrary: Boolean; GetThemeDocumentationProperty := GetProcAddress(ThemeLibrary, 'GetThemeDocumentationProperty'); DrawThemeParentBackground := GetProcAddress(ThemeLibrary, 'DrawThemeParentBackground'); EnableTheming := GetProcAddress(ThemeLibrary, 'EnableTheming'); + if WindowsVersionAtLeast(10, 0, 18362) then { 10.0.18362 = Windows 10 Version 1903 (May 2019 Update) } + SetPreferredAppMode := GetProcAddress(ThemeLibrary, MakeIntResource(135)) + else + SetPreferredAppMode := nil; end; end; Result := ThemeLibrary <> 0; diff --git a/Projects/Src/CompForm.dfm b/Projects/Src/CompForm.dfm index 30bd77fe7..0ea035ebc 100644 --- a/Projects/Src/CompForm.dfm +++ b/Projects/Src/CompForm.dfm @@ -19075,4 +19075,271 @@ object CompileForm: TCompileForm Left = 224 Top = 149 end + object DarkVirtualImageList: TVirtualImageList + AutoFill = True + Images = < + item + CollectionIndex = 0 + CollectionName = 'document-new' + Name = 'document-new' + end + item + CollectionIndex = 1 + CollectionName = 'folder-open-filled-arrow-down-right' + Name = 'folder-open-filled-arrow-down-right' + end + item + CollectionIndex = 2 + CollectionName = 'save-filled' + Name = 'save-filled' + end + item + CollectionIndex = 3 + CollectionName = 'build' + Name = 'build' + end + item + CollectionIndex = 4 + CollectionName = 'build-cancel-2' + Name = 'build-cancel-2' + end + item + CollectionIndex = 5 + CollectionName = 'debug-start-filled' + Name = 'debug-start-filled' + end + item + CollectionIndex = 6 + CollectionName = 'debug-break-all-filled' + Name = 'debug-break-all-filled' + end + item + CollectionIndex = 7 + CollectionName = 'install' + Name = 'install' + end + item + CollectionIndex = 8 + CollectionName = 'uninstall' + Name = 'uninstall' + end + item + CollectionIndex = 9 + CollectionName = 'button-help' + Name = 'button-help' + end + item + CollectionIndex = 10 + CollectionName = 'debug-stop-filled' + Name = 'debug-stop-filled' + end + item + CollectionIndex = 11 + CollectionName = 'save-as-filled' + Name = 'save-as-filled' + end + item + CollectionIndex = 12 + CollectionName = 'save-all-filled' + Name = 'save-all-filled' + end + item + CollectionIndex = 13 + CollectionName = 'printer' + Name = 'printer' + end + item + CollectionIndex = 14 + CollectionName = 'command-redo-1' + Name = 'command-redo-1' + end + item + CollectionIndex = 15 + CollectionName = 'command-undo-1' + Name = 'command-undo-1' + end + item + CollectionIndex = 16 + CollectionName = 'clipboard-cut' + Name = 'clipboard-cut' + end + item + CollectionIndex = 17 + CollectionName = 'clipboard-copy' + Name = 'clipboard-copy' + end + item + CollectionIndex = 18 + CollectionName = 'clipboard-paste' + Name = 'clipboard-paste' + end + item + CollectionIndex = 19 + CollectionName = 'select-all' + Name = 'select-all' + end + item + CollectionIndex = 20 + CollectionName = 'find' + Name = 'find' + end + item + CollectionIndex = 21 + CollectionName = 'replace' + Name = 'replace' + end + item + CollectionIndex = 22 + CollectionName = 'control-edit' + Name = 'control-edit' + end + item + CollectionIndex = 23 + CollectionName = 'debug-step-into' + Name = 'debug-step-into' + end + item + CollectionIndex = 24 + CollectionName = 'debug-step-over' + Name = 'debug-step-over' + end + item + CollectionIndex = 25 + CollectionName = 'debug-step-out' + Name = 'debug-step-out' + end + item + CollectionIndex = 26 + CollectionName = 'debug-breakpoint-filled' + Name = 'debug-breakpoint-filled' + end + item + CollectionIndex = 27 + CollectionName = 'variables' + Name = 'variables' + end + item + CollectionIndex = 28 + CollectionName = 'heart-filled' + Name = 'heart-filled' + end + item + CollectionIndex = 29 + CollectionName = 'alert-filled' + Name = 'alert-filled' + end + item + CollectionIndex = 30 + CollectionName = 'home' + Name = 'home' + end + item + CollectionIndex = 31 + CollectionName = 'button-info' + Name = 'button-info' + end + item + CollectionIndex = 32 + CollectionName = 'application' + Name = 'application' + end + item + CollectionIndex = 33 + CollectionName = 'folder-filled-find' + Name = 'folder-filled-find' + end + item + CollectionIndex = 34 + CollectionName = 'gear-filled' + Name = 'gear-filled' + end + item + CollectionIndex = 35 + CollectionName = 'key-filled' + Name = 'key-filled' + end + item + CollectionIndex = 36 + CollectionName = 'letter-a-arrow-right-2' + Name = 'letter-a-arrow-right-2' + end + item + CollectionIndex = 37 + CollectionName = 'symbol-cancel' + Name = 'symbol-cancel' + end + item + CollectionIndex = 38 + CollectionName = 'comment-text-script-filled' + Name = 'comment-text-script-filled' + end + item + CollectionIndex = 39 + CollectionName = 'control-tree-script-filled' + Name = 'control-tree-script-filled' + end + item + CollectionIndex = 40 + CollectionName = 'documents-script-filled' + Name = 'documents-script-filled' + end + item + CollectionIndex = 41 + CollectionName = 'tag-script-filled' + Name = 'tag-script-filled' + end + item + CollectionIndex = 42 + CollectionName = 'control-tab-filled-arrow-left-2' + Name = 'control-tab-filled-arrow-left-2' + end + item + CollectionIndex = 43 + CollectionName = 'control-tab-filled-arrow-right-2' + Name = 'control-tab-filled-arrow-right-2' + end + item + CollectionIndex = 44 + CollectionName = 'unused\control-tab-filled-cancel-2' + Name = 'unused\control-tab-filled-cancel-2' + end + item + CollectionIndex = 45 + CollectionName = 'control-tab-filled-redo-1' + Name = 'control-tab-filled-redo-1' + end + item + CollectionIndex = 46 + CollectionName = 'unused\find-arrow-left-2' + Name = 'unused\find-arrow-left-2' + end + item + CollectionIndex = 47 + CollectionName = 'unused\find-arrow-right-2' + Name = 'unused\find-arrow-right-2' + end + item + CollectionIndex = 48 + CollectionName = 'announcement' + Name = 'announcement' + end + item + CollectionIndex = 49 + CollectionName = 'debug-start-filled-arrow-right-2' + Name = 'debug-start-filled-arrow-right-2' + end + item + CollectionIndex = 50 + CollectionName = 'zoom-in' + Name = 'zoom-in' + end + item + CollectionIndex = 51 + CollectionName = 'zoom-out' + Name = 'zoom-out' + end> + ImageCollection = DarkToolBarImageCollection + Left = 272 + Top = 180 + end end diff --git a/Projects/Src/CompForm.pas b/Projects/Src/CompForm.pas index 47550b490..c9f1b755f 100644 --- a/Projects/Src/CompForm.pas +++ b/Projects/Src/CompForm.pas @@ -189,6 +189,7 @@ TCompileForm = class(TUIStateForm) DarkToolBarImageCollection: TImageCollection; ThemedVirtualImageList: TVirtualImageList; LightVirtualImageList: TVirtualImageList; + DarkVirtualImageList: TVirtualImageList; PListSelectAll: TMenuItem; DebugCallStackList: TListBox; VDebugCallStack: TMenuItem; @@ -412,6 +413,7 @@ TCompileForm = class(TUIStateForm) FPendingSquigglyCaretPos: Integer; FCallStackCount: Cardinal; FDevMode, FDevNames: HGLOBAL; + FMenuImageList: TVirtualImageList; FMenuBitmaps: TMenuBitmaps; FMenuBitmapsSize: TSize; class procedure AppOnException(Sender: TObject; E: Exception); @@ -499,6 +501,7 @@ TCompileForm = class(TUIStateForm) procedure UpdatePreprocMemos; procedure UpdateLineMarkers(const AMemo: TCompScintFileEdit; const Line: Integer); procedure UpdateMemosTabSetVisibility; + procedure UpdateMenuBitmapsIfNeeded; procedure UpdateModifiedPanel; procedure UpdateNewMainFileButtons; procedure UpdateOutputTabSetListsItemHeightAndDebugTimeWidth; @@ -507,7 +510,6 @@ TCompileForm = class(TUIStateForm) procedure UpdateTargetMenu; procedure UpdateTheme; procedure UpdateThemeData(const Open: Boolean); - procedure UpdateMenuBitmapsIfNeeded; procedure ApplyMenuBitmaps(const ParentMenuItem: TMenuItem); procedure UpdateStatusPanelHeight(H: Integer); procedure WMCopyData(var Message: TWMCopyData); message WM_COPYDATA; @@ -588,6 +590,9 @@ TCompileFormPopupMenu = class(TPopupMenu) procedure Popup(X, Y: Integer); override; end; +var + DarkMenusAvailable, DarkMenusEnabled: Boolean; + constructor TCompileFormPopupMenu.Create(const AOwner: TComponent; const ParentMenuItem: TMenuItem); begin inherited Create(AOwner); @@ -3220,6 +3225,8 @@ procedure TCompileForm.TOptionsClick(Sender: TObject); begin OptionsForm := TOptionsForm.Create(Application); try + var OldDarkType := TTheme.DarkType(FOptions.ThemeType); + OptionsForm.StartupCheck.Checked := FOptions.ShowStartupForm; OptionsForm.WizardCheck.Checked := FOptions.UseWizard; OptionsForm.AutosaveCheck.Checked := FOptions.Autosave; @@ -3316,6 +3323,9 @@ procedure TCompileForm.TOptionsClick(Sender: TObject); finally Ini.Free; end; + + if DarkMenusAvailable and (OldDarkType <> TTheme.DarkType(FOptions.ThemeType)) then + MsgBox('The new theme has been activated.' + SNewLine2 + 'A restart is required to also switch the context menus to or from dark mode.', SCompilerFormCaption, mbInformation, MB_OK); finally OptionsForm.Free; end; @@ -4679,6 +4689,10 @@ procedure TCompileForm.UpdateTheme; ThemedVirtualImageList.ImageCollection := DarkToolBarImageCollection else ThemedVirtualImageList.ImageCollection := LightToolBarImageCollection; + if DarkMenusEnabled then + FMenuImageList := DarkVirtualImageList + else + FMenuImageList := LightVirtualImageList; UpdateBevel1Visibility; SplitPanel.ParentBackground := False; SplitPanel.Color := FTheme.Colors[tcSplitterBack]; @@ -4762,7 +4776,7 @@ procedure TCompileForm.UpdateMenuBitmapsIfNeeded; icons *are* present in ThemedVirtualImageList, so even the ones which are not on the toolbar and therefore not used at the moment. } - var ImageList := LightVirtualImageList; + var ImageList := FMenuImageList; var NewSize: TSize; NewSize.cx := ImageList.Width; @@ -5842,6 +5856,23 @@ function TCompileForm.FromCurrentPPI(const XY: Integer): Integer; initialization InitThemeLibrary; + { SetPrefferedAppMode only works to get dark menus when its called before the + form is created so we call it here if dark mode will be activated later on. + When the user switches to or from dark mode TOptionsClick will ask for a + restart. } + if Assigned(SetPreferredAppMode) then begin + DarkMenusAvailable := True; + var Ini := TConfigIniFile.Create; + try + var I := Ini.ReadInteger('Options', 'ThemeType', Ord(GetDefaultThemeType)); + if (I >= 0) and (I <= Ord(High(TThemeType))) and TTheme.DarkType(TThemeType(I)) then begin + SetPreferredAppMode(pamForceDark); + DarkMenusEnabled := True; + end; + finally + Ini.Free; + end; + end; InitHtmlHelpLibrary; { For ClearType support, try to make the default font Microsoft Sans Serif } if DefFontData.Name = 'MS Sans Serif' then diff --git a/whatsnew.htm b/whatsnew.htm index 4014f2796..094c43f69 100644 --- a/whatsnew.htm +++ b/whatsnew.htm @@ -128,7 +128,8 @@
  • Added new Generate [Registry] Entries... (Ctrl+Shift+R) menu item to the Tools menu to import a Windows registry .reg file as extra entries to the [Registry] section at the cursor position, or to a new section.
  • Added new Generate [Files] Entries... (Ctrl+Shift+I) menu item to the Tools menu to design and insert extra entries to the [Files] section at the cursor position, or to a new section.
  • The Generate MsgBox/TaskDialogMsgBox Call... (Ctrl+Shift+M) tool (previously named MsgBox/TaskDialogMsgBox Designer) now respects the tab width and tab character settings, indents the generated Pascal script one extra level, and warns if the cursor position is not in the [Code] section.
  • -
  • Added icons to the main menu items. +
  • Added dark mode support to the context menus on Windows 10 Version 1903 and later. +
  • Added icons to the context menus.
  • Added new [Setup] section directive UninstallLogging, which defaults to no. If set to yes, the uninstaller will always create a log file if it is launched from the Add/Remove Programs Control Panel applet. Equivalent to passing /LOG on the command line.