diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3a7ca0d08..da534e1d5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,15 +21,16 @@ jobs: runs-on: windows-latest strategy: matrix: - configuration: [Release] # No need to distribute Debug builds + configuration: [Debug] # No need to distribute Debug builds platform: [x64] - framework: [net8.0-windows10.0.22621.0] + framework: [net9.0-windows10.0.22621.0] env: Configuration: ${{ matrix.configuration }} Platform: ${{ matrix.platform }} DOTNET_INSTALL_DIR: '.\.dotnet' - DOTNET_VERSION: '8.x' + DOTNET_VERSION: '9.x' + DOTNET_QUALITY: 'preview' NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages steps: @@ -42,6 +43,48 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} + dotnet-quality: ${{ env.DOTNET_QUALITY }} + cache: true + cache-dependency-path: CollapseLauncher/packages.lock.json + + - name: Build + run: | + dotnet publish CollapseLauncher -p:PublishProfile=Publish-DebugCIRelease -p:PublishDir=".\debug-build\" + + - name: Upload Artifact + uses: actions/upload-artifact@v4.3.1 + with: + name: collapse_${{ matrix.platform }}-${{ matrix.configuration }}_${{ matrix.framework }}_${{ github.sha }} + path: ./CollapseLauncher/debug-build/ + compression-level: 9 + + build-nativeaot: + runs-on: windows-latest + strategy: + matrix: + configuration: [Debug] + platform: [x64] + framework: [net9.0-windows10.0.22621.0] + + env: + Configuration: ${{ matrix.configuration }} + Platform: ${{ matrix.platform }} + DOTNET_INSTALL_DIR: '.\.dotnet' + DOTNET_VERSION: '9.x' + DOTNET_QUALITY: 'preview' + NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages + + steps: + - name: Checkout + uses: actions/checkout@v4.1.5 + with: + submodules: recursive + + - name: Install .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + dotnet-quality: ${{ env.DOTNET_QUALITY }} cache: true cache-dependency-path: CollapseLauncher/packages.lock.json @@ -51,16 +94,20 @@ jobs: - name: Build run: | - dotnet publish CollapseLauncher -p:PublishProfile=Publish-PreviewRelease -p:PublishDir=".\preview-build\" + dotnet publish CollapseLauncher -p:PublishProfile=Publish-DebugCIReleaseAOT -p:PublishDir=".\debug-aot-build\" - - name: Upload Artifact (Release) + - name: Upload Artifact uses: actions/upload-artifact@v4.3.1 - if: ${{ matrix.configuration == 'Release' }} with: - name: collapse_${{ matrix.platform }}-${{ matrix.configuration }}_${{ matrix.framework }}_${{ github.sha }} - path: ./CollapseLauncher/preview-build/ + name: aot-experimental_collapse_${{ matrix.platform }}-${{ matrix.configuration }}_${{ matrix.framework }}_${{ github.sha }} + path: ./CollapseLauncher/debug-aot-build/ compression-level: 9 + notify-discord: + runs-on: ubuntu-latest + if: always() + needs: [build, build-nativeaot] + steps: - name: Notify Discord uses: sarisia/actions-status-discord@v1.13.0 if: always() diff --git a/.gitignore b/.gitignore index 7fe060537..b2fcd1f69 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -*/bin/* -*/obj/* +**/bin/* +**/obj/* *build/* .vs/* *.user @@ -7,7 +7,7 @@ packages/* CollapseLauncher/Deps/* CollapseLauncher/Invoker/* -CollapseLauncher/Generated Files/** +**/Generated Files/** *.psd InstallerProp/Output/* InstallerProp/temp/** diff --git a/.gitmodules b/.gitmodules index 8c68a51a8..bd0331340 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,6 @@ [submodule "SevenZipExtractor"] path = SevenZipExtractor url = https://github.com/CollapseLauncher/SevenZipExtractor +[submodule "H.NotifyIcon"] + path = H.NotifyIcon + url = https://github.com/CollapseLauncher/H.NotifyIcon diff --git a/Backup/CommunityToolkit.WinUI.Controls.ImageCropper.csproj b/Backup/CommunityToolkit.WinUI.Controls.ImageCropper.csproj new file mode 100644 index 000000000..0e97ae7dd --- /dev/null +++ b/Backup/CommunityToolkit.WinUI.Controls.ImageCropper.csproj @@ -0,0 +1,30 @@ + + + + + ImageCropper + The ImageCropper control allows the user to freely crop an image. + + + CommunityToolkit.WinUI.Controls.ImageCropperRns + ReadMe.md + + + + + + + + + + + + True + \ + + + + + $(PackageIdPrefix).$(PackageIdVariant).Controls.$(ToolkitComponentName) + + diff --git a/Backup/ReadMe.md b/Backup/ReadMe.md new file mode 100644 index 000000000..07942198d --- /dev/null +++ b/Backup/ReadMe.md @@ -0,0 +1,38 @@ + +# Windows Community Toolkit - ImageCropper + +This package is part of the [Windows Community Toolkit](https://aka.ms/toolkit/windows) from the [.NET Foundation](https://dotnetfoundation.org). + +## Package Contents + +This package contains the following controls in the `CommunityToolkit.WinUI.Controls` namespace: + +- ImageCropper + +## Which Package is for me? + +If you're developing with _UWP/WinUI 2 or Uno.UI_ you should be using the `CommunityToolkit.Uwp.Controls.ImageCropper` package. + +If you're developing with _WindowsAppSDK/WinUI 3 or Uno.WinUI_ you should be using the `CommunityToolkit.WinUI.Controls.ImageCropper` package. + +## WinUI Resources (UWP) + +For UWP projects, the WinUI 2 reference requires you include the WinUI XAML Resources in your App.xaml file: + +```xml + + + +``` + +See [Getting Started in WinUI 2](https://learn.microsoft.com/windows/apps/winui/winui2/getting-started) for more information. + +## Documentation + +Further documentation about these components can be found at: https://aka.ms/windowstoolkitdocs + +## License + +MIT + +See License.md in package for more details. diff --git a/ClearCache.bat b/ClearCache.bat index 50a691b8b..c42722760 100644 --- a/ClearCache.bat +++ b/ClearCache.bat @@ -3,6 +3,10 @@ echo Clearing Collapse cache rmdir /S /Q CollapseLauncher\bin && rmdir /S /Q CollapseLauncher\obj echo Clearing ColorThief cache rmdir /S /Q ColorThief\ColorThief\bin && rmdir /S /Q ColorThief\ColorThief\obj +echo Clearing CommunityToolkit.ImageCropper cache +rmdir /S /Q Hi3Helper.CommunityToolkit\ImageCropper\bin && rmdir /S /Q Hi3Helper.CommunityToolkit\ImageCropper\obj +echo Clearing CommunityToolkit.SettingsControls cache +rmdir /S /Q Hi3Helper.CommunityToolkit\SettingsControls\bin && rmdir /S /Q Hi3Helper.CommunityToolkit\SettingsControls\obj echo Clearing Core cache rmdir /S /Q Hi3Helper.Core\bin && rmdir /S /Q Hi3Helper.Core\obj echo Clearing EncTool cache @@ -13,12 +17,16 @@ echo Clearing Http cache rmdir /S /Q Hi3Helper.Http\bin && rmdir /S /Q Hi3Helper.Http\obj echo Clearing Http tester cache rmdir /S /Q Hi3Helper.Http\Test\bin && rmdir /S /Q Hi3Helper.Http\Test\obj +echo Clearing TaskScheduler cache +rmdir /S /Q Hi3Helper.TaskScheduler\bin && rmdir /S /Q Hi3Helper.TaskScheduler\obj echo Clearing HDiff cache rmdir /S /Q Hi3Helper.SharpHDiffPatch\Hi3Helper.SharpHDiffPatch\bin && rmdir /S /Q Hi3Helper.SharpHDiffPatch\Hi3Helper.SharpHDiffPatch\obj echo Clearing 2nd HDiff cache rmdir /S /Q Hi3Helper.SharpHDiffPatch\SharpHDiffPatch\bin && rmdir /S /Q Hi3Helper.SharpHDiffPatch\SharpHDiffPatch\obj echo Clearing InnoSetupHelper cache rmdir /S /Q InnoSetupHelper\bin && rmdir /S /Q InnoSetupHelper\obj +echo Clearing ImageEx cache +rmdir /S /Q ImageEx\ImageEx\bin && rmdir /S /Q ImageEx\ImageEx\obj echo Clearing 7z cache rmdir /S /Q Hi3Helper.Core\Classes\Data\Tools\SevenZipTool\SevenZipExtractor\SevenZipExtractor\bin && rmdir /S /Q Hi3Helper.Core\Classes\Data\Tools\SevenZipTool\SevenZipExtractor\SevenZipExtractor\obj echo Clearing SharpDiscordRPC cache diff --git a/CollapseLauncher.sln b/CollapseLauncher.sln index aa68aa993..c6045a5c1 100644 --- a/CollapseLauncher.sln +++ b/CollapseLauncher.sln @@ -24,6 +24,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hi3Helper.Sophon", "Hi3Help EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageEx", "ImageEx\ImageEx\ImageEx.csproj", "{A6AF9DE9-1A18-4C2D-B106-B68A0A7CD07D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hi3Helper.TaskScheduler", "Hi3Helper.TaskScheduler\Hi3Helper.TaskScheduler.csproj", "{C9CBAF52-49C7-4B72-A03B-130F596E24CB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hi3Helper.CommunityToolkit.WinUI.Controls.ImageCropper", "Hi3Helper.CommunityToolkit\ImageCropper\Hi3Helper.CommunityToolkit.WinUI.Controls.ImageCropper.csproj", "{558A1D17-BEB4-49DF-A200-15ABE283BDED}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hi3Helper.CommunityToolkit.WinUI.Controls.SettingsControls", "Hi3Helper.CommunityToolkit\SettingsControls\Hi3Helper.CommunityToolkit.WinUI.Controls.SettingsControls.csproj", "{5A1243EC-EFD9-4B55-8F29-D1A91A9B027D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "H.NotifyIcon.WinUI", "H.NotifyIcon\src\libs\H.NotifyIcon.WinUI\H.NotifyIcon.WinUI.csproj", "{141083CC-A924-4E19-904C-AF91361405A5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "H.NotifyIcon", "H.NotifyIcon\src\libs\H.NotifyIcon\H.NotifyIcon.csproj", "{6C8A25FA-BA1C-4EE4-8A9D-2FB4918077FB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "H.GeneratedIcons.System.Drawing", "H.NotifyIcon\src\libs\H.GeneratedIcons.System.Drawing\H.GeneratedIcons.System.Drawing.csproj", "{911C98FD-C64D-4BAC-8EF5-0616F8EFF7B9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -92,10 +104,44 @@ Global {3F87DCD0-39B7-4F8C-8F34-A0FC7C6E65A0}.Release|x64.Build.0 = Release|x64 {A6AF9DE9-1A18-4C2D-B106-B68A0A7CD07D}.Debug|x64.ActiveCfg = Debug|x64 {A6AF9DE9-1A18-4C2D-B106-B68A0A7CD07D}.Debug|x64.Build.0 = Debug|x64 - {A6AF9DE9-1A18-4C2D-B106-B68A0A7CD07D}.Publish|x64.ActiveCfg = Debug|x64 - {A6AF9DE9-1A18-4C2D-B106-B68A0A7CD07D}.Publish|x64.Build.0 = Debug|x64 + {A6AF9DE9-1A18-4C2D-B106-B68A0A7CD07D}.Publish|x64.ActiveCfg = Release|x64 + {A6AF9DE9-1A18-4C2D-B106-B68A0A7CD07D}.Publish|x64.Build.0 = Release|x64 {A6AF9DE9-1A18-4C2D-B106-B68A0A7CD07D}.Release|x64.ActiveCfg = Release|x64 {A6AF9DE9-1A18-4C2D-B106-B68A0A7CD07D}.Release|x64.Build.0 = Release|x64 + {C9CBAF52-49C7-4B72-A03B-130F596E24CB}.Debug|x64.ActiveCfg = Debug|x64 + {C9CBAF52-49C7-4B72-A03B-130F596E24CB}.Debug|x64.Build.0 = Debug|x64 + {C9CBAF52-49C7-4B72-A03B-130F596E24CB}.Publish|x64.ActiveCfg = Release|x64 + {C9CBAF52-49C7-4B72-A03B-130F596E24CB}.Release|x64.ActiveCfg = Release|x64 + {558A1D17-BEB4-49DF-A200-15ABE283BDED}.Debug|x64.ActiveCfg = Debug|x64 + {558A1D17-BEB4-49DF-A200-15ABE283BDED}.Debug|x64.Build.0 = Debug|x64 + {558A1D17-BEB4-49DF-A200-15ABE283BDED}.Publish|x64.ActiveCfg = Release|x64 + {558A1D17-BEB4-49DF-A200-15ABE283BDED}.Publish|x64.Build.0 = Release|x64 + {558A1D17-BEB4-49DF-A200-15ABE283BDED}.Release|x64.ActiveCfg = Release|x64 + {558A1D17-BEB4-49DF-A200-15ABE283BDED}.Release|x64.Build.0 = Release|x64 + {5A1243EC-EFD9-4B55-8F29-D1A91A9B027D}.Debug|x64.ActiveCfg = Debug|x64 + {5A1243EC-EFD9-4B55-8F29-D1A91A9B027D}.Debug|x64.Build.0 = Debug|x64 + {5A1243EC-EFD9-4B55-8F29-D1A91A9B027D}.Publish|x64.ActiveCfg = Release|x64 + {5A1243EC-EFD9-4B55-8F29-D1A91A9B027D}.Publish|x64.Build.0 = Release|x64 + {5A1243EC-EFD9-4B55-8F29-D1A91A9B027D}.Release|x64.ActiveCfg = Release|x64 + {5A1243EC-EFD9-4B55-8F29-D1A91A9B027D}.Release|x64.Build.0 = Release|x64 + {141083CC-A924-4E19-904C-AF91361405A5}.Debug|x64.ActiveCfg = Debug|x64 + {141083CC-A924-4E19-904C-AF91361405A5}.Debug|x64.Build.0 = Debug|x64 + {141083CC-A924-4E19-904C-AF91361405A5}.Publish|x64.ActiveCfg = Release|x64 + {141083CC-A924-4E19-904C-AF91361405A5}.Publish|x64.Build.0 = Release|x64 + {141083CC-A924-4E19-904C-AF91361405A5}.Release|x64.ActiveCfg = Release|x64 + {141083CC-A924-4E19-904C-AF91361405A5}.Release|x64.Build.0 = Release|x64 + {6C8A25FA-BA1C-4EE4-8A9D-2FB4918077FB}.Debug|x64.ActiveCfg = Debug|x64 + {6C8A25FA-BA1C-4EE4-8A9D-2FB4918077FB}.Debug|x64.Build.0 = Debug|x64 + {6C8A25FA-BA1C-4EE4-8A9D-2FB4918077FB}.Publish|x64.ActiveCfg = Release|x64 + {6C8A25FA-BA1C-4EE4-8A9D-2FB4918077FB}.Publish|x64.Build.0 = Release|x64 + {6C8A25FA-BA1C-4EE4-8A9D-2FB4918077FB}.Release|x64.ActiveCfg = Release|x64 + {6C8A25FA-BA1C-4EE4-8A9D-2FB4918077FB}.Release|x64.Build.0 = Release|x64 + {911C98FD-C64D-4BAC-8EF5-0616F8EFF7B9}.Debug|x64.ActiveCfg = Debug|x64 + {911C98FD-C64D-4BAC-8EF5-0616F8EFF7B9}.Debug|x64.Build.0 = Debug|x64 + {911C98FD-C64D-4BAC-8EF5-0616F8EFF7B9}.Publish|x64.ActiveCfg = Release|x64 + {911C98FD-C64D-4BAC-8EF5-0616F8EFF7B9}.Publish|x64.Build.0 = Release|x64 + {911C98FD-C64D-4BAC-8EF5-0616F8EFF7B9}.Release|x64.ActiveCfg = Release|x64 + {911C98FD-C64D-4BAC-8EF5-0616F8EFF7B9}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/CollapseLauncher/App.xaml b/CollapseLauncher/App.xaml index 03ea596c7..29062f0d2 100644 --- a/CollapseLauncher/App.xaml +++ b/CollapseLauncher/App.xaml @@ -1,4 +1,4 @@ - - + + + + + + + + + + + + + + @@ -28,345 +41,359 @@ #ffd52a #ffd52a #ffd52a - - - - - + + + + + + FallbackColor="{ThemeResource SystemAccentColor}" + Opacity="0.8" + TintColor="{ThemeResource SystemAccentColor}" + TintLuminosityOpacity="0.8" + TintOpacity="0.2" /> + Color="{ThemeResource SystemAccentColorLight2}" /> + Color="#FFFFFF" /> + Color="#000000" /> - - - - + Color="#242424" /> + + + + #ffd52a + Color="{ThemeResource DialogTitleColor}" /> - - - + TintOpacity="0.0" /> + + + + TintOpacity="0.0" /> + TintOpacity="0" /> + TintOpacity="0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="1" /> + TintOpacity="0.6" /> + TintOpacity="0.0" /> + TintOpacity="0.75" /> + TintOpacity="0.0" /> + TintLuminosityOpacity="0.2" + TintOpacity="0.2" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="1" /> - + + TintOpacity="0.0" /> + TintLuminosityOpacity="0.5" + TintOpacity="0.0" /> - - - + TintOpacity="0.0" /> + + + + + Color="#00000000" /> + Color="#22000000" /> + Color="#11000000" /> + Color="#0A000000" /> + + Color="#22000000" /> + Color="#11000000" /> + Color="#0A000000" /> + Color="#44000000" /> + Color="#55000000" /> + Color="#33000000" /> + Color="#22000000" /> - - - + + + + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> - + TintOpacity="0.0" /> + + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> - + TintOpacity="0.0" /> + + TintOpacity="0" /> + Opacity="0.9" + TintColor="{ThemeResource SystemAccentColor}" /> + Opacity="0.75" + TintColor="{ThemeResource SystemAccentColor}" /> + FallbackColor="{ThemeResource SystemAccentColor}" + TintColor="{ThemeResource SystemAccentColor}" /> + Opacity="0.75" + Color="#DDFFFFFF" /> + Color="#EE000000" /> + Color="#CC000000" /> + Color="#FF111111" /> + TintOpacity="0" /> + TintOpacity="0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> - + TintOpacity="0.0" /> + + + Color="#00000000" /> + Color="#00000000" /> + + + Color="#A0C00000" /> + + @@ -378,338 +405,350 @@ #693758 #693758 #693758 - - - - - + + + + + + FallbackColor="{ThemeResource SystemAccentColor}" + Opacity="0.8" + TintColor="{ThemeResource SystemAccentColor}" + TintLuminosityOpacity="0.8" + TintOpacity="0.2" /> + Color="{ThemeResource SystemAccentColor}" /> + Color="#000000" /> + Color="#FFFFFF" /> - - - - + Color="#ECECEC" /> + + + + #693758 + Color="{ThemeResource DialogTitleColor}" /> - - - + TintOpacity="0.0" /> + + + + TintOpacity="0.0" /> + TintOpacity="1" /> + TintOpacity="0" /> + TintOpacity="0" /> + TintLuminosityOpacity="0.9" + TintOpacity="0.4" /> + TintOpacity="0.25" /> + TintOpacity="0.25" /> + TintOpacity="0" /> + TintOpacity="0" /> + TintOpacity="0.0" /> + TintOpacity="0.75" /> + TintOpacity="0.7" /> + TintOpacity="0" /> + TintOpacity="0" /> + TintOpacity="0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> - + TintOpacity="1" /> + + TintOpacity="0.0" /> + TintLuminosityOpacity="0.9" + TintOpacity="0.0" /> + - - - + TintOpacity="0.0" /> + + + + Color="#00FFFFFF" /> + Color="#66FFFFFF" /> + Color="#44FFFFFF" /> + Color="#22FFFFFF" /> + Color="#66FFFFFF" /> + Color="#44FFFFFF" /> + Color="#22FFFFFF" /> + Color="#88FFFFFF" /> + Color="#55FFFFFF" /> + Color="#44FFFFFF" /> - - - - + Color="#22FFFFFF" /> + + + + - + + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> - + TintOpacity="0.0" /> + + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> - + TintOpacity="0.0" /> + + FallbackColor="#88FFFFFF" + TintColor="#88FFFFFF" /> + Opacity="0.9" + TintColor="{ThemeResource SystemAccentColor}" /> + Opacity="0.8" + TintColor="{ThemeResource SystemAccentColor}" /> + FallbackColor="{ThemeResource SystemAccentColor}" + TintColor="{ThemeResource SystemAccentColor}" /> + Color="#88000000" /> + Color="#DDFFFFFF" /> + Color="#CCFFFFFF" /> + Color="#FFFFFF" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> - + TintOpacity="0.0" /> + + + Color="#00FFFFFF" /> + Color="#00FFFFFF" /> + + + Color="#90FF6666" /> + + @@ -718,38 +757,28 @@ + - + - - - - - - - - - - ms-appx:///Assets/Fonts/FontAwesomeBrand6.otf#Font Awesome 6 Brands diff --git a/CollapseLauncher/App.xaml.cs b/CollapseLauncher/App.xaml.cs index 7159fa386..d6cf12ce2 100644 --- a/CollapseLauncher/App.xaml.cs +++ b/CollapseLauncher/App.xaml.cs @@ -8,7 +8,6 @@ using PhotoSauce.MagicScaler; using PhotoSauce.NativeCodecs.Libwebp; using System; -using System.Collections.Generic; using System.Linq; using Windows.UI; using static CollapseLauncher.InnerLauncherConfig; @@ -134,7 +133,8 @@ protected override void OnLaunched(LaunchActivatedEventArgs args) { LogWriteLine($"FATAL ERROR ON APP INITIALIZER LEVEL!!!\r\n{ex}", LogType.Error, true); LogWriteLine("\r\nIf this is not intended, please report it to: https://github.com/CollapseLauncher/Collapse/issues\r\nPress any key to exit..."); - Console.ReadLine(); + //Console.ReadLine(); + throw; } } @@ -147,14 +147,12 @@ public static void ToggleBlurBackdrop(bool useBackdrop = true) // then select the value, get the type of ResourceDictionary, then enumerate it foreach (ResourceDictionary list in resource! .ThemeDictionaries! - .OfType>() .Select(x => x.Value) .OfType()) { // Parse the dictionary as type of KeyValuePair, // and get the value which has type of AcrylicBrush only, then enumerate it foreach (AcrylicBrush theme in list - .OfType>() .Select(x => x.Value) .OfType()) { diff --git a/CollapseLauncher/Classes/Extension/UIElementExtensions.cs b/CollapseLauncher/Classes/Extension/UIElementExtensions.cs index cb79a82f3..ad4cebb6a 100644 --- a/CollapseLauncher/Classes/Extension/UIElementExtensions.cs +++ b/CollapseLauncher/Classes/Extension/UIElementExtensions.cs @@ -1,12 +1,13 @@ using CommunityToolkit.WinUI; -using CommunityToolkit.WinUI.Controls; using Hi3Helper; +using Hi3Helper.CommunityToolkit.WinUI.Controls; using Microsoft.UI; using Microsoft.UI.Input; using Microsoft.UI.Text; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Data; using Microsoft.UI.Xaml.Documents; using Microsoft.UI.Xaml.Media; using System; @@ -20,6 +21,12 @@ namespace CollapseLauncher.Extension { internal enum CornerRadiusKind { Normal, Rounded } + internal class NavigationViewItemLocaleTextProperty + { + public string LocaleSetName { get; set; } + public string LocalePropertyName { get; set; } + } + internal static class UIElementExtensions { /// @@ -30,6 +37,173 @@ internal static class UIElementExtensions [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "set_ProtectedCursor")] internal static extern void SetCursor(this UIElement element, InputCursor inputCursor); +#nullable enable + /// + /// Set the initial navigation view item's locale binding before getting set with + /// + /// The instance to set the initial text binding to. + /// The instance to set the initial text binding to. + /// The instance name of a members. + /// Name of the locale property + /// A reference of the + internal static ref T BindNavigationViewItemText(this T element, string localeSetName, string localePropertyName) + where T : NavigationViewItemBase + { + NavigationViewItemLocaleTextProperty property = new NavigationViewItemLocaleTextProperty + { + LocaleSetName = localeSetName, + LocalePropertyName = localePropertyName + }; + + if (element is NavigationViewItemHeader elementAsHeader) + { + elementAsHeader.Tag = property; + } + else + { + TextBlock textBlock = new TextBlock().WithTag(property); + element.Content = textBlock; + } + return ref Unsafe.AsRef(ref element); + } + + internal static void SetAllControlsCursorRecursive(this UIElement element, InputSystemCursor toCursor) + { + if (element == null) + { + return; + } + + if (element is Panel panelKind) + { + foreach (UIElement panelElements in panelKind.Children) + { + SetAllControlsCursorRecursive(panelElements, toCursor); + } + } + + if (element is RadioButtons radioButtonsKind) + { + foreach (UIElement radioButtonContent in radioButtonsKind.Items.OfType()) + { + radioButtonContent.SetCursor(toCursor); + } + } + + if (element is Border borderKind) + { + SetAllControlsCursorRecursive(borderKind.Child, toCursor); + } + + if (element is ComboBox comboBoxKind) + { + comboBoxKind.SetCursor(toCursor); + } + + if (element is UserControl userControlKind) + { + SetAllControlsCursorRecursive(userControlKind.Content, toCursor); + } + + if (element is ContentControl contentControlKind + && contentControlKind.Content is UIElement contentControlKindInner) + { + SetAllControlsCursorRecursive(contentControlKindInner, toCursor); + } + + if (element is NavigationView navigationViewKind) + { + foreach (UIElement navigationViewElements in navigationViewKind.FindDescendants()) + { + if (navigationViewElements is NavigationViewItem) + { + navigationViewElements.SetCursor(toCursor); + continue; + } + SetAllControlsCursorRecursive(navigationViewElements, toCursor); + } + } + + if (element is ButtonBase buttonBaseKind) + { + buttonBaseKind.SetCursor(toCursor); + if (buttonBaseKind is Button buttonKind && buttonKind.Flyout != null && buttonKind.Flyout is Flyout buttonKindFlyout) + { + SetAllControlsCursorRecursive(buttonKindFlyout.Content, toCursor); + } + } + + if (element is ToggleSwitch) + { + element.SetCursor(toCursor); + } + + if (element.ContextFlyout != null && element.ContextFlyout is Flyout elementFlyoutKind) + { + SetAllControlsCursorRecursive(elementFlyoutKind.Content, toCursor); + } + } + + internal static void ApplyNavigationViewItemLocaleTextBindings(this NavigationView navViewControl) + { + foreach (NavigationViewItemBase navItem in navViewControl + .FindDescendants() + .OfType()) + { + string? localeValue = null; + if (navItem.Content is TextBlock navItemTextBlock + && navItemTextBlock.Tag is NavigationViewItemLocaleTextProperty localeProperty) + { + navItemTextBlock.BindProperty( + TextBlock.TextProperty, + Locale.Lang, + $"{localeProperty.LocaleSetName}.{localeProperty.LocalePropertyName}"); + localeValue = navItemTextBlock.GetValue(TextBlock.TextProperty) as string; + } + else if (navItem is NavigationViewItemHeader navItemAsHeader + && navItemAsHeader.Tag is NavigationViewItemLocaleTextProperty localePropertyOnHeader) + { + navItemAsHeader.BindProperty( + ContentControl.ContentProperty, + Locale.Lang, + $"{localePropertyOnHeader.LocaleSetName}.{localePropertyOnHeader.LocalePropertyName}"); + localeValue = navItemAsHeader.GetValue(ContentControl.ContentProperty) as string; + } + + if (!string.IsNullOrEmpty(localeValue)) + { + ToolTipService.SetToolTip(navItem, localeValue); + } + } + + navViewControl.UpdateLayout(); + } + + internal static ref T BindProperty(this T element, DependencyProperty dependencyProperty, object objectToBind, string propertyName, IValueConverter? converter = null, BindingMode bindingMode = BindingMode.OneWay) + where T : FrameworkElement + { + // Create a new binding instance + Binding binding = new Binding + { + Source = objectToBind, + Mode = bindingMode, + Path = new PropertyPath(propertyName), + UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged + }; + + // If the converter is assigned, then add the converter + if (converter != null) + { + binding.Converter = converter; + } + + // Set binding to the element + element.SetBinding(dependencyProperty, binding); + + return ref Unsafe.AsRef(ref element); + } +#nullable restore + internal static TButtonBase CreateButtonWithIcon(string text = null, string iconGlyph = null, string iconFontFamily = "FontAwesome", string buttonStyle = "DefaultButtonStyle", double iconSize = 16d, double? textSize = null, CornerRadius? cornerRadius = null, FontWeight? textWeight = null) where TButtonBase : ButtonBase, new() @@ -704,6 +878,9 @@ void AssignShadowAttachment(FrameworkElement thisElement, bool innerMasked) case Image imageElement: AttachShadow(imageElement, true, offset); break; + case ImageEx.ImageEx imageExElement: + AttachShadow(imageExElement, true, offset); + break; default: AttachShadow(element, innerMasked, offset); break; diff --git a/CollapseLauncher/Classes/FileMigrationProcess/Statics.cs b/CollapseLauncher/Classes/FileMigrationProcess/Statics.cs index 717758e4d..707429ed5 100644 --- a/CollapseLauncher/Classes/FileMigrationProcess/Statics.cs +++ b/CollapseLauncher/Classes/FileMigrationProcess/Statics.cs @@ -21,6 +21,8 @@ internal static async Task CreateJob(UIElement parentUI, s // Check whether the input is a file or not. bool isFileTransfer = File.Exists(inputPath) && !inputPath.StartsWith('\\'); outputPath = await InitializeAndCheckOutputPath(parentUI, dialogTitle, inputPath, outputPath, isFileTransfer); + if (outputPath == null) return null; + if (Helper.FileUtility.IsRootPath(outputPath)) { await SimpleDialogs.SpawnDialog(Lang._HomePage.InstallFolderRootTitle, @@ -30,7 +32,6 @@ await SimpleDialogs.SpawnDialog(Lang._HomePage.InstallFolderRootTitle, null, null, ContentDialogButton.Close, ContentDialogTheme.Error); return null; } - if (outputPath == null) return null; if (showWarningMessage) if (await ShowNotCancellableProcedureMessage(parentUI) == ContentDialogResult.None) diff --git a/CollapseLauncher/Classes/GameManagement/GameSettings/BaseClass/ImportExportBase.cs b/CollapseLauncher/Classes/GameManagement/GameSettings/BaseClass/ImportExportBase.cs index 68cb4f2bd..f03ed7616 100644 --- a/CollapseLauncher/Classes/GameManagement/GameSettings/BaseClass/ImportExportBase.cs +++ b/CollapseLauncher/Classes/GameManagement/GameSettings/BaseClass/ImportExportBase.cs @@ -33,7 +33,7 @@ internal class ImportExportBase using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read)) { byte[] head = new byte[6]; - fs.Read(head, 0, head.Length); + _ = fs.Read(head, 0, head.Length); Logger.LogWriteLine($"Importing registry {RegistryPath}..."); @@ -425,7 +425,7 @@ private string ReadValueName(Stream stream) byte[] buffer = ArrayPool.Shared.Rent(length); try { - stream.Read(buffer, 0, length); + _ = stream.Read(buffer, 0, length); return Encoding.UTF8.GetString(buffer, 0, length); } finally @@ -439,21 +439,21 @@ private string ReadValueName(Stream stream) private long ReadInt64(Stream stream) { Span buffer = stackalloc byte[4]; - stream.Read(buffer); + _ = stream.Read(buffer); return MemoryMarshal.Read(buffer); } private int ReadInt32(Stream stream) { Span buffer = stackalloc byte[4]; - stream.Read(buffer); + _ = stream.Read(buffer); return MemoryMarshal.Read(buffer); } private short ReadInt16(Stream stream) { Span buffer = stackalloc byte[2]; - stream.Read(buffer); + _ = stream.Read(buffer); return MemoryMarshal.Read(buffer); } @@ -481,7 +481,7 @@ private void ReadBinary(EndianBinaryReader reader, string valueName) { int leng = reader.ReadInt32(); byte[] val = new byte[leng]; - reader.Read(val, 0, leng); + _ = reader.Read(val, 0, leng); RegistryRoot?.SetValue(valueName, val, RegistryValueKind.Binary); } } diff --git a/CollapseLauncher/Classes/GameManagement/GameSettings/Zenless/Sleepy.cs b/CollapseLauncher/Classes/GameManagement/GameSettings/Zenless/Sleepy.cs index a4d0ea4cd..417adbd84 100644 --- a/CollapseLauncher/Classes/GameManagement/GameSettings/Zenless/Sleepy.cs +++ b/CollapseLauncher/Classes/GameManagement/GameSettings/Zenless/Sleepy.cs @@ -166,6 +166,13 @@ private static unsafe int InternalDecode(ReadOnlySpan magic, bool* evil, B internal static void WriteString(string filePath, ReadOnlySpan content, ReadOnlySpan magic) { + // Ensure the folder always exist + string? fileDir = Path.GetDirectoryName(filePath); + if (!string.IsNullOrEmpty(fileDir) && !Directory.Exists(fileDir)) + { + Directory.CreateDirectory(fileDir); + } + // Get the FileInfo FileInfo fileInfo = new FileInfo(filePath); diff --git a/CollapseLauncher/Classes/Helper/Animation/AnimationHelper.cs b/CollapseLauncher/Classes/Helper/Animation/AnimationHelper.cs index ee5329d0f..4f33ad75b 100644 --- a/CollapseLauncher/Classes/Helper/Animation/AnimationHelper.cs +++ b/CollapseLauncher/Classes/Helper/Animation/AnimationHelper.cs @@ -1,5 +1,5 @@ -using CommunityToolkit.WinUI.Controls; -using Hi3Helper; +using Hi3Helper; +using Hi3Helper.CommunityToolkit.WinUI.Controls; using Microsoft.UI.Composition; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; @@ -8,6 +8,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; +// ReSharper disable CheckNamespace namespace CollapseLauncher.Helper.Animation { @@ -65,26 +66,26 @@ internal static async Task StartAnimation(this UIElement element, TimeSpan durat { foreach (KeyFrameAnimation anim in animBase!) { - if (element?.DispatcherQueue?.HasThreadAccess ?? false) + if (element.DispatcherQueue?.HasThreadAccess ?? false) { anim.Duration = duration; anim.StopBehavior = AnimationStopBehavior.LeaveCurrentValue; animGroup.Add(anim); } else - element?.DispatcherQueue?.TryEnqueue(() => + element.DispatcherQueue?.TryEnqueue(() => { anim.Duration = duration; anim.StopBehavior = AnimationStopBehavior.LeaveCurrentValue; animGroup.Add(anim); }); } - if (element?.DispatcherQueue?.HasThreadAccess ?? false) + if (element.DispatcherQueue?.HasThreadAccess ?? false) { element.StartAnimation(animGroup); } else - element?.DispatcherQueue?.TryEnqueue(() => + element.DispatcherQueue?.TryEnqueue(() => { element.StartAnimation(animGroup); }); @@ -123,69 +124,85 @@ internal static void EnableSingleImplicitAnimation(this UIElement element, rootFrameVisual.ImplicitAnimations = animationCollection; } - internal static void EnableImplicitAnimation(this UIElement element, - bool recursiveAssignment = false, - CompositionEasingFunction easingFunction = null) + internal static void EnableImplicitAnimation(this UIElement element, bool recursiveAssignment = false, CompositionEasingFunction easingFunction = null) { - try + while (true) { - Visual rootFrameVisual = ElementCompositionPreview.GetElementVisual(element); - Compositor compositor = CompositionTarget.GetCompositorForCurrentThread(); - - ImplicitAnimationCollection animationCollection = - rootFrameVisual.ImplicitAnimations != null ? - rootFrameVisual.ImplicitAnimations : compositor!.CreateImplicitAnimationCollection(); - - foreach (VisualPropertyType type in Enum.GetValues()) + try { - KeyFrameAnimation animation = CreateAnimationByType(compositor, type, 250, 0, easingFunction); + Visual rootFrameVisual = ElementCompositionPreview.GetElementVisual(element); + Compositor compositor = CompositionTarget.GetCompositorForCurrentThread(); - if (animation != null) - { - animationCollection[type.ToString()] = animation; - } - } + ImplicitAnimationCollection animationCollection = rootFrameVisual.ImplicitAnimations != null ? rootFrameVisual.ImplicitAnimations : compositor!.CreateImplicitAnimationCollection(); - rootFrameVisual.ImplicitAnimations = animationCollection; - element.EnableElementVisibilityAnimation(); - } - catch (Exception ex) - { - Logger.LogWriteLine($"[AnimationHelper::EnableImplicitAnimation()] Error has occurred while assigning Implicit Animation to the element!\r\n{ex}", LogType.Error, true); - } + foreach (VisualPropertyType type in Enum.GetValues()) + { + KeyFrameAnimation animation = CreateAnimationByType(compositor, type, 250, 0, easingFunction); - if (!recursiveAssignment) return; + if (animation != null) + { + animationCollection[type.ToString()] = animation; + } + } - if (element is Button button && button.Content is UIElement buttonContent) - buttonContent.EnableImplicitAnimation(recursiveAssignment, easingFunction); + rootFrameVisual.ImplicitAnimations = animationCollection; + element.EnableElementVisibilityAnimation(); + } + catch (Exception ex) + { + Logger.LogWriteLine($"[AnimationHelper::EnableImplicitAnimation()] Error has occurred while assigning Implicit Animation to the element!\r\n{ex}", LogType.Error, true); + } - if (element is Page page && page.Content is UIElement pageContent) - pageContent.EnableImplicitAnimation(recursiveAssignment, easingFunction); + if (!recursiveAssignment) return; - if (element is NavigationView navigationView && navigationView.Content is UIElement navigationViewContent) - navigationViewContent.EnableImplicitAnimation(recursiveAssignment, easingFunction); + switch (element) + { + case Button { Content: UIElement buttonContent }: + buttonContent.EnableImplicitAnimation(true, easingFunction); + break; + case Page { Content: not null } page: + page.Content.EnableImplicitAnimation(true, easingFunction); + break; + case NavigationView { Content: UIElement navigationViewContent }: + navigationViewContent.EnableImplicitAnimation(true, easingFunction); + break; + case Panel panel: + { + foreach (UIElement childrenElement in panel.Children!) childrenElement.EnableImplicitAnimation(true, easingFunction); + break; + } + case ScrollViewer { Content: UIElement elementInner }: + elementInner.EnableImplicitAnimation(true, easingFunction); + break; + } - if (element is Panel panel) - foreach (UIElement childrenElement in panel.Children!) - childrenElement.EnableImplicitAnimation(recursiveAssignment, easingFunction); + switch (element) + { + case ContentControl { Content: UIElement contentControlInner } contentControl and (SettingsCard or Expander): + { + contentControlInner.EnableImplicitAnimation(true, easingFunction); - if (element is ScrollViewer scrollViewer && scrollViewer.Content is UIElement elementInner) - elementInner.EnableImplicitAnimation(recursiveAssignment, easingFunction); + if (contentControl is Expander { Header: UIElement expanderHeader }) + { + element = expanderHeader; + recursiveAssignment = true; + continue; + } - if (element is ContentControl contentControl && (element is SettingsCard || element is Expander) && contentControl.Content is UIElement contentControlInner) - { - contentControlInner.EnableImplicitAnimation(true, easingFunction); + break; + } + case InfoBar { Content: UIElement infoBarInner }: + element = infoBarInner; + recursiveAssignment = true; + continue; + } - if (contentControl is Expander expander && expander.Header is UIElement expanderHeader) - expanderHeader.EnableImplicitAnimation(true, easingFunction); + break; } - - if (element is InfoBar infoBar && infoBar.Content is UIElement infoBarInner) - infoBarInner.EnableImplicitAnimation(true, easingFunction); } private static KeyFrameAnimation CreateAnimationByType(Compositor compositor, VisualPropertyType type, - double duration = 800, double delay = 0, CompositionEasingFunction easing = null) + double duration = 800, double delay = 0, CompositionEasingFunction easing = null) { KeyFrameAnimation animation; @@ -202,6 +219,8 @@ private static KeyFrameAnimation CreateAnimationByType(Compositor compositor, Vi case VisualPropertyType.RotationAngleInDegrees: animation = compositor.CreateScalarKeyFrameAnimation(); break; + case VisualPropertyType.None: + case VisualPropertyType.All: default: return null; } @@ -221,25 +240,25 @@ public static void EnableElementVisibilityAnimation(this UIElement element, Comp ElementCompositionPreview.SetIsTranslationEnabled(element, true); - ScalarKeyFrameAnimation HideOpacityAnimation = compositor.CreateScalarKeyFrameAnimation(); - ScalarKeyFrameAnimation ShowOpacityAnimation = compositor.CreateScalarKeyFrameAnimation(); + ScalarKeyFrameAnimation hideOpacityAnimation = compositor.CreateScalarKeyFrameAnimation(); + ScalarKeyFrameAnimation showOpacityAnimation = compositor.CreateScalarKeyFrameAnimation(); - HideOpacityAnimation.InsertKeyFrame(1.0f, 0.0f); - HideOpacityAnimation.Duration = animDur; - HideOpacityAnimation.Target = "Opacity"; + hideOpacityAnimation.InsertKeyFrame(1.0f, 0.0f); + hideOpacityAnimation.Duration = animDur; + hideOpacityAnimation.Target = "Opacity"; - ShowOpacityAnimation.InsertKeyFrame(1.0f, 1.0f); - ShowOpacityAnimation.Duration = animDur; - ShowOpacityAnimation.Target = "Opacity"; + showOpacityAnimation.InsertKeyFrame(1.0f, 1.0f); + showOpacityAnimation.Duration = animDur; + showOpacityAnimation.Target = "Opacity"; - CompositionAnimationGroup HideAnimationGroup = compositor.CreateAnimationGroup(); - CompositionAnimationGroup ShowAnimationGroup = compositor.CreateAnimationGroup(); + CompositionAnimationGroup hideAnimationGroup = compositor.CreateAnimationGroup(); + CompositionAnimationGroup showAnimationGroup = compositor.CreateAnimationGroup(); - HideAnimationGroup.Add(HideOpacityAnimation); - ShowAnimationGroup.Add(ShowOpacityAnimation); + hideAnimationGroup.Add(hideOpacityAnimation); + showAnimationGroup.Add(showOpacityAnimation); - ElementCompositionPreview.SetImplicitHideAnimation(element, HideAnimationGroup); - ElementCompositionPreview.SetImplicitShowAnimation(element, ShowAnimationGroup); + ElementCompositionPreview.SetImplicitHideAnimation(element, hideAnimationGroup); + ElementCompositionPreview.SetImplicitShowAnimation(element, showAnimationGroup); } internal static Compositor GetElementCompositor(this UIElement element) diff --git a/CollapseLauncher/Classes/Helper/HttpClientBuilder.cs b/CollapseLauncher/Classes/Helper/HttpClientBuilder.cs index 14dfcf285..d48104dc3 100644 --- a/CollapseLauncher/Classes/Helper/HttpClientBuilder.cs +++ b/CollapseLauncher/Classes/Helper/HttpClientBuilder.cs @@ -230,6 +230,7 @@ public HttpClient Create() socketsHttpHandler.UseCookies = IsAllowHttpCookies; socketsHttpHandler.AutomaticDecompression = DecompressionMethod; socketsHttpHandler.EnableMultipleHttp2Connections = true; + socketsHttpHandler.EnableMultipleHttp3Connections = true; // Toggle for allowing untrusted cert if (IsAllowUntrustedCert) diff --git a/CollapseLauncher/Classes/Helper/ILoggerHelper.cs b/CollapseLauncher/Classes/Helper/ILoggerHelper.cs new file mode 100644 index 000000000..67b113c53 --- /dev/null +++ b/CollapseLauncher/Classes/Helper/ILoggerHelper.cs @@ -0,0 +1,49 @@ +#if USEVELOPACK +using Hi3Helper; +using Microsoft.Extensions.Logging; +using System; + +namespace CollapseLauncher.Helper +{ + internal static class ILoggerHelper + { + internal static ILogger CreateCollapseILogger() => new CollapseILoggerWrapper(); + } + + public class CollapseILoggerWrapper : ILogger + { + internal CollapseILoggerWrapper() { } + + public IDisposable BeginScope(TState state) + where TState : notnull => default; + + public bool IsEnabled(LogLevel logLevel) => true; + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + LogType logType = logLevel switch + { + LogLevel.Trace => LogType.Debug, + LogLevel.Debug => LogType.Debug, + LogLevel.Information => LogType.Default, + LogLevel.Warning => LogType.Warning, + LogLevel.Error => LogType.Error, + LogLevel.Critical => LogType.Error, + LogLevel.None => LogType.NoTag, + _ => LogType.Default + }; + + bool isWriteToLog = logType switch + { + LogType.Error => true, + LogType.Warning => true, + LogType.Debug => true, + _ => false + }; + + string message = formatter(state, exception); + Logger.LogWriteLine(message, logType, isWriteToLog); + } + } +} +#endif \ No newline at end of file diff --git a/CollapseLauncher/Classes/Helper/Image/ImageLoaderHelper.cs b/CollapseLauncher/Classes/Helper/Image/ImageLoaderHelper.cs index 1f6e871c8..fdeae5577 100644 --- a/CollapseLauncher/Classes/Helper/Image/ImageLoaderHelper.cs +++ b/CollapseLauncher/Classes/Helper/Image/ImageLoaderHelper.cs @@ -3,7 +3,6 @@ using CollapseLauncher.Extension; using CollapseLauncher.Helper.Background; using CommunityToolkit.WinUI.Animations; -using CommunityToolkit.WinUI.Controls; using CommunityToolkit.WinUI.Media; using Hi3Helper; using Hi3Helper.Data; @@ -28,6 +27,12 @@ using static Hi3Helper.Shared.Region.LauncherConfig; using Orientation = Microsoft.UI.Xaml.Controls.Orientation; +using ImageBlendBrush = Hi3Helper.CommunityToolkit.WinUI.Media.ImageBlendBrush; +using ImageCropper = Hi3Helper.CommunityToolkit.WinUI.Controls.ImageCropper; +using CropShape = Hi3Helper.CommunityToolkit.WinUI.Controls.CropShape; +using ThumbPlacement = Hi3Helper.CommunityToolkit.WinUI.Controls.ThumbPlacement; +using BitmapFileFormat = Hi3Helper.CommunityToolkit.WinUI.Controls.BitmapFileFormat; + namespace CollapseLauncher.Helper.Image { internal static class ImageLoaderHelper @@ -152,7 +157,7 @@ internal static async Task LoadImage(string path, bool isUseImageCro } private static async Task SpawnImageCropperDialog(string filePath, string cachedFilePath, - uint ToWidth, uint ToHeight) + uint toWidth, uint toHeight) { Grid parentGrid = new() { @@ -169,15 +174,19 @@ private static async Task SpawnImageCropperDialog(string filePath, s imageCropper.HorizontalAlignment = HorizontalAlignment.Stretch; imageCropper.VerticalAlignment = VerticalAlignment.Stretch; imageCropper.Opacity = 0; + + // Path of image + Uri overlayImageUri = new Uri(Path.Combine(AppFolder!, @"Assets\Images\ImageCropperOverlay", + GetAppConfigValue("WindowSizeProfile").ToString() == "Small" ? "small.png" : "normal.png")); + // Why not use ImageBrush? // https://github.com/microsoft/microsoft-ui-xaml/issues/7809 imageCropper.Overlay = new ImageBlendBrush() { - Source = new BitmapImage(new Uri(Path.Combine(AppFolder!, @"Assets\Images\ImageCropperOverlay", - GetAppConfigValue("WindowSizeProfile").ToString() == "Small" ? "small.png" : "normal.png"))), Opacity = 0.5, Stretch = Stretch.Fill, Mode = ImageBlendMode.Multiply, + SourceUri = overlayImageUri }; ContentDialogOverlay dialogOverlay = new ContentDialogOverlay(ContentDialogTheme.Informational) @@ -215,7 +224,7 @@ private static async Task SpawnImageCropperDialog(string filePath, s } FileInfo cachedFileInfo = new FileInfo(cachedFilePath); - return await GenerateCachedStream(cachedFileInfo, ToWidth, ToHeight, true); + return await GenerateCachedStream(cachedFileInfo, toWidth, toHeight, true); } private static async void LoadImageCropperDetached(string filePath, ImageCropper imageCropper, @@ -392,21 +401,25 @@ public static bool IsFileCompletelyDownloaded(FileInfo fileInfo) // Try to get the prop file which includes the filename + the suggested size provided // by the network stream if it has been downloaded before - string propFilePath = Directory.EnumerateFiles(outputParentPath, $"{outputFileName}#*", SearchOption.TopDirectoryOnly).FirstOrDefault(); - // Check if the file is found (not null), then try parse the information - if (string.IsNullOrEmpty(propFilePath)) + if (outputParentPath != null) { - return false; - } + string propFilePath = Directory.EnumerateFiles(outputParentPath, $"{outputFileName}#*", SearchOption.TopDirectoryOnly).FirstOrDefault(); + // Check if the file is found (not null), then try parse the information + if (string.IsNullOrEmpty(propFilePath)) + { + return false; + } - // Try split the filename into a segment by # char - string[] propSegment = Path.GetFileName(propFilePath).Split('#'); - // Assign the check if the condition met and set the file existence status - return propSegment.Length >= 2 - && long.TryParse(propSegment[1], null, out long suggestedSize) - && fileInfo.Exists && fileInfo.Length == suggestedSize; + // Try split the filename into a segment by # char + string[] propSegment = Path.GetFileName(propFilePath).Split('#'); + // Assign the check if the condition met and set the file existence status + return propSegment.Length >= 2 + && long.TryParse(propSegment[1], null, out long suggestedSize) + && fileInfo.Exists && fileInfo.Length == suggestedSize; + } // If the prop doesn't exist, then return false to assume that the file doesn't exist + return false; } public static async void TryDownloadToCompletenessAsync(string url, FileInfo fileInfo, CancellationToken token) @@ -435,8 +448,11 @@ public static async ValueTask TryDownloadToCompleteness(string url, FileInfo fil // Create the prop file for download completeness checking string outputParentPath = Path.GetDirectoryName(fileInfo.FullName); string outputFilename = Path.GetFileName(fileInfo.FullName); - string propFilePath = Path.Combine(outputParentPath, $"{outputFilename}#{netStream.Length}"); - await File.Create(propFilePath).DisposeAsync(); + if (outputParentPath != null) + { + string propFilePath = Path.Combine(outputParentPath, $"{outputFilename}#{netStream.Length}"); + await File.Create(propFilePath).DisposeAsync(); + } // Copy (and download) the remote streams to local int read; @@ -446,6 +462,9 @@ public static async ValueTask TryDownloadToCompleteness(string url, FileInfo fil Logger.LogWriteLine($"Resource download from: {url} has been completed and stored locally into:" + $"\"{fileInfo.FullName}\" with size: {ConverterTool.SummarizeSizeSimple(fileLength)} ({fileLength} bytes)", LogType.Default, true); } + // Ignore cancellation exceptions + catch (TaskCanceledException) { } + catch (OperationCanceledException) { } #if !DEBUG catch (Exception ex) { diff --git a/CollapseLauncher/Classes/Helper/LauncherApiLoader/HoYoPlay/HoYoPlayLauncherApiLoader.cs b/CollapseLauncher/Classes/Helper/LauncherApiLoader/HoYoPlay/HoYoPlayLauncherApiLoader.cs index ae917ff1e..945b62832 100644 --- a/CollapseLauncher/Classes/Helper/LauncherApiLoader/HoYoPlay/HoYoPlayLauncherApiLoader.cs +++ b/CollapseLauncher/Classes/Helper/LauncherApiLoader/HoYoPlay/HoYoPlayLauncherApiLoader.cs @@ -28,7 +28,7 @@ protected override async Task LoadLauncherGameResource(ActionOnTimeOutRetry? onT ActionTimeoutValueTaskCallback hypResourceResponseCallback = async innerToken => - await FallbackCDNUtil.DownloadAsJSONType(PresetConfig?.LauncherResourceURL, InternalAppJSONContext.Default, innerToken); + await FallbackCDNUtil.DownloadAsJSONType(PresetConfig?.LauncherResourceURL, InternalAppJSONContext.Default.HoYoPlayLauncherResources, innerToken); HoYoPlayLauncherResources? hypResourceResponse = await hypResourceResponseCallback.WaitForRetryAsync(ExecutionTimeout, ExecutionTimeoutStep, ExecutionTimeoutAttempt, onTimeoutRoutine, token); @@ -39,7 +39,7 @@ protected override async Task LoadLauncherGameResource(ActionOnTimeOutRetry? onT { ActionTimeoutValueTaskCallback hypPluginResourceCallback = async innerToken => - await FallbackCDNUtil.DownloadAsJSONType(PresetConfig?.LauncherPluginURL, InternalAppJSONContext.Default, innerToken); + await FallbackCDNUtil.DownloadAsJSONType(PresetConfig?.LauncherPluginURL, InternalAppJSONContext.Default.HoYoPlayLauncherResources, innerToken); hypPluginResource = await hypPluginResourceCallback.WaitForRetryAsync(ExecutionTimeout, ExecutionTimeoutStep, ExecutionTimeoutAttempt, onTimeoutRoutine, token); @@ -49,7 +49,7 @@ protected override async Task LoadLauncherGameResource(ActionOnTimeOutRetry? onT { ActionTimeoutValueTaskCallback hypSdkResourceCallback = async innerToken => - await FallbackCDNUtil.DownloadAsJSONType(PresetConfig?.LauncherGameChannelSDKURL, InternalAppJSONContext.Default, innerToken); + await FallbackCDNUtil.DownloadAsJSONType(PresetConfig?.LauncherGameChannelSDKURL, InternalAppJSONContext.Default.HoYoPlayLauncherResources, innerToken); hypSdkResource = await hypSdkResourceCallback.WaitForRetryAsync(ExecutionTimeout, ExecutionTimeoutStep, ExecutionTimeoutAttempt, onTimeoutRoutine, token); @@ -319,11 +319,11 @@ protected override async ValueTask LoadLauncherNews(ActionOnTimeOutRetry? onTime ActionTimeoutValueTaskCallback hypLauncherBackgroundCallback = async innerToken => - await FallbackCDNUtil.DownloadAsJSONType(launcherSpriteUrl, InternalAppJSONContext.Default, innerToken); + await FallbackCDNUtil.DownloadAsJSONType(launcherSpriteUrl, InternalAppJSONContext.Default.HoYoPlayLauncherNews, innerToken); ActionTimeoutValueTaskCallback hypLauncherNewsCallback = async innerToken => - await FallbackCDNUtil.DownloadAsJSONType(launcherNewsUrl, InternalAppJSONContext.Default, innerToken); + await FallbackCDNUtil.DownloadAsJSONType(launcherNewsUrl, InternalAppJSONContext.Default.HoYoPlayLauncherNews, innerToken); HoYoPlayLauncherNews? hypLauncherBackground = await hypLauncherBackgroundCallback.WaitForRetryAsync(ExecutionTimeout, ExecutionTimeoutStep, ExecutionTimeoutAttempt, onTimeoutRoutine, token); @@ -462,7 +462,7 @@ protected override async ValueTask LoadLauncherGameInfo(ActionOnTimeOutRetry? on ActionTimeoutValueTaskCallback hypLauncherGameInfoCallback = async innerToken => - await FallbackCDNUtil.DownloadAsJSONType(launcherGameInfoUrl, InternalAppJSONContext.Default, innerToken); + await FallbackCDNUtil.DownloadAsJSONType(launcherGameInfoUrl, InternalAppJSONContext.Default.HoYoPlayLauncherGameInfo, innerToken); HoYoPlayLauncherGameInfo? hypLauncherGameInfo = await hypLauncherGameInfoCallback.WaitForRetryAsync(ExecutionTimeout, ExecutionTimeoutStep, ExecutionTimeoutAttempt, onTimeoutRoutine, token); diff --git a/CollapseLauncher/Classes/Helper/LauncherApiLoader/LauncherApiBase.cs b/CollapseLauncher/Classes/Helper/LauncherApiLoader/LauncherApiBase.cs index 8ef1d36f5..daea33f48 100644 --- a/CollapseLauncher/Classes/Helper/LauncherApiLoader/LauncherApiBase.cs +++ b/CollapseLauncher/Classes/Helper/LauncherApiLoader/LauncherApiBase.cs @@ -89,7 +89,7 @@ protected virtual async Task LoadLauncherGameResource(ActionOnTimeOutRetry? onTi ActionTimeoutValueTaskCallback launcherGameResourceCallback = async innerToken => - await FallbackCDNUtil.DownloadAsJSONType(PresetConfig?.LauncherResourceURL, InternalAppJSONContext.Default, innerToken); + await FallbackCDNUtil.DownloadAsJSONType(PresetConfig?.LauncherResourceURL, InternalAppJSONContext.Default.RegionResourceProp, innerToken); LauncherGameResource = await launcherGameResourceCallback.WaitForRetryAsync(ExecutionTimeout, ExecutionTimeoutStep, ExecutionTimeoutAttempt, onTimeoutRoutine, token); @@ -103,7 +103,7 @@ protected virtual async Task LoadLauncherGameResource(ActionOnTimeOutRetry? onTi { ActionTimeoutValueTaskCallback launcherPluginPropCallback = async innerToken => - await FallbackCDNUtil.DownloadAsJSONType(string.Format(PresetConfig?.LauncherPluginURL!, GetDeviceId(PresetConfig!)), InternalAppJSONContext.Default, innerToken); + await FallbackCDNUtil.DownloadAsJSONType(string.Format(PresetConfig?.LauncherPluginURL!, GetDeviceId(PresetConfig!)), InternalAppJSONContext.Default.RegionResourceProp, innerToken); RegionResourceProp? pluginProp = await launcherPluginPropCallback.WaitForRetryAsync(ExecutionTimeout, ExecutionTimeoutStep, ExecutionTimeoutAttempt, onTimeoutRoutine, token); @@ -282,8 +282,7 @@ protected virtual async ValueTask LoadLauncherGameInfo(ActionOnTimeOutRetry? onT private static async Task LoadSingleLangLauncherNews( string launcherSpriteUrl, CancellationToken token) { - return await FallbackCDNUtil.DownloadAsJSONType(launcherSpriteUrl, - InternalAppJSONContext.Default, token); + return await FallbackCDNUtil.DownloadAsJSONType(launcherSpriteUrl, InternalAppJSONContext.Default.LauncherGameNews, token); } private static async Task LoadMultiLangLauncherNews(string launcherSpriteUrl, string lang, @@ -291,8 +290,7 @@ protected virtual async ValueTask LoadLauncherGameInfo(ActionOnTimeOutRetry? onT { return await FallbackCDNUtil - .DownloadAsJSONType(string.Format(launcherSpriteUrl, lang), - InternalAppJSONContext.Default, token); + .DownloadAsJSONType(string.Format(launcherSpriteUrl, lang), InternalAppJSONContext.Default.LauncherGameNews, token); } protected virtual string GetDeviceId(PresetConfig preset) diff --git a/CollapseLauncher/Classes/Helper/LauncherApiLoader/Sophon/LauncherGameNews.cs b/CollapseLauncher/Classes/Helper/LauncherApiLoader/Sophon/LauncherGameNews.cs index 00496cc1c..59ac9600b 100644 --- a/CollapseLauncher/Classes/Helper/LauncherApiLoader/Sophon/LauncherGameNews.cs +++ b/CollapseLauncher/Classes/Helper/LauncherApiLoader/Sophon/LauncherGameNews.cs @@ -6,6 +6,8 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; +using WinRT; +// ReSharper disable PartialTypeWithSinglePart namespace CollapseLauncher.Helper.LauncherApiLoader.Sophon { @@ -83,7 +85,7 @@ public List? NewsPostTypeInfo return _newsPostTypeInfo; } - _newsPostTypeInfo = NewsPost.Where(x => x.PostType == LauncherGameNewsPostType.POST_TYPE_INFO)? + _newsPostTypeInfo = NewsPost.Where(x => x.PostType == LauncherGameNewsPostType.POST_TYPE_INFO) .OrderBy(x => x.PostOrder) .ToList(); return _newsPostTypeInfo; @@ -105,7 +107,7 @@ public List? NewsPostTypeActivity return _newsPostTypeActivity; } - _newsPostTypeActivity = NewsPost.Where(x => x.PostType == LauncherGameNewsPostType.POST_TYPE_ACTIVITY)? + _newsPostTypeActivity = NewsPost.Where(x => x.PostType == LauncherGameNewsPostType.POST_TYPE_ACTIVITY) .OrderBy(x => x.PostOrder) .ToList(); return _newsPostTypeActivity; @@ -128,7 +130,7 @@ public List? NewsPostTypeAnnouncement } _newsPostTypeAnnouncement = NewsPost - .Where(x => x.PostType == LauncherGameNewsPostType.POST_TYPE_ANNOUNCE)? + .Where(x => x.PostType == LauncherGameNewsPostType.POST_TYPE_ANNOUNCE) .OrderBy(x => x.PostOrder) .ToList(); return _newsPostTypeAnnouncement; @@ -220,7 +222,8 @@ public string? CarouselImg public int? CarouselOrder { get; init; } } - public class LauncherGameNewsSocialMedia : ILauncherGameNewsDataTokenized + [GeneratedBindableCustomProperty] + public partial class LauncherGameNewsSocialMedia : ILauncherGameNewsDataTokenized { private readonly string? _qrImg; private readonly List? _qrLinks; @@ -303,7 +306,8 @@ public List? QrLinks [JsonIgnore] public bool IsHasQrDescription => !string.IsNullOrEmpty(QrTitle); } - public class LauncherGameNewsSocialMediaQrLinks + [GeneratedBindableCustomProperty] + public partial class LauncherGameNewsSocialMediaQrLinks { [JsonPropertyName("title")] [JsonConverter(typeof(EmptyStringAsNullConverter))] diff --git a/CollapseLauncher/Classes/Helper/Metadata/PresetConfig.cs b/CollapseLauncher/Classes/Helper/Metadata/PresetConfig.cs index b5def426f..e303d79a7 100644 --- a/CollapseLauncher/Classes/Helper/Metadata/PresetConfig.cs +++ b/CollapseLauncher/Classes/Helper/Metadata/PresetConfig.cs @@ -94,7 +94,7 @@ public async ValueTask EnsureReassociated(HttpClient client, string? branchUrl, // Fetch branch info ActionTimeoutValueTaskCallback hypLauncherBranchCallback = async innerToken => - await FallbackCDNUtil.DownloadAsJSONType(branchUrl, InternalAppJSONContext.Default, innerToken); + await FallbackCDNUtil.DownloadAsJSONType(branchUrl, InternalAppJSONContext.Default.HoYoPlayLauncherGameInfo, innerToken); HoYoPlayLauncherGameInfo? hypLauncherBranchInfo = await hypLauncherBranchCallback.WaitForRetryAsync( LauncherApiBase.ExecutionTimeout, diff --git a/CollapseLauncher/Classes/Helper/TaskSchedulerHelper.cs b/CollapseLauncher/Classes/Helper/TaskSchedulerHelper.cs new file mode 100644 index 000000000..ec4e1b2d4 --- /dev/null +++ b/CollapseLauncher/Classes/Helper/TaskSchedulerHelper.cs @@ -0,0 +1,224 @@ +using Hi3Helper; +using Hi3Helper.Shared.Region; +using System; +using System.Diagnostics; +using System.IO; +using System.Text; + +namespace CollapseLauncher.Helper +{ + internal static class TaskSchedulerHelper + { + private const string CollapseStartupTaskName = "CollapseLauncherStartupTask"; + + internal static bool IsInitialized; + internal static bool CachedIsOnTrayEnabled; + internal static bool CachedIsEnabled; + + internal static bool IsOnTrayEnabled() + { + if (!IsInitialized) + InvokeGetStatusCommand(); + + return CachedIsOnTrayEnabled; + } + + internal static bool IsEnabled() + { + if (!IsInitialized) + InvokeGetStatusCommand(); + + return CachedIsEnabled; + } + +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + internal static async void InitializeDetached() => InvokeGetStatusCommand(); +#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + + internal static void InvokeGetStatusCommand() + { + // Build the argument and mode to set + StringBuilder argumentBuilder = new StringBuilder(); + argumentBuilder.Append("IsEnabled"); + + // Append task name and stub path + AppendTaskNameAndPathArgument(argumentBuilder); + + // Store argument builder as string + string argumentString = argumentBuilder.ToString(); + + // Invoke command and get return code + int returnCode = GetInvokeCommandReturnCode(argumentString); + + (CachedIsEnabled, CachedIsOnTrayEnabled) = returnCode switch + { + // -1 means task is disabled with tray enabled + -1 => (false, true), + // 0 means task is disabled with tray disabled + 0 => (false, false), + // 1 means task is enabled with tray disabled + 1 => (true, false), + // 2 means task is enabled with tray enabled + 2 => (true, true), + // Otherwise, return both disabled (due to failure) + _ => (false, false) + }; + + // Print init determination + CheckInitDetermination(returnCode); + } + + private static void CheckInitDetermination(int returnCode) + { + // If the return code is within range, then set as initialized + if (returnCode is > -2 and < 3) + { + // Set as initialized + IsInitialized = true; + } + // Otherwise, log the return code + else + { + string reason = returnCode switch + { + int.MaxValue => "ARGUMENT_INVALID", + int.MinValue => "UNHANDLED_ERROR", + short.MaxValue => "INTERNALINVOKE_ERROR", + short.MinValue => "APPLET_NOTFOUND", + _ => $"UNKNOWN_{returnCode}" + }; + Logger.LogWriteLine($"Error while getting task status from applet with reason: {reason}", LogType.Error, true); + } + } + + internal static void ToggleTrayEnabled(bool isEnabled) + { + CachedIsOnTrayEnabled = isEnabled; + InvokeToggleCommand(); + } + + internal static void ToggleEnabled(bool isEnabled) + { + CachedIsEnabled = isEnabled; + InvokeToggleCommand(); + } + + private static void InvokeToggleCommand() + { + // Build the argument and mode to set + StringBuilder argumentBuilder = new StringBuilder(); + argumentBuilder.Append(CachedIsEnabled ? "Enable" : "Disable"); + + // Append argument whether to toggle the tray or not + if (CachedIsOnTrayEnabled) + argumentBuilder.Append("ToTray"); + + // Append task name and stub path + AppendTaskNameAndPathArgument(argumentBuilder); + + // Store argument builder as string + string argumentString = argumentBuilder.ToString(); + + // Invoke applet + int returnCode = GetInvokeCommandReturnCode(argumentString); + + // Print init determination + CheckInitDetermination(returnCode); + } + + private static void AppendTaskNameAndPathArgument(StringBuilder argumentBuilder) + { + // Get current stub or main executable path + string currentExecPath = MainEntryPoint.FindCollapseStubPath(); + + // Build argument to the task name + argumentBuilder.Append(" \""); + argumentBuilder.Append(CollapseStartupTaskName); + argumentBuilder.Append('"'); + + // Build argument to the executable path + argumentBuilder.Append(" \""); + argumentBuilder.Append(currentExecPath); + argumentBuilder.Append('"'); + } + + internal static void RecreateIconShortcuts() + { + // Build the argument and get the current executable path + StringBuilder argumentBuilder = new StringBuilder(); + argumentBuilder.Append("RecreateIcons"); + string currentExecPath = LauncherConfig.AppExecutablePath; + + // Build argument to the executable path + argumentBuilder.Append(" \""); + argumentBuilder.Append(currentExecPath); + argumentBuilder.Append('"'); + + // Store argument builder as string + string argumentString = argumentBuilder.ToString(); + + // Invoke applet + int returnCode = GetInvokeCommandReturnCode(argumentString); + + // Print init determination + CheckInitDetermination(returnCode); + } + + private static int GetInvokeCommandReturnCode(string argument) + { + const string returnvalmark = "RETURNVAL_"; + + // Get the applet path and check if the file exist + string appletPath = Path.Combine(LauncherConfig.AppFolder, "Lib", "win-x64", "Hi3Helper.TaskScheduler.exe"); + if (!File.Exists(appletPath)) + { + Logger.LogWriteLine($"Task Scheduler Applet does not exist in this path: {appletPath}", LogType.Error, true); + return short.MinValue; + } + + // Try to make process instance for the applet + using Process process = new Process(); + process.StartInfo = new ProcessStartInfo + { + FileName = appletPath, + Arguments = argument, + UseShellExecute = false, + RedirectStandardOutput = true, + CreateNoWindow = true + }; + int lastErrCode = short.MaxValue; + try + { + // Start the applet and wait until it exit. + process.Start(); + while (!process.StandardOutput.EndOfStream) + { + string consoleStdOut = process.StandardOutput.ReadLine(); + Logger.LogWriteLine("[TaskScheduler] " + consoleStdOut, LogType.Debug, true); + + // Parse if it has RETURNVAL_ + if (consoleStdOut == null || !consoleStdOut.StartsWith(returnvalmark)) + { + continue; + } + + ReadOnlySpan span = consoleStdOut.AsSpan(returnvalmark.Length); + if (int.TryParse(span, null, out int resultReturnCode)) + { + lastErrCode = resultReturnCode; + } + } + process.WaitForExit(); + } + catch (Exception ex) + { + // If error happened, then return. + Logger.LogWriteLine($"An error has occurred while invoking Task Scheduler applet!\r\n{ex}", LogType.Error, true); + return short.MaxValue; + } + + // Get return code + return lastErrCode; + } + } +} diff --git a/CollapseLauncher/Classes/Helper/Update/LauncherUpdateHelper.cs b/CollapseLauncher/Classes/Helper/Update/LauncherUpdateHelper.cs index 44ab9b118..08c33ffb8 100644 --- a/CollapseLauncher/Classes/Helper/Update/LauncherUpdateHelper.cs +++ b/CollapseLauncher/Classes/Helper/Update/LauncherUpdateHelper.cs @@ -1,14 +1,21 @@ #nullable enable - using Hi3Helper; - using Hi3Helper.Data; - using Hi3Helper.Shared.Region; - using Squirrel; - using Squirrel.Sources; - using System; - using System.Threading.Tasks; +using Hi3Helper; +using Hi3Helper.Data; +using Hi3Helper.Shared.Region; +using System; +using System.Threading.Tasks; +#if !USEVELOPACK +using Squirrel; +using Squirrel.Sources; +#else +using Microsoft.Extensions.Logging; +using Velopack; +using Velopack.Locators; +using Velopack.Sources; +#endif // ReSharper disable CheckNamespace - namespace CollapseLauncher.Helper.Update +namespace CollapseLauncher.Helper.Update { internal static class LauncherUpdateHelper { @@ -42,7 +49,7 @@ internal static async Task RunUpdateCheckDetached() } catch (Exception ex) { - Logger.LogWriteLine($"The squirrel check throws an error, Skipping update check!\r\n{ex}", LogType.Warning, true); + Logger.LogWriteLine($"The update manager check throws an error, Skipping update check!\r\n{ex}", LogType.Warning, true); } } @@ -51,12 +58,65 @@ internal static async Task IsUpdateAvailable(bool isForceCheckUpdate = fal string updateChannel = LauncherConfig.IsPreview ? "preview" : "stable"; CDNURLProperty launcherUpdatePreferredCdn = FallbackCDNUtil.GetPreferredCDN(); - string? launcherUpdateSquirrelBaseUrl = ConverterTool.CombineURLFromString(launcherUpdatePreferredCdn.URLPrefix, "squirrel", updateChannel); + string? launcherUpdateManagerBaseUrl = ConverterTool.CombineURLFromString(launcherUpdatePreferredCdn.URLPrefix, +#if USEVELOPACK + "velopack", + updateChannel +#else + "squirrel", + updateChannel +#endif + ); - IFileDownloader squirrelUpdateManagerHttpAdapter = new UpdateManagerHttpAdapter(); - using (UpdateManager squirrelUpdateManager = new UpdateManager(launcherUpdateSquirrelBaseUrl, null, null, squirrelUpdateManagerHttpAdapter)) + // Register the update manager adapter + IFileDownloader updateManagerHttpAdapter = new UpdateManagerHttpAdapter(); +#if USEVELOPACK + // Initialize update manager logger, locator and options + ILogger velopackLogger = ILoggerHelper.CreateCollapseILogger(); + VelopackLocator updateManagerLocator = VelopackLocator.GetDefault(velopackLogger); + UpdateOptions updateManagerOptions = new UpdateOptions { - UpdateInfo info = await squirrelUpdateManager.CheckForUpdate(); + AllowVersionDowngrade = true, + ExplicitChannel = updateChannel + }; + + // Initialize update manager source + IUpdateSource updateSource = new SimpleWebSource(launcherUpdateManagerBaseUrl, updateManagerHttpAdapter); + + // Initialize the update manager + UpdateManager updateManager = new UpdateManager( + updateSource, + updateManagerOptions, + velopackLogger, + updateManagerLocator); + + // Get the update info. If it's null, then return false (no update) + UpdateInfo? updateInfo = await updateManager.CheckForUpdatesAsync(); + if (updateInfo == null) + { + return false; + } + + // If there's an update, then get the update metadata + GameVersion updateVersion = new GameVersion(updateInfo.TargetFullRelease.Version.ToString()); + AppUpdateVersionProp = await GetUpdateMetadata(updateChannel); + if (AppUpdateVersionProp == null) + { + return false; + } + + // Compare the version + IsLauncherUpdateAvailable = LauncherCurrentVersion.Compare(updateVersion); + + // Get the status if the update is ignorable or forced update. + bool isUserIgnoreUpdate = (LauncherConfig.GetAppConfigValue("DontAskUpdate").ToBoolNullable() ?? false) && !isForceCheckUpdate; + bool isUpdateRoutineSkipped = isUserIgnoreUpdate && !AppUpdateVersionProp.IsForceUpdate; + + return IsLauncherUpdateAvailable && !isUpdateRoutineSkipped; +#else + using (UpdateManager updateManager = new UpdateManager(launcherUpdateManagerBaseUrl, null, null, updateManagerHttpAdapter)) + { + UpdateInfo info = await updateManager.CheckForUpdate(); if (info == null) return false; GameVersion remoteVersion = new GameVersion(info.FutureReleaseEntry.Version.Version); @@ -71,6 +131,7 @@ internal static async Task IsUpdateAvailable(bool isForceCheckUpdate = fal return IsLauncherUpdateAvailable && !isUpdateRoutineSkipped; } +#endif } private static async ValueTask GetUpdateMetadata(string updateChannel) diff --git a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs index 5b2941b2e..5af34c8e7 100644 --- a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs +++ b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs @@ -949,11 +949,7 @@ await _gameVersionManager.GamePreset async ValueTask DelegateAssetDownload(SophonAsset asset, CancellationToken _) { // ReSharper disable once AccessToDisposedClosure - if (httpClient != null) - { - // ReSharper disable once AccessToDisposedClosure - await RunSophonAssetDownloadThread(httpClient, asset, parallelChunksOptions); - } + await RunSophonAssetDownloadThread(httpClient, asset, parallelChunksOptions); } // Declare the rename temp file delegate @@ -1517,9 +1513,13 @@ protected virtual async Task StartPackageInstallationInner(List entriesIndex, List entries, CancellationToken cancellationToken) { - // 16 MB of buffer (hope it's not too big) - byte[] buffer = new byte[16 << 20]; + // 4 MB of buffer + byte[] buffer = GC.AllocateUninitializedArray(4 << 20); foreach (int entryIndex in entriesIndex) { @@ -2309,10 +2309,6 @@ public virtual async ValueTask ApplyHdiffListPatch() { throw ex.Flatten().InnerExceptions.First(); } - catch (Exception) - { - throw; - } finally { EventListener.LoggerEvent -= EventListener_PatchLogEvent; @@ -2591,12 +2587,7 @@ protected virtual bool TryGetVoiceOverResourceByLocaleCode(List localeCode) { - // If it's empty or null, return false - if (localeCode == null) - { - return false; - } - + // If it's empty, return false if (localeCode.IsEmpty) { return false; @@ -3153,9 +3144,9 @@ private async Task GetLatestPackageList(List packageList, Ga if (gameState != GameInstallStateEnum.InstalledHavePlugin) { // Iterate the package resource version and add it into packageList - foreach (RegionResourceVersion asset in usePreload + foreach (RegionResourceVersion asset in (usePreload ? _gameVersionManager.GetGamePreloadZip() - : _gameVersionManager.GetGameLatestZip(gameState)) + : _gameVersionManager.GetGameLatestZip(gameState))!) { if (asset == null) { @@ -3844,17 +3835,20 @@ await Dialog_InsufficientDriveSpace(Content, diskFreeSpace, remainedDownloadUnco private async Task GetPackagesRemoteSize(List packageList, CancellationToken token) { - // Iterate and assign the remote size to each package inside the list - for (int i = 0; i < packageList.Count; i++) + // Iterate and assign the remote size to each package inside the list in parallel + await Parallel.ForEachAsync(packageList, new ParallelOptions { - if (packageList[i].Segments != null) + CancellationToken = token + }, async (package, innerToken) => + { + if (package.Segments != null) { - await TryGetSegmentedPackageRemoteSize(packageList[i], token); - continue; + await TryGetSegmentedPackageRemoteSize(package, token); + return; } - await TryGetPackageRemoteSize(packageList[i], token); - } + await TryGetPackageRemoteSize(package, token); + }); } #endregion diff --git a/CollapseLauncher/Classes/Interfaces/Class/Structs.cs b/CollapseLauncher/Classes/Interfaces/Class/Structs.cs index 418654861..a783e09f3 100644 --- a/CollapseLauncher/Classes/Interfaces/Class/Structs.cs +++ b/CollapseLauncher/Classes/Interfaces/Class/Structs.cs @@ -111,7 +111,7 @@ public static bool Equals(this GameVersion? fromVersion, GameVersion? toVersion) } } - public struct GameVersion + public record struct GameVersion { public GameVersion(int major, int minor, int build, int revision = 0) { diff --git a/CollapseLauncher/Classes/Properties/WindowSizeProp/WindowSizeProp.cs b/CollapseLauncher/Classes/Properties/WindowSizeProp/WindowSizeProp.cs index 38e542342..2aac761a1 100644 --- a/CollapseLauncher/Classes/Properties/WindowSizeProp/WindowSizeProp.cs +++ b/CollapseLauncher/Classes/Properties/WindowSizeProp/WindowSizeProp.cs @@ -31,11 +31,11 @@ Dictionary WindowSizeProfiles EventPostCarouselBounds = new Size(340, 158), PostPanelBounds = new Size(340, 84), PostPanelBottomMargin = new Thickness(0, 0, 0, 20), - PostPanelPaimonHeight = 138, - PostPanelPaimonMargin = new Thickness(0, -48, -64, 0), + PostPanelPaimonHeight = 110, + PostPanelPaimonMargin = new Thickness(0, -48, -56, 0), PostPanelPaimonInnerMargin = new Thickness(0, 0, 0, 0), - PostPanelPaimonTextMargin = new Thickness(0, 0, 8, 28), - PostPanelPaimonTextSize = 14, + PostPanelPaimonTextMargin = new Thickness(0, 0, 0, 18), + PostPanelPaimonTextSize = 11, BannerIconWidth = 136, BannerIconWidthHYP = 111, BannerIconMargin = new Thickness(0, 0, 94, 84), diff --git a/CollapseLauncher/Classes/RegionManagement/FallbackCDNUtil.cs b/CollapseLauncher/Classes/RegionManagement/FallbackCDNUtil.cs index 8bff6178f..8b9b283bf 100644 --- a/CollapseLauncher/Classes/RegionManagement/FallbackCDNUtil.cs +++ b/CollapseLauncher/Classes/RegionManagement/FallbackCDNUtil.cs @@ -4,7 +4,6 @@ using Hi3Helper.Http; using Hi3Helper.Http.Legacy; using Hi3Helper.Shared.Region; -using Squirrel.Sources; using System; using System.Diagnostics; using System.IO; @@ -12,18 +11,30 @@ using System.Net; using System.Net.Http; using System.Net.Http.Json; -using System.Text; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; using System.Threading; using System.Threading.Tasks; using static Hi3Helper.Logger; using static Hi3Helper.Shared.Region.LauncherConfig; +#if !USEVELOPACK +using System.Text; +using Squirrel.Sources; +#else +using System.Text; +using Velopack.Sources; +#endif + namespace CollapseLauncher { public class UpdateManagerHttpAdapter : IFileDownloader { +#if !USEVELOPACK public async Task DownloadFile(string url, string targetFile, Action progress, string authorization = null, string accept = null) +#else +#nullable enable + public async Task DownloadFile(string url, string targetFile, Action progress, string? authorization = null, string? accept = null, CancellationToken cancelToken = default) +#endif { // Initialize new proxy-aware HttpClient using HttpClient client = new HttpClientBuilder() @@ -36,7 +47,13 @@ public async Task DownloadFile(string url, string targetFile, Action progre try { FallbackCDNUtil.DownloadProgress += progressEvent; - await FallbackCDNUtil.DownloadCDNFallbackContent(downloadClient, targetFile, AppCurrentDownloadThread, GetRelativePathOnly(url), default); + await FallbackCDNUtil.DownloadCDNFallbackContent(downloadClient, targetFile, AppCurrentDownloadThread, GetRelativePathOnly(url), +#if !USEVELOPACK + default +#else + cancelToken +#endif + ); } catch { throw; } finally @@ -45,7 +62,11 @@ public async Task DownloadFile(string url, string targetFile, Action progre } } +#if !USEVELOPACK public async Task DownloadBytes(string url, string authorization = null, string accept = null) +#else + public async Task DownloadBytes(string url, string? authorization = null, string? accept = null) +#endif { await using BridgedNetworkStream stream = await FallbackCDNUtil.TryGetCDNFallbackStream(GetRelativePathOnly(url), default, true); byte[] buffer = new byte[stream.Length]; @@ -53,7 +74,11 @@ public async Task DownloadBytes(string url, string authorization = null, return buffer; } +#if !USEVELOPACK public async Task DownloadString(string url, string authorization = null, string accept = null) +#else + public async Task DownloadString(string url, string? authorization = null, string? accept = null) +#endif { await using BridgedNetworkStream stream = await FallbackCDNUtil.TryGetCDNFallbackStream(GetRelativePathOnly(url), default, true); byte[] buffer = new byte[stream.Length]; @@ -68,6 +93,10 @@ private string GetRelativePathOnly(string url) } } +#if USEVELOPACK +#nullable restore +#endif + internal readonly struct CDNUtilHTTPStatus { internal readonly HttpStatusCode StatusCode; @@ -80,7 +109,7 @@ internal CDNUtilHTTPStatus(HttpResponseMessage message) : this(false) Message = message; StatusCode = Message.StatusCode; IsSuccessStatusCode = Message.IsSuccessStatusCode; - AbsoluteURL = Message.RequestMessage.RequestUri; + AbsoluteURL = Message.RequestMessage?.RequestUri; } private CDNUtilHTTPStatus(bool isInitializationError) => IsInitializationError = isInitializationError; @@ -133,34 +162,6 @@ public static void InitializeHttpClient() public static event EventHandler DownloadProgress; - public static async Task DownloadCDNFallbackContent(Http httpInstance, string outputPath, int parallelThread, string relativeURL, CancellationToken token) - { - // Get the preferred CDN first and try get the content - CDNURLProperty preferredCDN = GetPreferredCDN(); - bool isSuccess = await TryGetCDNContent(preferredCDN, httpInstance, outputPath, relativeURL, parallelThread, token); - - // If successful, then return - if (isSuccess) return; - - // If the fail return code occurred by the token, then throw cancellation exception - token.ThrowIfCancellationRequested(); - - // If not, then continue to get the content from another CDN - foreach (CDNURLProperty fallbackCDN in CDNList.Where(x => !x.Equals(preferredCDN))) - { - isSuccess = await TryGetCDNContent(fallbackCDN, httpInstance, outputPath, relativeURL, parallelThread, token); - - // If successful, then return - if (isSuccess) return; - } - - // If all of them failed, then throw an exception - if (!isSuccess) - { - throw new AggregateException($"All available CDNs aren't reachable for your network while getting content: {relativeURL}. Please check your internet!"); - } - } - public static async Task DownloadCDNFallbackContent(DownloadClient downloadClient, string outputPath, int parallelThread, string relativeURL, CancellationToken token) { // Get the preferred CDN first and try get the content @@ -189,37 +190,6 @@ public static async Task DownloadCDNFallbackContent(DownloadClient downloadClien } } - public static async Task DownloadCDNFallbackContent(Http httpInstance, Stream outputStream, string relativeURL, CancellationToken token) - { - // Argument check - PerformStreamCheckAndSeek(outputStream); - - // Get the preferred CDN first and try get the content - CDNURLProperty preferredCDN = GetPreferredCDN(); - bool isSuccess = await TryGetCDNContent(preferredCDN, httpInstance, outputStream, relativeURL, token); - - // If successful, then return - if (isSuccess) return; - - // If the fail return code occurred by the token, then throw cancellation exception - token.ThrowIfCancellationRequested(); - - // If not, then continue to get the content from another CDN - foreach (CDNURLProperty fallbackCDN in CDNList.Where(x => !x.Equals(preferredCDN))) - { - isSuccess = await TryGetCDNContent(fallbackCDN, httpInstance, outputStream, relativeURL, token); - - // If successful, then return - if (isSuccess) return; - } - - // If all of them failed, then throw an exception - if (!isSuccess) - { - throw new AggregateException($"All available CDNs aren't reachable for your network while getting content: {relativeURL}. Please check your internet!"); - } - } - public static async Task DownloadCDNFallbackContent(DownloadClient downloadClient, Stream outputStream, string relativeURL, CancellationToken token) { // Argument check @@ -251,7 +221,7 @@ public static async Task DownloadCDNFallbackContent(DownloadClient downloadClien } } - public static async ValueTask TryGetCDNFallbackStream(string relativeURL, CancellationToken token = default, bool isForceUncompressRequest = false) + public static async Task TryGetCDNFallbackStream(string relativeURL, CancellationToken token = default, bool isForceUncompressRequest = false) { // Get the preferred CDN first and try get the content CDNURLProperty preferredCDN = GetPreferredCDN(); @@ -290,7 +260,7 @@ private static void PerformStreamCheckAndSeek(Stream outputStream) outputStream.Position = 0; } - private static async ValueTask TryGetCDNContent(CDNURLProperty cdnProp, string relativeURL, CancellationToken token, bool isForceUncompressRequest) + private static async Task TryGetCDNContent(CDNURLProperty cdnProp, string relativeURL, CancellationToken token, bool isForceUncompressRequest) { try { @@ -311,58 +281,24 @@ private static async ValueTask TryGetCDNContent(CDNURLProp } } - private static async ValueTask TryGetCDNContent(CDNURLProperty cdnProp, Http httpInstance, Stream outputStream, string relativeURL, CancellationToken token) + private static async ValueTask TryGetCDNContent(CDNURLProperty cdnProp, DownloadClient downloadClient, Stream outputStream, string relativeURL, CancellationToken token) { - try - { - // Subscribe the progress to the adapter - httpInstance.DownloadProgress += HttpInstance_DownloadProgressAdapter; - - // Get the URL Status then return boolean and and URLStatus - (bool, string) urlStatus = await TryGetURLStatus(cdnProp, httpInstance, relativeURL, token); - - // If URL status is false, then return false - if (!urlStatus.Item1) return false; - - // Continue to get the content and return true if successful - await httpInstance.Download(urlStatus.Item2, outputStream, null, null, token); - return true; - } - // Handle the error and log it. If fails, then log it and return false - catch (Exception ex) - { - LogWriteLine($"Failed while getting CDN content from: {cdnProp.Name} (prefix: {cdnProp.URLPrefix}) (relPath: {relativeURL})\r\n{ex}", LogType.Error, true); - return false; - } - // Finally, unsubscribe the progress from the adapter - finally - { - httpInstance.DownloadProgress -= HttpInstance_DownloadProgressAdapter; - } - } + DownloadEvent DownloadClientAdapter = new DownloadEvent(); + Stopwatch stopwatch = new Stopwatch(); - private static async ValueTask TryGetCDNContent(CDNURLProperty cdnProp, Http httpInstance, string outputPath, string relativeURL, int parallelThread, CancellationToken token) - { try { - // Subscribe the progress to the adapter - httpInstance.DownloadProgress += HttpInstance_DownloadProgressAdapter; - // Get the URL Status then return boolean and and URLStatus - (bool, string) urlStatus = await TryGetURLStatus(cdnProp, httpInstance, relativeURL, token); + (bool, string) urlStatus = await TryGetURLStatus(cdnProp, downloadClient, relativeURL, token); // If URL status is false, then return false if (!urlStatus.Item1) return false; + // Start stopwatch + stopwatch.Start(); + // Continue to get the content and return true if successful - if (!cdnProp.PartialDownloadSupport) - { - // If the CDN marked to not supporting the partial download, then use single thread mode download. - await httpInstance.Download(urlStatus.Item2, outputPath, true, null, null, token); - return true; - } - await httpInstance.Download(urlStatus.Item2, outputPath, (byte)parallelThread, true, token); - await httpInstance.Merge(token); + await downloadClient.DownloadAsync(urlStatus.Item2, outputStream, false, HttpInstanceDownloadProgressAdapter, null, null, cancelToken:token); return true; } // Handle the error and log it. If fails, then log it and return false @@ -371,37 +307,29 @@ private static async ValueTask TryGetCDNContent(CDNURLProperty cdnProp, Ht LogWriteLine($"Failed while getting CDN content from: {cdnProp.Name} (prefix: {cdnProp.URLPrefix}) (relPath: {relativeURL})\r\n{ex}", LogType.Error, true); return false; } - // Finally, unsubscribe the progress from the adapter finally { - httpInstance.DownloadProgress -= HttpInstance_DownloadProgressAdapter; + stopwatch.Stop(); } - } - - private static async ValueTask TryGetCDNContent(CDNURLProperty cdnProp, DownloadClient downloadClient, Stream outputStream, string relativeURL, CancellationToken token) - { - try - { - // Get the URL Status then return boolean and and URLStatus - (bool, string) urlStatus = await TryGetURLStatus(cdnProp, downloadClient, relativeURL, token); - - // If URL status is false, then return false - if (!urlStatus.Item1) return false; - // Continue to get the content and return true if successful - await downloadClient.DownloadAsync(urlStatus.Item2, outputStream, false, HttpInstance_DownloadProgressAdapter, null, null, cancelToken:token); - return true; - } - // Handle the error and log it. If fails, then log it and return false - catch (Exception ex) + void HttpInstanceDownloadProgressAdapter(int read, DownloadProgress downloadProgress) { - LogWriteLine($"Failed while getting CDN content from: {cdnProp.Name} (prefix: {cdnProp.URLPrefix}) (relPath: {relativeURL})\r\n{ex}", LogType.Error, true); - return false; + DownloadClientAdapter.SizeToBeDownloaded = downloadProgress.BytesTotal; + DownloadClientAdapter.SizeDownloaded = downloadProgress.BytesDownloaded; + DownloadClientAdapter.Read = read; + + long speed = (long)(downloadProgress.BytesDownloaded / stopwatch.Elapsed.TotalSeconds); + DownloadClientAdapter.Speed = speed; + + DownloadProgress?.Invoke(null, DownloadClientAdapter); } } private static async ValueTask TryGetCDNContent(CDNURLProperty cdnProp, DownloadClient downloadClient, string outputPath, string relativeURL, int parallelThread, CancellationToken token) { + DownloadEvent DownloadClientAdapter = new DownloadEvent(); + Stopwatch stopwatch = new Stopwatch(); + try { // Get the URL Status then return boolean and and URLStatus @@ -410,15 +338,18 @@ private static async ValueTask TryGetCDNContent(CDNURLProperty cdnProp, Do // If URL status is false, then return false if (!urlStatus.Item1) return false; + // Start stopwatch + stopwatch.Start(); + // Continue to get the content and return true if successful if (!cdnProp.PartialDownloadSupport) { // If the CDN marked to not supporting the partial download, then use single thread mode download. using FileStream stream = File.Create(outputPath); - await downloadClient.DownloadAsync(urlStatus.Item2, stream, false, HttpInstance_DownloadProgressAdapter, null, null, cancelToken:token); + await downloadClient.DownloadAsync(urlStatus.Item2, stream, false, HttpInstanceDownloadProgressAdapter, null, null, cancelToken:token); return true; } - await downloadClient.DownloadAsync(urlStatus.Item2, outputPath, true, progressDelegateAsync: HttpInstance_DownloadProgressAdapter, maxConnectionSessions: parallelThread, cancelToken: token); + await downloadClient.DownloadAsync(urlStatus.Item2, outputPath, true, progressDelegateAsync: HttpInstanceDownloadProgressAdapter, maxConnectionSessions: parallelThread, cancelToken: token); return true; } // Handle the error and log it. If fails, then log it and return false @@ -427,30 +358,25 @@ private static async ValueTask TryGetCDNContent(CDNURLProperty cdnProp, Do LogWriteLine($"Failed while getting CDN content from: {cdnProp.Name} (prefix: {cdnProp.URLPrefix}) (relPath: {relativeURL})\r\n{ex}", LogType.Error, true); return false; } - } - - private static async Task<(bool, string)> TryGetURLStatus(CDNURLProperty cdnProp, Http httpInstance, string relativeURL, CancellationToken token) - { - // Concat the URL Prefix and Relative URL - string absoluteURL = ConverterTool.CombineURLFromString(cdnProp.URLPrefix, relativeURL); + finally + { + stopwatch.Stop(); + } - LogWriteLine($"Getting CDN Content from: {cdnProp.Name} at URL: {absoluteURL}", LogType.Default, true); + void HttpInstanceDownloadProgressAdapter(int read, DownloadProgress downloadProgress) + { + DownloadClientAdapter.SizeToBeDownloaded = downloadProgress.BytesTotal; + DownloadClientAdapter.SizeDownloaded = downloadProgress.BytesDownloaded; + DownloadClientAdapter.Read = read; - // Try check the status of the URL - Tuple returnCode = await httpInstance.GetURLStatus(absoluteURL, token); + long speed = (long)(downloadProgress.BytesDownloaded / stopwatch.Elapsed.TotalSeconds); + DownloadClientAdapter.Speed = speed; - // If it's not a successful code, then return false - if (!returnCode.Item2) - { - LogWriteLine($"CDN content from: {cdnProp.Name} (prefix: {cdnProp.URLPrefix}) (relPath: {relativeURL}) has returned error code: {returnCode.Item1}", LogType.Error, true); - return (false, absoluteURL); + DownloadProgress?.Invoke(null, DownloadClientAdapter); } - - // Otherwise, return true - return (true, absoluteURL); } - private static async Task<(bool, string)> TryGetURLStatus(CDNURLProperty cdnProp, DownloadClient downloadClient, string relativeURL, CancellationToken token) + private static async ValueTask<(bool, string)> TryGetURLStatus(CDNURLProperty cdnProp, DownloadClient downloadClient, string relativeURL, CancellationToken token) { // Concat the URL Prefix and Relative URL string absoluteURL = ConverterTool.CombineURLFromString(cdnProp.URLPrefix, relativeURL); @@ -513,22 +439,16 @@ public static CDNURLProperty GetPreferredCDN() return CDNList[cdnIndex]; } - public static async Task DownloadAsJSONType(string URL, JsonSerializerContext context, CancellationToken token) - => (T)await _client.GetFromJsonAsync(URL, typeof(T), context, token) ?? default; - - public static async ValueTask GetURLHttpResponse(string URL, CancellationToken token, bool isForceUncompressRequest = false) - { - return isForceUncompressRequest ? await _clientNoCompression.GetURLHttpResponse(URL, HttpMethod.Get, token) - : await _client.GetURLHttpResponse(URL, HttpMethod.Get, token); - } + public static async Task GetURLHttpResponse(string URL, CancellationToken token, bool isForceUncompressRequest = false) + => isForceUncompressRequest ? await _clientNoCompression.GetURLHttpResponse(URL, HttpMethod.Get, token) + : await _client.GetURLHttpResponse(URL, HttpMethod.Get, token); #nullable enable - public static async ValueTask GetURLHttpResponse(this HttpClient client, string url, HttpMethod? httpMethod = null, CancellationToken token = default) - { - httpMethod ??= HttpMethod.Get; - using HttpRequestMessage requestMsg = new HttpRequestMessage(httpMethod, url); - return await client.SendAsync(requestMsg, HttpCompletionOption.ResponseHeadersRead, token); - } + public static async Task DownloadAsJSONType(string? URL, JsonTypeInfo typeInfo, CancellationToken token) + => await _client.GetFromJsonAsync(URL, typeInfo, token); + + public static async Task GetURLHttpResponse(this HttpClient client, string url, HttpMethod? httpMethod = null, CancellationToken token = default) + => await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token); public static async ValueTask GetURLStatusCode(string URL, CancellationToken token) => await _client.GetURLStatusCode(URL, token); @@ -540,13 +460,13 @@ public static async ValueTask GetURLStatusCode(this HttpClient client } #nullable restore - public static async ValueTask GetHttpStreamFromResponse(string URL, CancellationToken token) + public static async Task GetHttpStreamFromResponse(string URL, CancellationToken token) { HttpResponseMessage responseMsg = await GetURLHttpResponse(URL, token); return await GetHttpStreamFromResponse(responseMsg, token); } - public static async ValueTask GetHttpStreamFromResponse(HttpResponseMessage responseMsg, CancellationToken token) + public static async Task GetHttpStreamFromResponse(HttpResponseMessage responseMsg, CancellationToken token) => await BridgedNetworkStream.CreateStream(responseMsg, token); public static async ValueTask GetCDNLatencies(CancellationTokenSource tokenSource, int pingCount = 1) @@ -591,17 +511,12 @@ public static async ValueTask GetCDNLatencies(CancellationTokenSource to return latencies; } - public static async ValueTask GetContentLength(string url, CancellationToken token) - { - using HttpRequestMessage message = new HttpRequestMessage() { RequestUri = new Uri(url) }; - using HttpResponseMessage response = await _clientNoCompression.SendAsync(message, HttpCompletionOption.ResponseHeadersRead, token); - long Length = response.Content.Headers.ContentLength ?? 0; - return Length; - } + public static async ValueTask GetContentLength(string url, CancellationToken token) => + (await _clientNoCompression.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token)).Content.Headers.ContentLength ?? 0; public static string TryGetAbsoluteToRelativeCDNURL(string URL, string searchIndexStr) { - int indexOf = URL.IndexOf(searchIndexStr); + int indexOf = URL.IndexOf(searchIndexStr, StringComparison.Ordinal); if (indexOf < 0) return URL; @@ -612,17 +527,5 @@ public static string TryGetAbsoluteToRelativeCDNURL(string URL, string searchInd URL = ConverterTool.CombineURLFromString(cdnParentURL, relativeURL); return URL; } - - // Re-send the events to the static DownloadProgress - private static void HttpInstance_DownloadProgressAdapter(object sender, DownloadEvent e) => DownloadProgress?.Invoke(sender, e); - - private static DownloadEvent DownloadClientAdapter = new DownloadEvent(); - - private static void HttpInstance_DownloadProgressAdapter(int read, DownloadProgress downloadProgress) - { - DownloadClientAdapter.SizeToBeDownloaded = downloadProgress.BytesTotal; - DownloadClientAdapter.SizeDownloaded = downloadProgress.BytesDownloaded; - DownloadClientAdapter.Read = read; - } } } diff --git a/CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs b/CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs index 337dd4598..e1af287fd 100644 --- a/CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs +++ b/CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs @@ -220,7 +220,7 @@ private async ValueTask ThrowIfFileIsNotSenadina(Stream stream, CancellationToke ArgumentNullException.ThrowIfNull(stream); Memory header = new byte[_collapseHeader.Length]; - await stream.ReadAsync(header, token)!; + _ = await stream.ReadAsync(header, token)!; if (!header.Span.SequenceEqual(_collapseHeader)) throw new InvalidDataException($"Daftar pustaka file is corrupted! Expecting header: 0x{BinaryPrimitives.ReadInt64LittleEndian(_collapseHeader):x8} but got: 0x{BinaryPrimitives.ReadInt64LittleEndian(header.Span):x8} instead!"); } @@ -241,12 +241,12 @@ private void EliminatePluginAssetIndex(List assetIndex) private HonkaiRepairAssetIgnore GetIgnoredAssetsProperty() { // Try get the parent registry key - RegistryKey? keys = Registry.CurrentUser.OpenSubKey(_gameVersionManager!.GamePreset!.ConfigRegistryLocation!); + RegistryKey? keys = Registry.CurrentUser.OpenSubKey(_gameVersionManager!.GamePreset!.ConfigRegistryLocation); if (keys == null) return HonkaiRepairAssetIgnore.CreateEmpty(); // Return an empty property if the parent key doesn't exist // Initialize the property - AudioPCKType[] IgnoredAudioPCKTypes = Array.Empty(); - int[] IgnoredVideoCGSubCategory = Array.Empty(); + AudioPCKType[] IgnoredAudioPCKTypes = []; + int[] IgnoredVideoCGSubCategory = []; // Try get the values of the registry key of the Audio ignored list object? objIgnoredAudioPCKTypes = keys.GetValue("GENERAL_DATA_V2_DeletedAudioTypes_h214176984"); @@ -310,14 +310,13 @@ private async Task BuildVideoIndex(DownloadClient downloadClient, CacheAsset cac private async Task BuildAndEnumerateVideoVersioningFile(CancellationToken token, IEnumerable enumEntry, List assetIndex, HonkaiRepairAssetIgnore ignoredAssetIDs, string assetBundleURL) { - ArgumentNullException.ThrowIfNull(token); ArgumentNullException.ThrowIfNull(assetIndex); // Get the base URL string baseURL = CombineURLFromString((GetAppConfigValue("EnableHTTPRepairOverride").ToBool() ? "http://" : "https://") + assetBundleURL, "/Video/"); // Build video versioning file - using (StreamWriter sw = new StreamWriter(Path.Combine(_gamePath!, NormalizePath(_videoBaseLocalPath)!, "Version.txt"), false)) + await using (StreamWriter sw = new StreamWriter(Path.Combine(_gamePath!, NormalizePath(_videoBaseLocalPath)!, "Version.txt"), false)) { // Iterate the metadata to be converted into asset index in parallel await Parallel.ForEachAsync(enumEntry!, new ParallelOptions @@ -340,10 +339,10 @@ private async Task BuildAndEnumerateVideoVersioningFile(CancellationToken token, } } - #if DEBUG + #if DEBUG if (isCGIgnored) LogWriteLine($"Ignoring CG Category: {metadata.CgSubCategory} {(_audioLanguage == AudioLanguageType.Japanese ? metadata.CgPathHighBitrateJP : metadata.CgPathHighBitrateCN)}", LogType.Debug, true); - #endif + #endif if (!metadata.InStreamingAssets && isCGAvailable && !isCGIgnored) { @@ -603,101 +602,100 @@ private async Task FetchXMFFile(HttpClient _httpClient, List FetchPatchConfigXMFFile(Stream xmfStream, SenadinaFileIdentifier patchConfigFileIdentifier, - HttpClient _httpClient, CancellationToken token) + HttpClient httpClient, CancellationToken token) { // Start downloading XMF and load it to MemoryStream first - using (MemoryStream mfs = new MemoryStream()) - { - // Copy the remote stream of Patch Config to temporal mfs - await patchConfigFileIdentifier!.fileStream!.CopyToAsync(mfs, token); - // Reset the MemoryStream position - mfs.Position = 0; - -#nullable enable - // Get the version provided by the XMF - int[]? gameVersion = XMFUtility.GetXMFVersion(xmfStream); - if (gameVersion == null) return null; -#nullable disable - - // Initialize and parse the manifest, then return the Patch Asset - return new BlockPatchManifest(mfs, gameVersion); - } + using MemoryStream mfs = new MemoryStream(); + // Copy the remote stream of Patch Config to temporal mfs + await patchConfigFileIdentifier!.fileStream!.CopyToAsync(mfs, token); + // Reset the MemoryStream position + mfs.Position = 0; + + #nullable enable + // Get the version provided by the XMF + int[]? gameVersion = XMFUtility.GetXMFVersion(xmfStream); + // Initialize and parse the manifest, then return the Patch Asset + return gameVersion == null ? null : new BlockPatchManifest(mfs, gameVersion); } -#nullable enable private void BuildBlockIndex(List assetIndex, BlockPatchManifest? patchInfo, string xmfPath, Stream xmfStream, bool isMeta) { // Reset the temporal stream pos. diff --git a/CollapseLauncher/CollapseLauncher.csproj b/CollapseLauncher/CollapseLauncher.csproj index ec4c2f4df..6b85775c4 100644 --- a/CollapseLauncher/CollapseLauncher.csproj +++ b/CollapseLauncher/CollapseLauncher.csproj @@ -1,4 +1,4 @@ - + WinExe @@ -11,26 +11,33 @@ Collapse Collapse Collapse Launcher - Collapse Launcher + Collapse Launcher Collapse Launcher Team $(Company). neon-nyan, Cry0, bagusnl, shatyuka, gablm. Copyright 2022-2024 $(Company) - 1.81.6 + 1.81.7 preview x64 - net8.0-windows10.0.22621.0 + net9.0-windows10.0.22621.0 10.0.22621.41 10.0.17763.0 win-x64 true + true + true portable false + + false true true + + true + false true true @@ -39,7 +46,11 @@ false true true - false + false + + + + true - DISABLE_XAML_GENERATED_MAIN;USENEWZIPDECOMPRESS;ENABLEHTTPREPAIR;DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION;PREVIEW;DUMPGIJSON;SIMULATEGIHDR;GSPBYPASSGAMERUNNING;MHYPLUGINSUPPORT - + DISABLE_XAML_GENERATED_MAIN;USEVELOPACK;USENEWZIPDECOMPRESS;ENABLEHTTPREPAIR;DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION;PREVIEW;DUMPGIJSON;SIMULATEGIHDR;GSPBYPASSGAMERUNNING;MHYPLUGINSUPPORT + full + true + - DISABLE_XAML_GENERATED_MAIN;USENEWZIPDECOMPRESS;ENABLEHTTPREPAIR;DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION;PREVIEW;MHYPLUGINSUPPORT + DISABLE_XAML_GENERATED_MAIN;USEVELOPACK;USENEWZIPDECOMPRESS;ENABLEHTTPREPAIR;DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION;PREVIEW;MHYPLUGINSUPPORT True true - DISABLE_XAML_GENERATED_MAIN;ENABLEHTTPREPAIR;USENEWZIPDECOMPRESS;DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION;MHYPLUGINSUPPORT + DISABLE_XAML_GENERATED_MAIN;USEVELOPACK;USENEWZIPDECOMPRESS;ENABLEHTTPREPAIR;DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION;MHYPLUGINSUPPORT true true - + + + - - - - - - - - - - + + + + + + + + - - - + + + - - - + + + + + + + + + + + + diff --git a/CollapseLauncher/Program.cs b/CollapseLauncher/Program.cs index 9f4224b56..83702c59c 100644 --- a/CollapseLauncher/Program.cs +++ b/CollapseLauncher/Program.cs @@ -1,10 +1,16 @@ -using CollapseLauncher.Helper.Update; +using CollapseLauncher.Helper; +using CollapseLauncher.Helper.Update; using Hi3Helper; using Hi3Helper.Http.Legacy; using Hi3Helper.Shared.ClassStruct; using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; +#if !USEVELOPACK using Squirrel; +#else +using NuGet.Versioning; +using Velopack; +#endif using System; using System.Diagnostics; using System.Globalization; @@ -19,15 +25,16 @@ using static Hi3Helper.Locale; using static Hi3Helper.Logger; using static Hi3Helper.Shared.Region.LauncherConfig; +using InnoSetupHelper; namespace CollapseLauncher; public static class MainEntryPoint { - #nullable enable +#nullable enable public static int InstanceCount; public static App? CurrentAppInstance; - #nullable restore +#nullable restore [DllImport("Microsoft.ui.xaml.dll")] private static extern void XamlCheckProcessRequirements(); @@ -35,14 +42,12 @@ public static class MainEntryPoint [STAThread] public static void Main(params string[] args) { - #if PREVIEW +#if PREVIEW IsPreview = true; - #endif +#endif try { - StartSquirrelHook(); - AppCurrentArgument = args; // Extract icons from the executable file @@ -69,6 +74,11 @@ public static void Main(params string[] args) Directory.SetCurrentDirectory(AppFolder); } + // Initialize TaskSchedulerHelper + TaskSchedulerHelper.InvokeGetStatusCommand(); + + StartUpdaterHook(); + LogWriteLine(string.Format("Running Collapse Launcher [{0}], [{3}], under {1}, as {2}", LauncherUpdateHelper.LauncherCurrentVersionString, GetVersionString(), @@ -88,6 +98,9 @@ public static void Main(params string[] args) ParseArguments(args); InitializeAppSettings(); + // Initiate InnoSetupHelper's log event + InnoSetupLogUpdate.LoggerEvent += InnoSetupLogUpdate_LoggerEvent; + HttpLogInvoker.DownloadLog += HttpClientLogWatcher!; switch (m_appMode) @@ -142,6 +155,17 @@ public static void Main(params string[] args) } } + private static void InnoSetupLogUpdate_LoggerEvent(object sender, InnoSetupLogStruct e) => + LogWriteLine( + e.Message, + e.LogType switch + { + InnoSetupLogType.Warning => LogType.Warning, + InnoSetupLogType.Error => LogType.Error, + _ => LogType.Default + }, + e.IsWriteToLog); + public static void SpawnFatalErrorConsole(Exception ex) { CurrentAppInstance?.Exit(); @@ -151,7 +175,7 @@ public static void SpawnFatalErrorConsole(Exception ex) "please report it to: https://github.com/CollapseLauncher/Collapse/issues\r\n" + "Press any key to exit or Press 'R' to restart the main thread app..."); - #if !DEBUG +#if !DEBUG try { if (ConsoleKey.R == Console.ReadKey().Key) @@ -162,7 +186,7 @@ public static void SpawnFatalErrorConsole(Exception ex) Console.WriteLine(e); throw; } - #endif +#endif } public static void StartMainApplication() @@ -196,8 +220,9 @@ private static void OnProcessExit(object sender, EventArgs e) App.IsAppKilled = true; } - private static void StartSquirrelHook() + private static void StartUpdaterHook() { +#if !USEVELOPACK // Add Squirrel Hooks SquirrelAwareApp.HandleEvents( // Add shortcut and uninstaller entry on first start-up @@ -221,7 +246,93 @@ private static void StartSquirrelHook() // ReSharper restore UnusedParameter.Local onEveryRun: (_, _, _) => { } ); +#else + VelopackApp.Build() + .WithRestarted(TryCleanupFallbackUpdate) + .WithAfterUpdateFastCallback(TryCleanupFallbackUpdate) + .WithFirstRun(TryCleanupFallbackUpdate) + .Run(ILoggerHelper.CreateCollapseILogger()); +#endif + } + +#if USEVELOPACK + public static void TryCleanupFallbackUpdate(SemanticVersion newVersion) + { + string currentExecutedAppFolder = AppFolder.TrimEnd('\\'); + + // If the path is not actually running under "current" velopack folder, then return + if (!currentExecutedAppFolder.EndsWith("current", StringComparison.OrdinalIgnoreCase)) // Expecting "current" + { + Logger.LogWriteLine("[TryCleanupFallbackUpdate] The launcher does not run from \"current\" folder"); + return; + } + + try + { + // Otherwise, start cleaning-up process + string currentExecutedParentFolder = Path.GetDirectoryName(currentExecutedAppFolder); + if (currentExecutedParentFolder != null) + { + DirectoryInfo directoryInfo = new DirectoryInfo(currentExecutedParentFolder); + foreach (DirectoryInfo childLegacyAppSemVerFolder in directoryInfo.EnumerateDirectories("app-*", SearchOption.TopDirectoryOnly)) + { + // Removing the "app-*" folder + childLegacyAppSemVerFolder.Delete(true); + Logger.LogWriteLine($"[TryCleanupFallbackUpdate] Removed {childLegacyAppSemVerFolder.FullName} folder!", LogType.Default, true); + } + + // Try to remove squirrel temp clowd folder + string squirrelTempPackagesFolder = Path.Combine(currentExecutedParentFolder, "SquirrelClowdTemp"); + DirectoryInfo squirrelTempPackagesFolderInfo = new DirectoryInfo(squirrelTempPackagesFolder); + if (squirrelTempPackagesFolderInfo.Exists) + { + squirrelTempPackagesFolderInfo.Delete(true); + Logger.LogWriteLine($"[TryCleanupFallbackUpdate] Removed package temp folder: {squirrelTempPackagesFolder}!", LogType.Default, true); + } + + // Try to remove stub executable + string squirrelLegacyStubPath = Path.Combine(currentExecutedParentFolder, "CollapseLauncher.exe"); + RemoveSquirrelFilePath(squirrelLegacyStubPath); + + // Try to remove createdump executable + string squirrelLegacyDumpPath = Path.Combine(currentExecutedParentFolder, "createdump.exe"); + RemoveSquirrelFilePath(squirrelLegacyDumpPath); + + // Try to remove RestartAgent executable + string squirrelLegacyRestartAgentPath = Path.Combine(currentExecutedParentFolder, "RestartAgent.exe"); + RemoveSquirrelFilePath(squirrelLegacyRestartAgentPath); + } + + // Try to remove legacy shortcuts + string currentWindowsPathDrive = Path.GetPathRoot(Environment.SystemDirectory); + if (currentWindowsPathDrive != null) + { + string squirrelLegacyStartMenuGlobal = Path.Combine(currentWindowsPathDrive, @"ProgramData\Microsoft\Windows\Start Menu\Programs\Collapse\Collapse Launcher"); + string squirrelLegacyStartMenuGlobalParent = Path.GetDirectoryName(squirrelLegacyStartMenuGlobal); + if (Directory.Exists(squirrelLegacyStartMenuGlobalParent) && Directory.Exists(squirrelLegacyStartMenuGlobal)) + { + Directory.Delete(squirrelLegacyStartMenuGlobalParent, true); + } + } + + // Try to recreate shortcuts + TaskSchedulerHelper.RecreateIconShortcuts(); + } + catch (Exception ex) + { + Logger.LogWriteLine($"[TryCleanupFallbackUpdate] Failed while operating clean-up routines...\r\n{ex}"); + } + + void RemoveSquirrelFilePath(string filePath) + { + if (File.Exists(filePath)) + { + File.Delete(filePath); + Logger.LogWriteLine($"[TryCleanupFallbackUpdate] Removed old squirrel executables: {filePath}!", LogType.Default, true); + } + } } +#endif public static string FindCollapseStubPath() { diff --git a/CollapseLauncher/Properties/PublishProfiles/Publish-DebugCIRelease.pubxml b/CollapseLauncher/Properties/PublishProfiles/Publish-DebugCIRelease.pubxml new file mode 100644 index 000000000..fda0e2e90 --- /dev/null +++ b/CollapseLauncher/Properties/PublishProfiles/Publish-DebugCIRelease.pubxml @@ -0,0 +1,56 @@ + + + + + + Debug + x64 + + + ..\..\CollapseLauncher-ReleaseRepo\preview-ci-build + FileSystem + + + net9.0-windows10.0.22621.0 + win-x64 + + + true + false + true + full + true + false + false + false + + + true + true + Speed + Speed + true + true + true + true + + + true + false + true + false + + + false + false + false + false + false + true + + + true + + \ No newline at end of file diff --git a/CollapseLauncher/Properties/PublishProfiles/Publish-DebugCIReleaseAOT.pubxml b/CollapseLauncher/Properties/PublishProfiles/Publish-DebugCIReleaseAOT.pubxml new file mode 100644 index 000000000..132d1e2e4 --- /dev/null +++ b/CollapseLauncher/Properties/PublishProfiles/Publish-DebugCIReleaseAOT.pubxml @@ -0,0 +1,56 @@ + + + + + + Debug + x64 + + + ..\..\CollapseLauncher-ReleaseRepo\preview-ci-build + FileSystem + + + net9.0-windows10.0.22621.0 + win-x64 + + + true + false + true + full + true + false + true + true + + + true + true + Speed + Speed + true + true + true + true + + + true + false + true + false + + + false + false + false + false + false + true + + + true + + \ No newline at end of file diff --git a/CollapseLauncher/Properties/PublishProfiles/Publish-PreviewRelease.pubxml b/CollapseLauncher/Properties/PublishProfiles/Publish-PreviewRelease.pubxml index 54ccea8cb..dbc990be9 100644 --- a/CollapseLauncher/Properties/PublishProfiles/Publish-PreviewRelease.pubxml +++ b/CollapseLauncher/Properties/PublishProfiles/Publish-PreviewRelease.pubxml @@ -3,30 +3,53 @@ https://go.microsoft.com/fwlink/?LinkID=208121. --> - - Release - x64 - ..\..\CollapseLauncher-ReleaseRepo\preview-build - FileSystem - net8.0-windows10.0.22621.0 - win-x64 - true - false - true - partial - true - false - false - false - false - false - false - true - false - false - false - true - true - Speed - + + + Release + x64 + + + ..\..\CollapseLauncher-ReleaseRepo\preview-build + FileSystem + + + net9.0-windows10.0.22621.0 + win-x64 + + + true + false + true + full + true + false + false + false + + + true + Speed + Speed + true + true + true + true + + + true + false + true + false + + + false + false + false + false + false + true + + + true + \ No newline at end of file diff --git a/CollapseLauncher/Properties/PublishProfiles/Publish-PreviewReleaseAOT.pubxml b/CollapseLauncher/Properties/PublishProfiles/Publish-PreviewReleaseAOT.pubxml new file mode 100644 index 000000000..8bd01850c --- /dev/null +++ b/CollapseLauncher/Properties/PublishProfiles/Publish-PreviewReleaseAOT.pubxml @@ -0,0 +1,55 @@ + + + + + + Release + x64 + + + ..\..\CollapseLauncher-ReleaseRepo\preview-build + FileSystem + + + net9.0-windows10.0.22621.0 + win-x64 + + + true + false + true + full + true + false + true + true + + + true + Speed + Speed + true + true + true + true + + + true + false + true + false + + + false + false + false + false + false + true + + + true + + \ No newline at end of file diff --git a/CollapseLauncher/Properties/PublishProfiles/Publish-StableRelease.pubxml b/CollapseLauncher/Properties/PublishProfiles/Publish-StableRelease.pubxml index b76b45239..80accc03f 100644 --- a/CollapseLauncher/Properties/PublishProfiles/Publish-StableRelease.pubxml +++ b/CollapseLauncher/Properties/PublishProfiles/Publish-StableRelease.pubxml @@ -3,30 +3,53 @@ https://go.microsoft.com/fwlink/?LinkID=208121. --> - - Publish - x64 - ..\..\CollapseLauncher-ReleaseRepo\stable-build - FileSystem - net8.0-windows10.0.22621.0 - win-x64 - true - false - true - partial - true - false - false - false - false - false - false - true - false - false - false - true - true - Speed - + + + Publish + x64 + + + ..\..\CollapseLauncher-ReleaseRepo\stable-build + FileSystem + + + net9.0-windows10.0.22621.0 + win-x64 + + + true + false + true + full + true + false + false + false + + + true + Speed + Speed + true + true + true + true + + + true + false + true + false + + + false + false + false + false + false + true + + + true + \ No newline at end of file diff --git a/CollapseLauncher/Properties/PublishProfiles/Publish-StableReleaseAOT.pubxml b/CollapseLauncher/Properties/PublishProfiles/Publish-StableReleaseAOT.pubxml new file mode 100644 index 000000000..240fca77c --- /dev/null +++ b/CollapseLauncher/Properties/PublishProfiles/Publish-StableReleaseAOT.pubxml @@ -0,0 +1,55 @@ + + + + + + Publish + x64 + + + ..\..\CollapseLauncher-ReleaseRepo\stable-build + FileSystem + + + net9.0-windows10.0.22621.0 + win-x64 + + + true + false + true + full + true + false + true + true + + + true + Speed + Speed + true + true + true + true + + + true + false + true + false + + + false + false + false + false + false + true + + + true + + \ No newline at end of file diff --git a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml index 3a305e1e5..4e179729c 100644 --- a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml @@ -7,7 +7,6 @@ xmlns:extension="using:CollapseLauncher.Extension" xmlns:helper="using:Hi3Helper" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:p="using:CollapseLauncher.Helper.Metadata" Unloaded="Page_Unloaded" mc:Ignorable="d"> @@ -20,8 +19,7 @@ Background="{ThemeResource WindowBackground}"> - @@ -45,7 +43,7 @@ CornerRadius="0,8,8,0" Opacity="0" Shadow="{ThemeResource SharedShadow}" - Translation="0,0,16" /> + Translation="-48,0,0" /> @@ -128,7 +125,6 @@ CornerRadius="15,0,0,15" DropDownClosed="GameComboBox_OnDropDownClosed" DropDownOpened="GameComboBox_OnDropDownOpened" - ItemsSource="{x:Bind p:LauncherMetadataHelper.LauncherGameNameCollection, Mode=OneWay}" PlaceholderText="{x:Bind helper:Locale.Lang._GameClientTitles['Honkai Impact 3rd']}" SelectionChanged="SetGameCategoryChange" Style="{ThemeResource AcrylicComboBoxStyle}" /> @@ -234,8 +230,8 @@ + Text="{x:Bind helper:Locale.Lang._MainPage.RegionChangeConfirm}" + TextWrapping="Wrap" /> - - + + - + HorizontalAlignment="Center" + VerticalAlignment="Center" + RenderTransformOrigin="0.5, 0.5"> + Width="18" + Height="18"> - + - + - @@ -344,7 +341,6 @@ Grid.Column="0" Height="33" Margin="58,8,0,0" - HorizontalAlignment="Left" VerticalAlignment="Top" Click="GridBG_Icon_Click" PointerEntered="GridBG_Icon_PointerEntered" @@ -395,8 +391,6 @@ Visibility="Visible" /> + - ? gameNameCollection = LauncherMetadataHelper.GetGameNameCollection()!; List? gameRegionCollection = LauncherMetadataHelper.GetGameRegionCollection(lastName!)!; int indexOfName = gameNameCollection.IndexOf(lastName!); int indexOfRegion = gameRegionCollection.IndexOf(lastRegion!); - #nullable restore +#nullable restore // Rebuild Game Titles and Regions ComboBox items ComboBoxGameCategory.ItemsSource = BuildGameTitleListUI(); ComboBoxGameCategory.SelectedIndex = indexOfName; ComboBoxGameRegion.SelectedIndex = indexOfRegion; - InitializeNavigationItems(false); ChangeTitleDragArea.Change(DragAreaTemplate.Default); + + UpdateLayout(); + Bindings.Update(); } private void ShowLoadingPageInvoker_PageEvent(object sender, ShowLoadingPageProperty e) @@ -487,13 +485,6 @@ private async void CustomBackgroundChanger_Event(object sender, BackgroundImgPro switch (mType) { case BackgroundMediaUtility.MediaType.Media: - MediaPlayerFrame = new MediaPlayerElement - { - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center, - Stretch = Stretch.UniformToFill, - Tag = "MediaPlayer" - }; BackgroundNewMediaPlayerGrid.Visibility = Visibility.Visible; BackgroundNewBackGrid.Visibility = Visibility.Collapsed; break; @@ -502,8 +493,8 @@ private async void CustomBackgroundChanger_Event(object sender, BackgroundImgPro BackgroundMediaUtility.SetAlternativeFileStream(imgStream); BackgroundNewMediaPlayerGrid.Visibility = Visibility.Collapsed; BackgroundNewBackGrid.Visibility = Visibility.Visible; - MediaPlayerFrame = null; break; + case BackgroundMediaUtility.MediaType.Unknown: default: throw new InvalidCastException(); } @@ -1261,57 +1252,71 @@ private void InitializeNavigationItems(bool ResetSelection = true) if (CurrentGameVersionCheck.GamePreset.IsCacheUpdateEnabled ?? false) { NavigationViewControl.MenuItems.Add(new NavigationViewItem() - { Content = Lang._CachesPage.PageTitle, Icon = IconCaches, Tag = "caches" }); + { Icon = IconCaches, Tag = "caches" } + .BindNavigationViewItemText("_CachesPage", "PageTitle")); } return; } NavigationViewControl.MenuItems.Add(new NavigationViewItem() - { Content = Lang._HomePage.PageTitle, Icon = IconLauncher, Tag = "launcher" }); + { Icon = IconLauncher, Tag = "launcher" } + .BindNavigationViewItemText("_HomePage", "PageTitle")); - NavigationViewControl.MenuItems.Add(new NavigationViewItemHeader() { Content = Lang._MainPage.NavigationUtilities }); + NavigationViewControl.MenuItems.Add(new NavigationViewItemHeader() + .BindNavigationViewItemText("_MainPage", "NavigationUtilities")); if (CurrentGameVersionCheck.GamePreset.IsRepairEnabled ?? false) { NavigationViewControl.MenuItems.Add(new NavigationViewItem() - { Content = Lang._GameRepairPage.PageTitle, Icon = IconRepair, Tag = "repair" }); + { Icon = IconRepair, Tag = "repair" } + .BindNavigationViewItemText("_GameRepairPage", "PageTitle")); } if (CurrentGameVersionCheck.GamePreset.IsCacheUpdateEnabled ?? false) { NavigationViewControl.MenuItems.Add(new NavigationViewItem() - { Content = Lang._CachesPage.PageTitle, Icon = IconCaches, Tag = "caches" }); + { Icon = IconCaches, Tag = "caches" } + .BindNavigationViewItemText("_CachesPage", "PageTitle")); } switch (CurrentGameVersionCheck.GameType) { case GameNameType.Honkai: NavigationViewControl.FooterMenuItems.Add(new NavigationViewItem() - { Content = Lang._GameSettingsPage.PageTitle, Icon = IconGameSettings, Tag = "honkaigamesettings" }); + { Icon = IconGameSettings, Tag = "honkaigamesettings" } + .BindNavigationViewItemText("_GameSettingsPage", "PageTitle")); break; case GameNameType.StarRail: NavigationViewControl.FooterMenuItems.Add(new NavigationViewItem() - { Content = Lang._StarRailGameSettingsPage.PageTitle, Icon = IconGameSettings, Tag = "starrailgamesettings" }); + { Icon = IconGameSettings, Tag = "starrailgamesettings" } + .BindNavigationViewItemText("_StarRailGameSettingsPage", "PageTitle")); break; case GameNameType.Genshin: NavigationViewControl.FooterMenuItems.Add(new NavigationViewItem() - { Content = Lang._GenshinGameSettingsPage.PageTitle, Icon = IconGameSettings, Tag = "genshingamesettings" }); + { Icon = IconGameSettings, Tag = "genshingamesettings" } + .BindNavigationViewItemText("_GenshinGameSettingsPage", "PageTitle")); break; case GameNameType.Zenless: NavigationViewControl.FooterMenuItems.Add(new NavigationViewItem() - {Content = Lang._GameSettingsPage.PageTitle, Icon = IconGameSettings, Tag = "zenlessgamesettings"}); + { Icon = IconGameSettings, Tag = "zenlessgamesettings" } + .BindNavigationViewItemText("_GameSettingsPage", "PageTitle")); break; } if (NavigationViewControl.SettingsItem is not null && NavigationViewControl.SettingsItem is NavigationViewItem SettingsItem) { - SettingsItem.Content = Lang._SettingsPage.PageTitle; SettingsItem.Icon = IconAppSettings; - ToolTipService.SetToolTip(SettingsItem, Lang._SettingsPage.PageTitle); + _ = SettingsItem.BindNavigationViewItemText("_SettingsPage", "PageTitle"); } - foreach (var deps in NavigationViewControl.FindDescendants()) + foreach (var deps in NavigationViewControl.FindDescendants().OfType()) { + // Avoid any icons to have shadow attached if it's not from this page + if (deps.BaseUri.AbsolutePath != this.BaseUri.AbsolutePath) + { + continue; + } + if (deps is FontIcon icon) AttachShadowNavigationPanelItem(icon); if (deps is AnimatedIcon animIcon) @@ -1323,6 +1328,11 @@ private void InitializeNavigationItems(bool ResetSelection = true) { NavigationViewControl.SelectedItem = (NavigationViewItem)NavigationViewControl.MenuItems[0]; } + + NavigationViewControl.ApplyNavigationViewItemLocaleTextBindings(); + + InputSystemCursor handCursor = InputSystemCursor.Create(InputSystemCursorShape.Hand); + MainPageGrid.SetAllControlsCursorRecursive(handCursor); } public static void AttachShadowNavigationPanelItem(FrameworkElement element) @@ -1346,11 +1356,20 @@ private void NavView_Loaded(object sender, RoutedEventArgs e) } } - var paneRoot = (Grid)NavigationViewControl.FindDescendant("PaneRoot"); - if (paneRoot != null) + NavViewPaneBackground.OpacityTransition = new ScalarTransition() + { + Duration = TimeSpan.FromMilliseconds(150) + }; + NavViewPaneBackground.TranslationTransition = new Vector3Transition() { - paneRoot.PointerEntered += NavView_PanePointerEntered; - paneRoot.PointerExited += NavView_PanePointerExited; + Duration = TimeSpan.FromMilliseconds(150) + }; + + var paneMainGrid = NavigationViewControl.FindDescendant("PaneContentGrid"); + if (paneMainGrid is Grid paneMainGridAsGrid) + { + paneMainGridAsGrid.PointerEntered += NavView_PanePointerEntered; + paneMainGridAsGrid.PointerExited += NavView_PanePointerExited; } // The toggle button is not a part of pane. Why Microsoft!!! @@ -1358,7 +1377,7 @@ private void NavView_Loaded(object sender, RoutedEventArgs e) if (paneToggleButtonGrid != null) { paneToggleButtonGrid.PointerEntered += NavView_PanePointerEntered; - paneToggleButtonGrid.PointerExited += NavView_PanePointerEntered; + paneToggleButtonGrid.PointerExited += NavView_PanePointerExited; } // var backIcon = NavigationViewControl.FindDescendant("NavigationViewBackButton")?.FindDescendant(); @@ -1368,8 +1387,12 @@ private void NavView_Loaded(object sender, RoutedEventArgs e) toggleIcon?.ApplyDropShadow(Colors.Gray, 20); } - private async void NavView_PanePointerEntered(object sender, PointerRoutedEventArgs e) + private void NavView_PanePointerEntered(object sender, PointerRoutedEventArgs e) { + IsCursorInNavBarHoverArea = true; + NavViewPaneBackground.Opacity = 1; + NavViewPaneBackground.Translation = new System.Numerics.Vector3(0, 0, 32); + /* if (!NavigationViewControl.IsPaneOpen) { var duration = TimeSpan.FromSeconds(0.25); @@ -1378,15 +1401,34 @@ private async void NavView_PanePointerEntered(object sender, PointerRoutedEventA .CreateScalarKeyFrameAnimation("Opacity", 1, current); await NavViewPaneBackground.StartAnimation(duration, animation); } + */ } - private async void NavView_PanePointerExited(object sender, PointerRoutedEventArgs e) + private bool IsCursorInNavBarHoverArea; + + private void NavView_PanePointerExited(object sender, PointerRoutedEventArgs e) { + PointerPoint pointerPoint = e.GetCurrentPoint(NavViewPaneBackgroundHoverArea); + IsCursorInNavBarHoverArea = pointerPoint.Position.X <= NavViewPaneBackgroundHoverArea.Width - 8 && pointerPoint.Position.X > 4; + + if (!IsCursorInNavBarHoverArea && !NavigationViewControl.IsPaneOpen) + { + NavViewPaneBackground.Opacity = 0; + NavViewPaneBackground.Translation = new System.Numerics.Vector3(-48, 0, 0); + } + + if (IsCursorInNavBarHoverArea && !NavigationViewControl.IsPaneOpen) + { + NavViewPaneBackground.Opacity = 1; + NavViewPaneBackground.Translation = new System.Numerics.Vector3(0, 0, 32); + } + /* var duration = TimeSpan.FromSeconds(0.25); var current = (float)NavViewPaneBackground.Opacity; var animation = NavViewPaneBackground.GetElementCompositor()! .CreateScalarKeyFrameAnimation("Opacity", 0, current); await NavViewPaneBackground.StartAnimation(duration, animation); + */ } private void NavView_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args) @@ -1395,8 +1437,8 @@ private void NavView_ItemInvoked(NavigationView sender, NavigationViewItemInvoke if (args.IsSettingsInvoked && PreviousTag != "settings") Navigate(typeof(SettingsPage), "settings"); #nullable enable - NavigationViewItem? item = sender.MenuItems.OfType().FirstOrDefault(x => (string)x.Content == (string)args.InvokedItem); - item ??= sender.FooterMenuItems.OfType().FirstOrDefault(x => (string)x.Content == (string)args.InvokedItem); + NavigationViewItem? item = sender.MenuItems.OfType().FirstOrDefault(x => (x.Content as TextBlock)?.Text == (args.InvokedItem as TextBlock)?.Text); + item ??= sender.FooterMenuItems.OfType().FirstOrDefault(x => (x.Content as TextBlock)?.Text == (args.InvokedItem as TextBlock)?.Text); if (item == null) return; #nullable restore @@ -1554,15 +1596,26 @@ private void NavigationPanelOpening_Event(NavigationView sender, object args) GridBG_Icon.Margin = curMargin; IsTitleIconForceShow = true; ToggleTitleIcon(false); + + NavViewPaneBackgroundHoverArea.Width = NavigationViewControl.OpenPaneLength; } - private void NavigationPanelClosing_Event(NavigationView sender, NavigationViewPaneClosingEventArgs args) + private async void NavigationPanelClosing_Event(NavigationView sender, NavigationViewPaneClosingEventArgs args) { Thickness curMargin = GridBG_Icon.Margin; curMargin.Left = 58; GridBG_Icon.Margin = curMargin; IsTitleIconForceShow = false; ToggleTitleIcon(true); + + NavViewPaneBackgroundHoverArea.Width = NavViewPaneBackground.Width; + + await Task.Delay(200); + if (!IsCursorInNavBarHoverArea) + { + NavViewPaneBackground.Opacity = 0; + NavViewPaneBackground.Translation = new System.Numerics.Vector3(-48, 0, 0); + } } #endregion diff --git a/CollapseLauncher/XAMLs/MainApp/MainWindow.xaml b/CollapseLauncher/XAMLs/MainApp/MainWindow.xaml index f5e05593d..5684b2678 100644 --- a/CollapseLauncher/XAMLs/MainApp/MainWindow.xaml +++ b/CollapseLauncher/XAMLs/MainApp/MainWindow.xaml @@ -163,54 +163,75 @@ Grid.Row="0"> - + + + + + + + + + + + diff --git a/CollapseLauncher/XAMLs/MainApp/MainWindow.xaml.cs b/CollapseLauncher/XAMLs/MainApp/MainWindow.xaml.cs index 81a41b0a9..42a956721 100644 --- a/CollapseLauncher/XAMLs/MainApp/MainWindow.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/MainWindow.xaml.cs @@ -1,4 +1,5 @@ using CollapseLauncher.AnimatedVisuals.Lottie; +using CollapseLauncher.Extension; using CollapseLauncher.Helper; using CollapseLauncher.Helper.Animation; using CollapseLauncher.Helper.Image; @@ -9,6 +10,7 @@ using Hi3Helper; using Hi3Helper.Shared.Region; using Microsoft.UI.Composition; +using Microsoft.UI.Input; using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; @@ -20,13 +22,13 @@ using static Hi3Helper.InvokeProp; using static Hi3Helper.Logger; using static Hi3Helper.Shared.Region.LauncherConfig; +// ReSharper disable RedundantExtendsListEntry namespace CollapseLauncher { public sealed partial class MainWindow : Window { private static bool _isForceDisableIntro; - public MainWindow() { } public void InitializeWindowProperties(bool startOOBE = false) { @@ -56,7 +58,8 @@ public void InitializeWindowProperties(bool startOOBE = false) catch (Exception ex) { LogWriteLine($"Failure while initializing window properties!!!\r\n{ex}", LogType.Error, true); - Console.ReadLine(); + //Console.ReadLine(); + throw; } } @@ -89,7 +92,6 @@ private async void RunIntroSequence() IntroAnimation.Stop(); } IntroAnimation.Source = null; - newIntro = null; GC.Collect(); GC.WaitForPendingFinalizers(); @@ -162,8 +164,13 @@ public void InitializeWindowSettings() WindowUtility.CurrentWindowIsResizable = false; WindowUtility.CurrentWindowIsMaximizable = false; - WindowUtility.CurrentAppWindow.TitleBar.ButtonBackgroundColor = new Color { A = 0, B = 0, G = 0, R = 0 }; - WindowUtility.CurrentAppWindow.TitleBar.ButtonInactiveBackgroundColor = new Color { A = 0, B = 0, G = 0, R = 0 }; + if (WindowUtility.CurrentAppWindow != null) + { + WindowUtility.CurrentAppWindow.TitleBar.ButtonBackgroundColor = + new Color { A = 0, B = 0, G = 0, R = 0 }; + WindowUtility.CurrentAppWindow.TitleBar.ButtonInactiveBackgroundColor = + new Color { A = 0, B = 0, G = 0, R = 0 }; + } // Hide system menu var controlsHwnd = FindWindowEx(WindowUtility.CurrentWindowPtr, 0, "ReunionWindowingCaptionControls", "ReunionCaptionControlsWindow"); @@ -270,5 +277,8 @@ private void IntroSequenceToggle_PointerExited(object sender, Microsoft.UI.Xaml. curCompositor.CreateScalarKeyFrameAnimation("Opacity", 0.25f) ); } + + private void SetWindowCaptionLoadedCursor(object sender, RoutedEventArgs e) + => (sender as UIElement)?.SetCursor(InputSystemCursor.Create(InputSystemCursorShape.Hand)); } } diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/CachesPage.xaml b/CollapseLauncher/XAMLs/MainApp/Pages/CachesPage.xaml index 92fb5d17e..42c261386 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/CachesPage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/Pages/CachesPage.xaml @@ -123,7 +123,6 @@ - @@ -182,6 +180,7 @@ + ToolTipService.ToolTip="{x:Bind helper:Locale.Lang._CachesPage.CachesBtn2QuickDesc}"> - - + + - + @@ -258,8 +259,7 @@ Text="{x:Bind helper:Locale.Lang._Misc.Cancel}" /> - FetchDataIntegrityURL(PresetConfig Profile) RegionResourceProp _Entry; - _Entry = await FallbackCDNUtil.DownloadAsJSONType(Profile.LauncherResourceURL, InternalAppJSONContext.Default, tokenSource.Token); + _Entry = await FallbackCDNUtil.DownloadAsJSONType(Profile.LauncherResourceURL, InternalAppJSONContext.Default.RegionResourceProp, tokenSource.Token); GameVersion = _Entry.data?.game?.latest?.version; return _RepoList[GameVersion ?? throw new InvalidOperationException()]; diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/GenshinGameSettingsPage.xaml b/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/GenshinGameSettingsPage.xaml index 1018f0351..c00b2a2bd 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/GenshinGameSettingsPage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/GenshinGameSettingsPage.xaml @@ -91,7 +91,6 @@ Height="24" Margin="16,0,8,0" Padding="0" - HorizontalAlignment="Left" CornerRadius="4" Style="{ThemeResource AcrylicButtonStyle}"> @@ -268,15 +267,15 @@ - + - + @@ -314,7 +313,6 @@ MaxWidth="514" Margin="0,8" HorizontalAlignment="Center" - IsThumbToolTipEnabled="False" Maximum="2000.0" Minimum="300.0" StepFrequency="50" @@ -344,7 +342,6 @@ MinWidth="350" Margin="0,8" HorizontalAlignment="Center" - IsThumbToolTipEnabled="False" Maximum="500.0" Minimum="100.0" StepFrequency="5" @@ -371,7 +368,6 @@ MinWidth="350" Margin="0,8,0,0" HorizontalAlignment="Center" - IsThumbToolTipEnabled="False" Maximum="550.0" Minimum="150.0" StepFrequency="5" @@ -408,7 +404,6 @@ (); + if (childGrid == null) return; + + // Bind the QR properties + ImageEx.ImageEx? qrImageInstance = childGrid.FindChild(); + qrImageInstance?.BindProperty(ImageExBase.SourceProperty, dataBind, "QrImg"); + + TextBlock? textBlockInstance = childGrid.FindChild(); + textBlockInstance?.BindProperty(TextBlock.TextProperty, dataBind, "QrTitle"); + textBlockInstance?.BindProperty(VisibilityProperty, dataBind, "IsHasQrDescription", HomePageExtension.BooleanVisibilityConverter); + } + + private static void BindSocialMediaLinks(Panel parentPanel, LauncherGameNewsSocialMedia dataBind) + { + // Bind visibility if dataBind has Links + parentPanel.BindProperty(VisibilityProperty, dataBind, "IsHasLinks", HomePageExtension.BooleanVisibilityConverter); + + // Find the ItemsControl + ItemsControl? itemsControl = parentPanel.FindChild(); + if (itemsControl == null) return; + + // If dataBind has links, then assign the ItemsSource + if (dataBind.IsHasLinks) + { + itemsControl.ItemsSource = dataBind.QrLinks; + } + } + + private static void BindSocialMediaDescription(TextBlock parentPanel, LauncherGameNewsSocialMedia dataBind) + { + // Bind visibility if dataBind has Description + parentPanel.BindProperty(VisibilityProperty, dataBind, "IsHasDescription", HomePageExtension.BooleanVisibilityConverter); + + // Bind description text + parentPanel.BindProperty(TextBlock.TextProperty, dataBind, "Title"); + } + } +} diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml index 09f68b11e..ce0143dcb 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml @@ -5,19 +5,18 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:animatedvisuals="using:Microsoft.UI.Xaml.Controls.AnimatedVisuals" - xmlns:conv="using:CollapseLauncher.Pages" xmlns:customcontrol="using:CollapseLauncher.CustomControls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:extension="using:CollapseLauncher.Extension" xmlns:helper="using:Hi3Helper" xmlns:imageex="using:ImageEx" xmlns:local="using:CollapseLauncher" - xmlns:localPage="using:CollapseLauncher.Pages" xmlns:localStatic="using:CollapseLauncher.Statics" xmlns:localWindowSize="using:CollapseLauncher.WindowSize" xmlns:lottie="using:CollapseLauncher.AnimatedVisuals.Lottie" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:s="using:CommunityToolkit.WinUI" + xmlns:sophonTypes="using:CollapseLauncher.Helper.LauncherApiLoader.Sophon" x:Name="HomePage_Page" x:FieldModifier="public" Unloaded="Page_Unloaded" @@ -29,7 +28,6 @@ CastTo="{x:Bind ImageEventImgShadow}" Opacity="0.3" Offset="0,4,0" /> - - - - + - - - @@ -442,7 +440,7 @@ - + @@ -588,7 +592,6 @@ - + + + + + - + + Tag="{x:Bind CarouselUrl}" + ToolTipService.ToolTip="{x:Bind CarouselTitle}" /> - - - - + + + + + + + + + + \ No newline at end of file diff --git a/CollapseLauncher/XAMLs/Updater/Classes/Updater.cs b/CollapseLauncher/XAMLs/Updater/Classes/Updater.cs index 91cbbd0d7..d67d1a521 100644 --- a/CollapseLauncher/XAMLs/Updater/Classes/Updater.cs +++ b/CollapseLauncher/XAMLs/Updater/Classes/Updater.cs @@ -3,12 +3,21 @@ using Hi3Helper; using Hi3Helper.Http; using Hi3Helper.Http.Legacy; +#if !USEVELOPACK using Squirrel; using Squirrel.Sources; +#else +using Microsoft.Extensions.Logging; +using Velopack; +using Velopack.Locators; +using Velopack.Sources; +#endif using System; using System.Diagnostics; using System.IO; +#if !USEVELOPACK using System.Linq; +#endif using System.Net; using System.Net.Http; using System.Threading; @@ -16,6 +25,8 @@ using static Hi3Helper.Data.ConverterTool; using static Hi3Helper.Locale; using static Hi3Helper.Shared.Region.LauncherConfig; +using Hi3Helper.Shared.Region; +using Hi3Helper.Data; namespace CollapseLauncher; @@ -27,6 +38,10 @@ public class Updater : IDisposable private Stopwatch UpdateStopwatch; private UpdateManager UpdateManager; private IFileDownloader UpdateDownloader; +#if USEVELOPACK + private ILogger UpdateManagerLogger; + private VelopackAsset VelopackVersionToUpdate; +#endif private GameVersion NewVersionTag; // ReSharper disable once RedundantDefaultMemberInitializer @@ -46,9 +61,34 @@ public class Updater : IDisposable public Updater(string ChannelName) { this.ChannelName = ChannelName; - ChannelURL = CombineURLFromString(FallbackCDNUtil.GetPreferredCDN().URLPrefix, "squirrel", this.ChannelName); + ChannelURL = CombineURLFromString(FallbackCDNUtil.GetPreferredCDN().URLPrefix, +#if USEVELOPACK + "velopack", +#else + "squirrel", +#endif + this.ChannelName + ); UpdateDownloader = new UpdateManagerHttpAdapter(); +#if USEVELOPACK + UpdateManagerLogger = ILoggerHelper.CreateCollapseILogger(); + VelopackLocator updateManagerLocator = VelopackLocator.GetDefault(UpdateManagerLogger); + UpdateOptions updateManagerOptions = new UpdateOptions + { + AllowVersionDowngrade = true, + ExplicitChannel = this.ChannelName + }; + + // Initialize update manager source + IUpdateSource updateSource = new SimpleWebSource(ChannelURL, UpdateDownloader); + UpdateManager = new UpdateManager( + updateSource, + updateManagerOptions, + UpdateManagerLogger, + updateManagerLocator); +#else UpdateManager = new UpdateManager(ChannelURL, null, null, UpdateDownloader); +#endif UpdateStopwatch = Stopwatch.StartNew(); Status = new UpdaterStatus(); Progress = new UpdaterProgress(UpdateStopwatch, 0, 100); @@ -61,19 +101,33 @@ public Updater(string ChannelName) public void Dispose() { +#if !USEVELOPACK UpdateManager?.Dispose(); +#endif } public async Task StartCheck() { +#if !USEVELOPACK return await UpdateManager.CheckForUpdate(); +#else + return await UpdateManager.CheckForUpdatesAsync(); +#endif } public bool IsUpdateAvailable(UpdateInfo info) { +#if !USEVELOPACK if (!info.ReleasesToApply.Any()) +#else + if (info.DeltasToTarget.Length != 0) +#endif { +#if !USEVELOPACK NewVersionTag = new GameVersion(info.FutureReleaseEntry.Version.Version); +#else + NewVersionTag = new GameVersion(info.TargetFullRelease.Version.ToString()); +#endif return DoesLatestVersionExist(NewVersionTag.VersionString); } @@ -91,9 +145,17 @@ public async Task StartUpdate(UpdateInfo UpdateInfo, CancellationToken tok try { +#if !USEVELOPACK if (!UpdateInfo.ReleasesToApply.Any()) +#else + if (UpdateInfo.DeltasToTarget.Length != 0) +#endif { +#if !USEVELOPACK NewVersionTag = new GameVersion(UpdateInfo.FutureReleaseEntry.Version.Version); +#else + NewVersionTag = new GameVersion(UpdateInfo.TargetFullRelease.Version.ToString()); +#endif if (DoesLatestVersionExist(NewVersionTag.VersionString)) { Progress = new UpdaterProgress(UpdateStopwatch, 100, 100); @@ -109,23 +171,38 @@ public async Task StartUpdate(UpdateInfo UpdateInfo, CancellationToken tok return false; } +#if !USEVELOPACK NewVersionTag = new GameVersion(UpdateInfo.ReleasesToApply.FirstOrDefault()!.Version.Version); +#else + NewVersionTag = new GameVersion(UpdateInfo.TargetFullRelease.Version.ToString()); +#endif + +#if !USEVELOPACK + await UpdateManager.DownloadReleases(UpdateInfo.ReleasesToApply, InvokeDownloadUpdateProgress); + + await UpdateManager.ApplyReleases(UpdateInfo, InvokeApplyUpdateProgress); +#else + await UpdateManager.DownloadUpdatesAsync(UpdateInfo, InvokeDownloadUpdateProgress, false, token); + VelopackVersionToUpdate = UpdateInfo.TargetFullRelease; +#endif + + void InvokeDownloadUpdateProgress(int progress) + { + Progress = new UpdaterProgress(UpdateStopwatch, progress +#if !USEVELOPACK + / 2 +#endif + , 100); + UpdateProgress(); + } - await UpdateManager.DownloadReleases(UpdateInfo.ReleasesToApply, (progress) => - { - Progress = - new - UpdaterProgress(UpdateStopwatch, - progress / 2, 100); - UpdateProgress(); - }); - - await UpdateManager.ApplyReleases(UpdateInfo, (progress) => - { - Progress = new UpdaterProgress(UpdateStopwatch, - progress / 2 + 50, 100); - UpdateProgress(); - }); +#if !USEVELOPACK + void InvokeApplyUpdateProgress(int progress) + { + Progress = new UpdaterProgress(UpdateStopwatch, progress / 2 + 50, 100); + UpdateProgress(); + } +#endif } catch (Exception ex) { @@ -150,9 +227,11 @@ private async Task StartLegacyUpdate() UpdateStopwatch = Stopwatch.StartNew(); - AppUpdateVersionProp updateInfo = await FallbackCDNUtil - .DownloadAsJSONType($"{ChannelName.ToLower()}/fileindex.json", - InternalAppJSONContext.Default, default); + CDNURLProperty preferredCdn = FallbackCDNUtil.GetPreferredCDN(); + string updateFileIndexUrl = ConverterTool.CombineURLFromString(preferredCdn.URLPrefix, ChannelName.ToLower(), "fileindex.json"); + + AppUpdateVersionProp updateInfo = await FallbackCDNUtil.DownloadAsJSONType(updateFileIndexUrl, + InternalAppJSONContext.Default.AppUpdateVersionProp, default)!; GameVersion? gameVersion = updateInfo!.Version; @@ -179,12 +258,40 @@ private void FallbackCDNUtil_DownloadProgress(object sender, DownloadEvent e) private bool DoesLatestVersionExist(string versionString) { + // Check legacy version first var filePath = Path.Combine(AppFolder, $"..\\app-{versionString}\\{Path.GetFileName(AppExecutablePath)}"); + if (File.Exists(filePath)) return true; + + // If none does not exist, then check the latest version + filePath = Path.Combine(AppFolder, $"..\\current\\{Path.GetFileName(AppExecutablePath)}"); + if (!Version.TryParse(versionString, out Version currentVersion)) + { + Logger.LogWriteLine($"[Updater::DoesLatestVersionExist] versionString is not valid! {versionString}", LogType.Error, true); + return false; + } + + // If the Velopack current folder doesn't exist, then return false + if (!File.Exists(filePath)) + { + return false; + } + + // Try get the version info and if the version info is null, return false + FileVersionInfo toCheckVersionInfo = FileVersionInfo.GetVersionInfo(filePath); - return File.Exists(filePath); + // Otherwise, try compare the version info. + if (!Version.TryParse(toCheckVersionInfo.ProductVersion, out Version latestVersion)) + { + Logger.LogWriteLine($"[Updater::DoesLatestVersionExist] toCheckVersionInfo.ProductVersion is not valid! {versionString}", LogType.Error, true); + return false; + } + + // Try compare. If latestVersion is more or equal to current version, return true. + // Otherwise, return false. + return latestVersion >= currentVersion; } - public async Task FinishUpdate(bool NoSuicide = false) + public async Task FinishUpdate(bool noSuicide = false) { var newVerTagPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "AppData", "LocalLow", "CollapseLauncher", "_NewVer"); @@ -198,8 +305,8 @@ public async Task FinishUpdate(bool NoSuicide = false) Progress = new UpdaterProgress(UpdateStopwatch, 100, 100); UpdateProgress(); - File.WriteAllText(newVerTagPath, NewVersionTag.VersionString); - File.WriteAllText(needInnoLogUpdatePath, NewVersionTag.VersionString); + await File.WriteAllTextAsync(newVerTagPath, NewVersionTag.VersionString); + await File.WriteAllTextAsync(needInnoLogUpdatePath, NewVersionTag.VersionString); if (IsUseLegacyDownload) { @@ -207,17 +314,28 @@ public async Task FinishUpdate(bool NoSuicide = false) return; } - if (!NoSuicide) + if (!noSuicide) await Suicide(); } private async Task Suicide() { await Task.Delay(3000); +#if !USEVELOPACK UpdateManager.RestartApp(); +#else + if (VelopackVersionToUpdate != null) + { + string currentAppPath = Path.Combine(Path.GetDirectoryName(AppFolder) ?? string.Empty, "current"); + if (!Directory.Exists(currentAppPath)) + Directory.CreateDirectory(currentAppPath); + + UpdateManager.ApplyUpdatesAndRestart(VelopackVersionToUpdate); + } +#endif } - private void SuicideLegacy() + private static void SuicideLegacy() { var applyUpdate = new Process() { diff --git a/CollapseLauncher/packages.lock.json b/CollapseLauncher/packages.lock.json index 4eee338ee..1c341f4fe 100644 --- a/CollapseLauncher/packages.lock.json +++ b/CollapseLauncher/packages.lock.json @@ -1,88 +1,59 @@ { "version": 1, "dependencies": { - "net8.0-windows10.0.22621": { - "Clowd.Squirrel": { - "type": "Direct", - "requested": "[2.11.1, )", - "resolved": "2.11.1", - "contentHash": "tregtapTyVXjwhgyc4llUNh8HX3008yrglakUWQocgtGf2bOBHcibgixakkb2yB1FrM/gPDpxykg4sWtFzjpVg==" - }, + "net9.0-windows10.0.22621": { "CommunityToolkit.Common": { "type": "Direct", - "requested": "[8.3.1, )", - "resolved": "8.3.1", - "contentHash": "r8jWhfnwSfn7/xPjh3Aom6gAGkENUMrr+FmZJOzOvRWBgXJ/latl8b8iCx1F+GHiS/0f5zK4zWZM18P0DgNNiw==" + "requested": "[8.3.2, )", + "resolved": "8.3.2", + "contentHash": "DFk1MUDzCUZHWFGl33HQkD1XtHHKmgfnjJ6sD2JICDc6et4DaCxf6GBPCzAZ7jcgyy/BdUDk0DUztu1K+61Q6w==" }, "CommunityToolkit.Mvvm": { "type": "Direct", - "requested": "[8.3.1, )", - "resolved": "8.3.1", - "contentHash": "Fuqjp5uYzHEdrRxsZlY0EqvFX9pF2v0cZN+zfso44lVLI3wZa8Nb9jdcBoXbHxiRSU3hEDhHUqh2fM8znzodzg==" + "requested": "[8.3.2, )", + "resolved": "8.3.2", + "contentHash": "m8EolE1A0Updj68WTsZSGI6VWb6mUqHPh7QFo0kt7+JPhYMNXRS1ch8TS/oITAdcxTLrwMOp3ku1KjeG1/Zdpg==" }, "CommunityToolkit.WinUI.Behaviors": { "type": "Direct", - "requested": "[8.1.240821, )", - "resolved": "8.1.240821", - "contentHash": "nc6MZdYp7ee1f8Qu8JO1LtbOuzAMtm79QN9073YMLTLZliCWVijfW+OFIVRHsFcCxpv6Ep4ii63x/OjXZIuWAQ==", + "requested": "[8.1.240916, )", + "resolved": "8.1.240916", + "contentHash": "HJ/fJmxXj/KmrPCtDUmkKUf0govYesCuYFEKtuTigzcFyAwiNIZzXRgNxMU5UBrdOYYt+9zFY7ScczEzj4FB9w==", "dependencies": { - "CommunityToolkit.WinUI.Animations": "8.1.240821", - "CommunityToolkit.WinUI.Extensions": "8.1.240821", + "CommunityToolkit.WinUI.Animations": "8.1.240916", + "CommunityToolkit.WinUI.Extensions": "8.1.240916", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", "Microsoft.WindowsAppSDK": "1.5.240311000", "Microsoft.Xaml.Behaviors.WinUI.Managed": "2.0.9" } }, - "CommunityToolkit.WinUI.Controls.ImageCropper": { - "type": "Direct", - "requested": "[8.1.240821, )", - "resolved": "8.1.240821", - "contentHash": "nafCnTNqXbI47xJRGsQpli0dBUvQ93O6c9AEUfuBdxCYcA6uwdNUiBW9Gq7piXyQo6yjM8G5pRyprxyVi+nwaQ==", - "dependencies": { - "CommunityToolkit.WinUI.Extensions": "8.1.240821", - "Microsoft.Graphics.Win2D": "1.0.0.30", - "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", - "Microsoft.WindowsAppSDK": "1.5.240311000" - } - }, "CommunityToolkit.WinUI.Controls.Primitives": { "type": "Direct", - "requested": "[8.1.240821, )", - "resolved": "8.1.240821", - "contentHash": "upwSObO113gfSMjMGTYGZs2zjK8smtkzaH1bSKYAau4vk4gVu3Ildnk8dMFfp5lkZKs+6xON2jbj/gfUFC2Xiw==", + "requested": "[8.1.240916, )", + "resolved": "8.1.240916", + "contentHash": "ULEQhMGFX9YEwq9lF7XdiWAPtrXntVFqvt7HdGfeYjU6+JdW4zO/38dCMCuKJES7cG5sWWNrsntsOWUONFfVaA==", "dependencies": { - "CommunityToolkit.WinUI.Extensions": "8.1.240821", - "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", - "Microsoft.WindowsAppSDK": "1.5.240311000" - } - }, - "CommunityToolkit.WinUI.Controls.SettingsControls": { - "type": "Direct", - "requested": "[8.1.240821, )", - "resolved": "8.1.240821", - "contentHash": "a5eEsR1wQSuVUsQALEpZiq6dNrTuw0POQCD6NXMBID+mY0g672Pg8OPG4MyTbwOgj+1NPua51WM22WFVGteRlQ==", - "dependencies": { - "CommunityToolkit.WinUI.Triggers": "8.1.240821", + "CommunityToolkit.WinUI.Extensions": "8.1.240916", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", "Microsoft.WindowsAppSDK": "1.5.240311000" } }, "CommunityToolkit.WinUI.Controls.Sizers": { "type": "Direct", - "requested": "[8.1.240821, )", - "resolved": "8.1.240821", - "contentHash": "qnJNGocX/XoaT1yADeG75Hb4JOIh5VcmO3j6EeE1iY6gq1+Z4LVBREVC6Dz7/notBPlEErysMgbJVKURimRKsw==", + "requested": "[8.1.240916, )", + "resolved": "8.1.240916", + "contentHash": "FD6ccqMnQc1urdEmTupd59mGnTOxKqf9UvjF2dheHz61F5hlj3y+Ngq/H9jgWw5VwoDV+AV4J40d/BjmCvBLYQ==", "dependencies": { - "CommunityToolkit.WinUI.Extensions": "8.1.240821", + "CommunityToolkit.WinUI.Extensions": "8.1.240916", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", "Microsoft.WindowsAppSDK": "1.5.240311000" } }, "CommunityToolkit.WinUI.Converters": { "type": "Direct", - "requested": "[8.1.240821, )", - "resolved": "8.1.240821", - "contentHash": "Fz8uewTFTrtj3qsPrgRQ2Ld7efjdGb7EVYLxGoNuii9pS4SiKqGC2KEUJqZ8oQlXX4eUFiO5pqOeE5SC/fQoPw==", + "requested": "[8.1.240916, )", + "resolved": "8.1.240916", + "contentHash": "UELI/UNyc9lf8NqKfuBZJGnrpUgXqqRZ3L/fP8RTRP/t8DWDr9JTdhgPUbXaF/Z16DVPZRId9c+nRfVINbme4Q==", "dependencies": { "CommunityToolkit.Common": "8.2.1", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", @@ -91,9 +62,9 @@ }, "CommunityToolkit.WinUI.Extensions": { "type": "Direct", - "requested": "[8.1.240821, )", - "resolved": "8.1.240821", - "contentHash": "mYVvEWQYF6keFQ0X2yEcTFNCarhw9fTSqMDFM00/fxWmGG2RvyyC0t8k/BeqAuV9azM9ZkRNTCqRt36usUsdFg==", + "requested": "[8.1.240916, )", + "resolved": "8.1.240916", + "contentHash": "y8FEzoy5HF3dBkxe/pU87hlh0QACtGZhnv881x2SFq9fvDjukLQz6VpoOkjxSZjEILmYu1NoI/tYjqXlETRGlw==", "dependencies": { "CommunityToolkit.Common": "8.2.1", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", @@ -102,12 +73,12 @@ }, "CommunityToolkit.WinUI.Media": { "type": "Direct", - "requested": "[8.1.240821, )", - "resolved": "8.1.240821", - "contentHash": "BriTsGK69Kw71OLCbVCTZXe5hEKk1xfahZccl7PTuQYg6wo7k5mQDEOKnVtJqynW2Yd/IQznU9qEJ+t7jizWOA==", + "requested": "[8.1.240916, )", + "resolved": "8.1.240916", + "contentHash": "ghd4u81dNf5F5knVkww9QDUy03L05TvMHPlUXKOVFER0bl8A3K6I0eYi0bUNtR8VTB7giPy8DFh50t3SJjswrA==", "dependencies": { - "CommunityToolkit.WinUI.Animations": "8.1.240821", - "CommunityToolkit.WinUI.Extensions": "8.1.240821", + "CommunityToolkit.WinUI.Animations": "8.1.240916", + "CommunityToolkit.WinUI.Extensions": "8.1.240916", "Microsoft.Graphics.Win2D": "1.1.1", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", "Microsoft.WindowsAppSDK": "1.5.240311000" @@ -122,16 +93,6 @@ "ThisAssembly.Constants": "1.4.1" } }, - "H.NotifyIcon.WinUI": { - "type": "Direct", - "requested": "[2.1.3, )", - "resolved": "2.1.3", - "contentHash": "1tAYbvV2vyQ0lXKrMmNvpIiP5tXZibtyXCqlmH2Wjcc3i/W/EatihtVATpeuWZpxM6RA9T1H5pTZhT8+5y7wcQ==", - "dependencies": { - "H.NotifyIcon": "2.1.3", - "Microsoft.WindowsAppSDK": "1.5.240627000" - } - }, "HtmlAgilityPack": { "type": "Direct", "requested": "[1.11.65, )", @@ -144,6 +105,12 @@ "resolved": "0.37.0", "contentHash": "J7Mt1QjKijE7aAK81u5nt4SYPYEvt1odfr4ddMvyOscyNT3PoGfHKKTBB5VBSXJrWZWhfJlcG7hHBLiqawg+nw==" }, + "Microsoft.DotNet.ILCompiler": { + "type": "Direct", + "requested": "[9.0.0-rc.1.24431.7, )", + "resolved": "9.0.0-rc.1.24431.7", + "contentHash": "FN38PfBwjKY4D753sz6GDSnvtHU9UUPb9fNAcPwImOn2j8SyUFZMnVmMVwnYX4dlDB9XL98m4g6szsjIx5UWgg==" + }, "Microsoft.Graphics.Win2D": { "type": "Direct", "requested": "[1.2.1-experimental2, )", @@ -153,11 +120,11 @@ "Microsoft.WindowsAppSDK": "1.6.240531000-experimental1" } }, - "Microsoft.NETCore.Platforms": { + "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.0-preview.7.23375.6, )", - "resolved": "8.0.0-preview.7.23375.6", - "contentHash": "ym5B/aglUc4K5kkiBOOgsm7EETjenJBbMchAHvcXpfFPmrdTrhnLKC1Tvx5Qnz5kHvhloyCNTpGQvWIpxvINHg==" + "requested": "[9.0.0-rc.1.24431.7, )", + "resolved": "9.0.0-rc.1.24431.7", + "contentHash": "B8jHIIszeUdDY+O4G1/xPOuhrriwsv9Z8/u3XSOAqc8Z0mQCeLBKg2oLGElDZeRF8wWXDGklAKY3f0IHopyXkQ==" }, "Microsoft.NETCore.Targets": { "type": "Direct", @@ -165,6 +132,12 @@ "resolved": "6.0.0-preview.4.21253.7", "contentHash": "ipvxo/6FRdrmcsAtCGI9QPeaZZSyeCk9LiSVIJ4AN36IZQG7KFtk9ZbbYgvy4wzCzHkbBmTWaf2ESBqrYEtCRg==" }, + "Microsoft.Web.WebView2": { + "type": "Direct", + "requested": "[1.0.2783-prerelease, )", + "resolved": "1.0.2783-prerelease", + "contentHash": "sPxd49RLVnOyzplXTvdU9z6NIx0t56MridYoZhw8BOBZt564Ufi168Wfg5G0rIc5EKWmrGihLeZ1OfmjQiqWTw==" + }, "Microsoft.Windows.CsWinRT": { "type": "Direct", "requested": "[2.1.3, )", @@ -173,9 +146,9 @@ }, "Microsoft.Windows.SDK.BuildTools": { "type": "Direct", - "requested": "[10.0.26100.1, )", - "resolved": "10.0.26100.1", - "contentHash": "ju02pHSp1LDEIvFgLIDbCbXGPpTTrXJrH90HxC6neclpbRpdS8RxeRbYOzPQFByUPCgT/Egbz4s/cpNMamW1Sg==" + "requested": "[10.0.26100.1742, )", + "resolved": "10.0.26100.1742", + "contentHash": "ypcHjr4KEi6xQhgClnbXoANHcyyX/QsC4Rky4igs6M4GiDa+weegPo8JuV/VMxqrZCV4zlqDsp2krgkN7ReAAg==" }, "Microsoft.WindowsAppSDK": { "type": "Direct", @@ -217,6 +190,12 @@ "resolved": "2.0.1", "contentHash": "Xvhd0IPCKhdYppa9RhDr4fkwhoMMWfahwCfyVtRgt6TRlbUw0gOmSjJR/+L+/s0QNhLTK93C+2pSlrkDgsoY2w==" }, + "runtime.win-x64.Microsoft.DotNet.ILCompiler": { + "type": "Direct", + "requested": "[9.0.0-rc.1.24431.7, )", + "resolved": "9.0.0-rc.1.24431.7", + "contentHash": "hOXKEHhsK/sWwrMEL1Yrfj0Mw1WnsBsi8OoEEEmS4hgOxo39Kic0aap2nKm9eWRl8Fe5S+K10a2nVkt/T2AF7Q==" + }, "SharpCompress": { "type": "Direct", "requested": "[0.38.0, )", @@ -251,46 +230,6 @@ "System.CommandLine": "2.0.0-beta4.22272.1" } }, - "System.Diagnostics.EventLog": { - "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "fdYxcRjQqTTacKId/2IECojlDSFvp7LP5N78+0z/xH7v/Tuw5ZAxu23Y6PTCRinqyu2ePx+Gn1098NC6jM6d+A==" - }, - "System.Net.Http": { - "type": "Direct", - "requested": "[4.3.4, )", - "resolved": "4.3.4", - "contentHash": "aOa2d51SEbmM+H+Csw7yJOuNZoHkrP2XnAurye5HWYgGVVU54YZDvsLUYRv6h18X3sPnjNCANmN7ZhIPiqMcjA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.1", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.DiagnosticSource": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Extensions": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Security.Cryptography.X509Certificates": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Http": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2" - } - }, "System.Security.AccessControl": { "type": "Direct", "requested": "[6.0.1, )", @@ -299,95 +238,66 @@ }, "System.Security.Cryptography.ProtectedData": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "+TUFINV2q2ifyXauQXRwy4CiBhqvDEDZeVJU7qfxya4aRYOKzVBpN+4acx25VcPB9ywUN6C0n8drWl110PhZEg==" + "requested": "[9.0.0-rc.1.24431.7, )", + "resolved": "9.0.0-rc.1.24431.7", + "contentHash": "oPjLCPnuxKQ8lXyY4PPgbwuTu1qpeZGLbh4/c+o1ZcLXuLJlpKYOu9HmqvWD9VpReAFOp72a1GYRSsMmJKZ9aQ==" }, "System.Text.Encoding.CodePages": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "OZIsVplFGaVY90G2SbpgU7EnCoOO5pw1t4ic21dBF3/1omrJFpAGoNAVpPyMVOC90/hvgkGG3VFqR13YgZMQfg==" + "requested": "[9.0.0-rc.1.24431.7, )", + "resolved": "9.0.0-rc.1.24431.7", + "contentHash": "RRwyNcn8Ph+P3gPGouH+6x1N8l9tc5h3+54zUt6Bhet1AcjypZ51mNMYmPTOz056rxCNoPUNAyDCkk9AsAnUTw==" }, "System.Text.Json": { "type": "Direct", - "requested": "[8.0.4, )", - "resolved": "8.0.4", - "contentHash": "bAkhgDJ88XTsqczoxEMliSrpijKZHhbJQldhAmObj/RbrN3sU5dcokuXmWJWsdQAhiMJ9bTayWsL1C9fbbCRhw==", - "dependencies": { - "System.Text.Encodings.Web": "8.0.0" - } + "requested": "[9.0.0-rc.1.24431.7, )", + "resolved": "9.0.0-rc.1.24431.7", + "contentHash": "f6EHMnCjPQbwIftWreac1RsDolPpRT47lBfdHM/7Y/phtdewEtn3+XKWUVZ5gpB04tpab5y/XaZRkq28p3xzFQ==" }, - "System.Text.RegularExpressions": { + "Velopack": { "type": "Direct", - "requested": "[4.3.1, )", - "resolved": "4.3.1", - "contentHash": "N0kNRrWe4+nXOWlpLT4LAY5brb8caNFlUuIRpraCVMDLYutKkol1aV079rQjLuSxKMJT2SpBQsYX9xbcTMmzwg==", + "requested": "[0.0.626, )", + "resolved": "0.0.626", + "contentHash": "el6qqNyJM3GA11j6SFZgcQJ/AFg7ocQQSu+Tk/+OIzejZlROGLK+RDWPb4tcp9E9ug9aT4HV0m35zISMSC++/Q==", "dependencies": { - "System.Runtime": "4.3.1" - } - }, - "TaskScheduler": { - "type": "Direct", - "requested": "[2.11.0, )", - "resolved": "2.11.0", - "contentHash": "p9wH58XSNIyUtO7PIFAEldaKUzpYmlj+YWAfnUqBKnGxIZRY51I9BrsBGJijUVwlxrgmLLPUigRIv2ZTD4uPJA==", - "dependencies": { - "Microsoft.Win32.Registry": "5.0.0", - "System.Diagnostics.EventLog": "8.0.0", - "System.Security.AccessControl": "6.0.1" + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "NuGet.Versioning": "6.10.1" } }, "CommunityToolkit.WinUI.Animations": { "type": "Transitive", - "resolved": "8.1.240821", - "contentHash": "ehqSIkYUE8bauxUTp/q3aI6VrX7os/8Bc+8+FFnpErI0u/aeBI6CYZe+WgiLGefxy8/hNHPrtJBbczah7o0y6g==", + "resolved": "8.1.240916", + "contentHash": "jHYltimsKSSrYAij1TJC4VXzWZ6RaKCOWzkdDPenkjAcvE6lhSdX68eH6nNRIwZZC9lMpDy2Fba4uAJ1vMt9Ew==", "dependencies": { - "CommunityToolkit.WinUI.Extensions": "8.1.240821", + "CommunityToolkit.WinUI.Extensions": "8.1.240916", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", "Microsoft.WindowsAppSDK": "1.5.240311000" } }, "CommunityToolkit.WinUI.Helpers": { "type": "Transitive", - "resolved": "8.1.240821", - "contentHash": "/h4p1+5Hv9j61mp++IfiWxVihq0yb6maRcIRrehzB4YTXFU60P3pZwQShNytTVnGMDRm6AgupbtnBoG+38PBnA==", + "resolved": "8.1.240916", + "contentHash": "Q2eipdR9ntktYT1b3Dn927lZjdwQsmSU9cq6Pf2GwXW+R2IwkbGn7uYKJhmr5athOT6mnNgc7pMmfwFHIsFPLQ==", "dependencies": { - "CommunityToolkit.WinUI.Extensions": "8.1.240821", + "CommunityToolkit.WinUI.Extensions": "8.1.240916", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", "Microsoft.WindowsAppSDK": "1.5.240311000" } }, "CommunityToolkit.WinUI.Triggers": { "type": "Transitive", - "resolved": "8.1.240821", - "contentHash": "D5Xr3MsJX1n3iIi7SnAW29Nec6K1rQDSfuFNZGHrdIsqnVw5Tu8ITmWjv3oWQAdEWql7vcyY/Hezb7l4uFXP0w==", + "resolved": "8.1.240916", + "contentHash": "4XUtIdhjTj9mTWgqUvnVIa3Ox+CW+fBks94DUOndAuxtHlDf9Oc8EguHXp4W8qxp7x4tUeYDoRPmcsnXgjt3Sw==", "dependencies": { - "CommunityToolkit.WinUI.Helpers": "8.1.240821", + "CommunityToolkit.WinUI.Helpers": "8.1.240916", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", "Microsoft.WindowsAppSDK": "1.5.240311000" } }, "Google.Protobuf": { "type": "Transitive", - "resolved": "3.28.1", - "contentHash": "i4EN7Z+OUdoRBNiVMIG6CfMh6UowXiUx+BKgE+GHLbAX5ArSmpUTFUDwgRNwNfYdosl6GXuBlDiHCcXSHw43+A==" - }, - "H.GeneratedIcons.System.Drawing": { - "type": "Transitive", - "resolved": "2.1.3", - "contentHash": "GH6MR2Qc/yyNaCj1205v2J3jT4siDmg5W+jlbJzxDuMZZtX20pEIb/QBABDS+l9brHlwPs7WygboRNeMii40Qw==", - "dependencies": { - "System.Drawing.Common": "8.0.7" - } - }, - "H.NotifyIcon": { - "type": "Transitive", - "resolved": "2.1.3", - "contentHash": "aiCL1gjfKWh/jRlV4oDOycOqRaRj/C9E5cwHL2KCc+jre5vyUw5lHcHW6t4e/U3ssDmBKJgwhcgAW3JwMUP3ow==", - "dependencies": { - "H.GeneratedIcons.System.Drawing": "2.1.3" - } + "resolved": "3.28.2", + "contentHash": "Z86ZKAB+v1B/m0LTM+EVamvZlYw/g3VND3/Gs4M/+aDIxa2JE9YPKjDxTpf0gv2sh26hrve3eI03brxBmzn92g==" }, "Hi3Helper.ZstdNet": { "type": "Transitive", @@ -399,556 +309,41 @@ "resolved": "4.7.0", "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" }, - "Microsoft.Web.WebView2": { - "type": "Transitive", - "resolved": "1.0.2651.64", - "contentHash": "f5sc/vcAoTCTEW7Nqzp4galAuTRguZViw8ksn+Nx2uskEBPm0/ubzy6gVjvXS/P96jLS89C8T9I0hPc417xpNg==" - }, - "Microsoft.Win32.Registry": { - "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", - "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" - } - }, - "Microsoft.Win32.SystemEvents": { + "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", "resolved": "8.0.0", - "contentHash": "9opKRyOKMCi2xJ7Bj7kxtZ1r9vbzosMvRrdEhVhDz8j8MoBGgB+WmC94yH839NPH+BclAjtQ/pyagvi/8gDLkw==" - }, - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "7VSGO0URRKoMEAq0Sc9cRz8mb6zbyx/BZDEWhgPdzzpmFhkam3fJ1DAGWFXBI4nGlma+uPKpfuMQP5LXRnOH5g==" - }, - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "0oAaTAm6e2oVH+/Zttt0cuhGaePQYKII1dY8iaqP7CvOpVKgLybKRFvQjXR2LtxXOXTVPNv14j0ot8uV+HrUmw==" - }, - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "G24ibsCNi5Kbz0oXWynBoRgtGvsw5ZSVEWjv13/KiCAM8C6wz9zzcCniMeQFIkJ2tasjo2kXlvlBZhplL51kGg==" - }, - "runtime.native.System": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "runtime.native.System.Net.Http": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "runtime.native.System.Security.Cryptography.Apple": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==", - "dependencies": { - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0" - } - }, - "runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "QR1OwtwehHxSeQvZKXe+iSd+d3XZNkEcuWMFYa2i0aG1l+lR739HPicKMlTbJst3spmeekDVBUS7SeS26s4U/g==", - "dependencies": { - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2" - } - }, - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "I+GNKGg2xCHueRd1m9PzeEW7WLbNNLznmTuEi8/vZX71HudUbx1UTwlGkiwMri7JLl8hGaIAWnA/GONhu+LOyQ==" - }, - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "1Z3TAq1ytS1IBRtPXJvEUZdVsfWfeNEhBkbiOCGEl9wwAfsjP2lz3ZFDx5tq8p60/EqbS0HItG5piHuB71RjoA==" - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ==" - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "6mU/cVmmHtQiDXhnzUImxIcDL48GbTk+TsptXyJA+MIOG9LRjPoAQC/qBFB7X+UNyK86bmvGwC8t+M66wsYC8w==" - }, - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "vjwG0GGcTW/PPg6KVud8F9GLWYuAV1rrw1BKAqY0oh4jcUqg15oYF1+qkGR2x2ZHM4DQnWKQ7cJgYbfncz/lYg==" - }, - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "7KMFpTkHC/zoExs+PwP8jDCWcrK9H6L7soowT80CUx3e+nxP/AFnq0AQAW5W76z2WYbLAYCRyPfwYFG6zkvQRw==" - }, - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "xrlmRCnKZJLHxyyLIqkZjNXqgxnKdZxfItrPkjI+6pkRo5lHX8YvSZlWrSI5AVwLMi4HbNWP7064hcAWeZKp5w==" - }, - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "leXiwfiIkW7Gmn7cgnNcdtNAU70SjmKW3jxGj1iKHOvdn0zRWsgv/l2OJUO5zdGdiv2VRFnAsxxhDgMzofPdWg==" - }, - "System.Collections": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } + "contentHash": "cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg==" }, - "System.Collections.Concurrent": { + "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Diagnostics.Debug": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", + "resolved": "8.0.0", + "contentHash": "arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==", "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0" } }, - "System.Diagnostics.DiagnosticSource": { + "Microsoft.Win32.SystemEvents": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - } + "resolved": "9.0.0-rc.1.24431.7", + "contentHash": "IK3fwmfSCVArYWKjPUTEeD9nRXF1VENiFb4+hlqWuUxfP1hPVw41y/sOEdLZtRB+BVdgh6NaZjetrVhEuG4Iyg==" }, - "System.Diagnostics.Tracing": { + "NuGet.Versioning": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } + "resolved": "6.10.1", + "contentHash": "tovHZ3OlMVmsTdhv2z5nwnnhoA1ryhfJMyVQ9/+iv6d3h78fp230XaGy3K/iVcLwB50DdfNfIsitW97KSOWDFg==" }, "System.Drawing.Common": { "type": "Transitive", - "resolved": "8.0.8", - "contentHash": "4ZM1wvLjz9nVVExsfPAaSl/qOvU+QNedJL5rQ+2Wbow+iGeyO0e7XN07707rMBgaffEeeLrCZBwC0oHUuvRdPw==", + "resolved": "9.0.0-rc.1.24451.1", + "contentHash": "5TJtJLhJk7viKkkflNYN+V+Tl/qxoKrkmCTog2j0dzqamIY/ximaAWQfPznYOUR5852XxxvhZsQLd2LzjJFWUw==", "dependencies": { - "Microsoft.Win32.SystemEvents": "8.0.0" - } - }, - "System.Globalization": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Globalization.Calendars": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Globalization": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Globalization.Extensions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.InteropServices": "4.3.0" - } - }, - "System.IO": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.IO.FileSystem": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.IO.FileSystem.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", - "dependencies": { - "System.Runtime": "4.3.0" + "Microsoft.Win32.SystemEvents": "9.0.0-rc.1.24431.7" } }, "System.IO.Hashing": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "ne1843evDugl0md7Fjzy6QjJrzsjh46ZKbhf8GwBXb5f/gw97J4bxMs0NQKifDuThh/f0bZ0e62NPl1jzTuRqA==" - }, - "System.Linq": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - } - }, - "System.Net.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Reflection": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Resources.ResourceManager": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Globalization": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime": { - "type": "Transitive", - "resolved": "4.3.1", - "contentHash": "abhfv1dTK6NXOmu4bgHIONxHyEqFjW8HwXPmpY9gmll+ix9UNo4XDcmzJn6oLooftxNssVHdJC1pGT9jkSynQg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.1", - "Microsoft.NETCore.Targets": "1.1.3" - } - }, - "System.Runtime.Extensions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.Handles": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.InteropServices": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Reflection": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Runtime.Numerics": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==", - "dependencies": { - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - } - }, - "System.Security.Cryptography.Algorithms": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.Apple": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.Cng": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0" - } - }, - "System.Security.Cryptography.Csp": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Security.Cryptography.Encoding": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Collections.Concurrent": "4.3.0", - "System.Linq": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==", - "dependencies": { - "System.Collections": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==", - "dependencies": { - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Security.Cryptography.X509Certificates": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Calendars": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Cng": "4.3.0", - "System.Security.Cryptography.Csp": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Http": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Principal.Windows": { - "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" - }, - "System.Text.Encoding": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Text.Encodings.Web": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==" - }, - "System.Threading": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", - "dependencies": { - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Threading.Tasks": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } + "resolved": "9.0.0-rc.1.24431.7", + "contentHash": "giMiDNlne8WcLG7rsEfrETaukDUTBPn8a97QRo9LYR71O0Rb4SJ6TbfGdQ9oiBYldVPSAbSQANb5ThLZOo408Q==" }, "System.Threading.Tasks.Extensions": { "type": "Transitive", @@ -972,28 +367,49 @@ "colorthief": { "type": "Project", "dependencies": { - "System.Drawing.Common": "[8.0.8, )" + "System.Drawing.Common": "[9.0.0-rc.1.24451.1, )" } }, "discordrpc": { "type": "Project", "dependencies": { - "System.Text.Json": "[8.0.4, )" + "System.Text.Json": "[9.0.0-rc.1.24431.7, )" + } + }, + "h.generatedicons.system.drawing": { + "type": "Project", + "dependencies": { + "System.Drawing.Common": "[9.0.0-rc.1.24451.1, )" + } + }, + "h.notifyicon": { + "type": "Project", + "dependencies": { + "H.GeneratedIcons.System.Drawing": "[1.0.0, )" + } + }, + "h.notifyicon.winui": { + "type": "Project", + "dependencies": { + "H.NotifyIcon": "[1.0.0, )", + "Microsoft.Web.WebView2": "[1.0.2783-prerelease, )", + "Microsoft.Windows.SDK.BuildTools": "[10.0.26100.1742, )", + "Microsoft.WindowsAppSDK": "[1.6.240829007, )" } }, "hi3helper.core": { "type": "Project", "dependencies": { "Hi3Helper.EncTool": "[1.0.0, )", - "Hi3Helper.Http": "[2.0.0, )" + "Microsoft.Windows.CsWinRT": "[2.1.3, )" } }, "hi3helper.enctool": { "type": "Project", "dependencies": { - "Google.Protobuf": "[3.28.1, )", + "Google.Protobuf": "[3.28.2, )", "Hi3Helper.Http": "[2.0.0, )", - "System.IO.Hashing": "[8.0.0, )" + "System.IO.Hashing": "[9.0.0-rc.1.24431.7, )" } }, "hi3helper.http": { @@ -1004,28 +420,60 @@ "dependencies": { "Google.Protobuf": "[*, )", "Hi3Helper.ZstdNet": "[*, )", - "System.IO.Hashing": "[*, )" + "System.IO.Hashing": "[9.0.0-rc.1.24431.7, )" + } + }, + "ImageCropper": { + "type": "Project", + "dependencies": { + "CommunityToolkit.WinUI.Extensions": "[8.1.240916, )", + "CommunityToolkit.WinUI.Media": "[8.1.240916, )", + "Microsoft.Graphics.Win2D": "[1.2.1-experimental2, )", + "Microsoft.Web.WebView2": "[1.0.2783-prerelease, )", + "Microsoft.Windows.CsWinRT": "[2.1.3, )", + "Microsoft.Windows.SDK.BuildTools": "[10.0.26100.1742, )", + "Microsoft.WindowsAppSDK": "[1.6.240829007, )" } }, "imageex": { "type": "Project", "dependencies": { - "CommunityToolkit.WinUI.Extensions": "[8.1.240821, )", + "CommunityToolkit.WinUI.Extensions": "[8.1.240916, )", + "Microsoft.Web.WebView2": "[1.0.2783-prerelease, )", + "Microsoft.Windows.SDK.BuildTools": "[10.0.26100.1742, )", "Microsoft.WindowsAppSDK": "[1.6.240829007, )" } }, "innosetuphelper": { "type": "Project", "dependencies": { - "Hi3Helper.Core": "[1.0.0, )", - "System.IO.Hashing": "[8.0.0, )" + "System.IO.Hashing": "[9.0.0-rc.1.24431.7, )" + } + }, + "SettingsControls": { + "type": "Project", + "dependencies": { + "CommunityToolkit.WinUI.Triggers": "[8.1.240916, )", + "Microsoft.Web.WebView2": "[1.0.2783-prerelease, )", + "Microsoft.Windows.CsWinRT": "[2.1.3, )", + "Microsoft.Windows.SDK.BuildTools": "[10.0.26100.1742, )", + "Microsoft.WindowsAppSDK": "[1.6.240829007, )" } }, "sevenzipextractor": { "type": "Project" } }, - "net8.0-windows10.0.22621/win-x64": { + "net9.0-windows10.0.22621/win-x64": { + "Microsoft.DotNet.ILCompiler": { + "type": "Direct", + "requested": "[9.0.0-rc.1.24431.7, )", + "resolved": "9.0.0-rc.1.24431.7", + "contentHash": "FN38PfBwjKY4D753sz6GDSnvtHU9UUPb9fNAcPwImOn2j8SyUFZMnVmMVwnYX4dlDB9XL98m4g6szsjIx5UWgg==", + "dependencies": { + "runtime.win-x64.Microsoft.DotNet.ILCompiler": "9.0.0-rc.1.24431.7" + } + }, "Microsoft.Graphics.Win2D": { "type": "Direct", "requested": "[1.2.1-experimental2, )", @@ -1035,6 +483,12 @@ "Microsoft.WindowsAppSDK": "1.6.240531000-experimental1" } }, + "Microsoft.Web.WebView2": { + "type": "Direct", + "requested": "[1.0.2783-prerelease, )", + "resolved": "1.0.2783-prerelease", + "contentHash": "sPxd49RLVnOyzplXTvdU9z6NIx0t56MridYoZhw8BOBZt564Ufi168Wfg5G0rIc5EKWmrGihLeZ1OfmjQiqWTw==" + }, "Microsoft.WindowsAppSDK": { "type": "Direct", "requested": "[1.6.240829007, )", @@ -1054,46 +508,6 @@ "PhotoSauce.MagicScaler": "[0.14.2]" } }, - "System.Diagnostics.EventLog": { - "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "fdYxcRjQqTTacKId/2IECojlDSFvp7LP5N78+0z/xH7v/Tuw5ZAxu23Y6PTCRinqyu2ePx+Gn1098NC6jM6d+A==" - }, - "System.Net.Http": { - "type": "Direct", - "requested": "[4.3.4, )", - "resolved": "4.3.4", - "contentHash": "aOa2d51SEbmM+H+Csw7yJOuNZoHkrP2XnAurye5HWYgGVVU54YZDvsLUYRv6h18X3sPnjNCANmN7ZhIPiqMcjA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.1", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.DiagnosticSource": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Extensions": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Security.Cryptography.X509Certificates": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Http": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2" - } - }, "System.Security.AccessControl": { "type": "Direct", "requested": "[6.0.1, )", @@ -1102,236 +516,14 @@ }, "System.Text.Encoding.CodePages": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "OZIsVplFGaVY90G2SbpgU7EnCoOO5pw1t4ic21dBF3/1omrJFpAGoNAVpPyMVOC90/hvgkGG3VFqR13YgZMQfg==" - }, - "Microsoft.Web.WebView2": { - "type": "Transitive", - "resolved": "1.0.2651.64", - "contentHash": "f5sc/vcAoTCTEW7Nqzp4galAuTRguZViw8ksn+Nx2uskEBPm0/ubzy6gVjvXS/P96jLS89C8T9I0hPc417xpNg==" - }, - "Microsoft.Win32.Registry": { - "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", - "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" - } + "requested": "[9.0.0-rc.1.24431.7, )", + "resolved": "9.0.0-rc.1.24431.7", + "contentHash": "RRwyNcn8Ph+P3gPGouH+6x1N8l9tc5h3+54zUt6Bhet1AcjypZ51mNMYmPTOz056rxCNoPUNAyDCkk9AsAnUTw==" }, "Microsoft.Win32.SystemEvents": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "9opKRyOKMCi2xJ7Bj7kxtZ1r9vbzosMvRrdEhVhDz8j8MoBGgB+WmC94yH839NPH+BclAjtQ/pyagvi/8gDLkw==" - }, - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "7VSGO0URRKoMEAq0Sc9cRz8mb6zbyx/BZDEWhgPdzzpmFhkam3fJ1DAGWFXBI4nGlma+uPKpfuMQP5LXRnOH5g==" - }, - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "0oAaTAm6e2oVH+/Zttt0cuhGaePQYKII1dY8iaqP7CvOpVKgLybKRFvQjXR2LtxXOXTVPNv14j0ot8uV+HrUmw==" - }, - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "G24ibsCNi5Kbz0oXWynBoRgtGvsw5ZSVEWjv13/KiCAM8C6wz9zzcCniMeQFIkJ2tasjo2kXlvlBZhplL51kGg==" - }, - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "I+GNKGg2xCHueRd1m9PzeEW7WLbNNLznmTuEi8/vZX71HudUbx1UTwlGkiwMri7JLl8hGaIAWnA/GONhu+LOyQ==" - }, - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "1Z3TAq1ytS1IBRtPXJvEUZdVsfWfeNEhBkbiOCGEl9wwAfsjP2lz3ZFDx5tq8p60/EqbS0HItG5piHuB71RjoA==" - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ==" - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "6mU/cVmmHtQiDXhnzUImxIcDL48GbTk+TsptXyJA+MIOG9LRjPoAQC/qBFB7X+UNyK86bmvGwC8t+M66wsYC8w==" - }, - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "vjwG0GGcTW/PPg6KVud8F9GLWYuAV1rrw1BKAqY0oh4jcUqg15oYF1+qkGR2x2ZHM4DQnWKQ7cJgYbfncz/lYg==" - }, - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "7KMFpTkHC/zoExs+PwP8jDCWcrK9H6L7soowT80CUx3e+nxP/AFnq0AQAW5W76z2WYbLAYCRyPfwYFG6zkvQRw==" - }, - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "xrlmRCnKZJLHxyyLIqkZjNXqgxnKdZxfItrPkjI+6pkRo5lHX8YvSZlWrSI5AVwLMi4HbNWP7064hcAWeZKp5w==" - }, - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "leXiwfiIkW7Gmn7cgnNcdtNAU70SjmKW3jxGj1iKHOvdn0zRWsgv/l2OJUO5zdGdiv2VRFnAsxxhDgMzofPdWg==" - }, - "System.Globalization.Extensions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.InteropServices": "4.3.0" - } - }, - "System.Security.Cryptography.Algorithms": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.Apple": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.Cng": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0" - } - }, - "System.Security.Cryptography.Csp": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Security.Cryptography.Encoding": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Collections.Concurrent": "4.3.0", - "System.Linq": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==", - "dependencies": { - "System.Collections": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.X509Certificates": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Calendars": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Cng": "4.3.0", - "System.Security.Cryptography.Csp": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Http": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Principal.Windows": { - "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" - }, - "System.Text.Encodings.Web": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==" + "resolved": "9.0.0-rc.1.24431.7", + "contentHash": "IK3fwmfSCVArYWKjPUTEeD9nRXF1VENiFb4+hlqWuUxfP1hPVw41y/sOEdLZtRB+BVdgh6NaZjetrVhEuG4Iyg==" } } } diff --git a/ColorThief b/ColorThief index 0d73e7cce..0cbee443a 160000 --- a/ColorThief +++ b/ColorThief @@ -1 +1 @@ -Subproject commit 0d73e7cce1013b9415c44c911993c494b54eac70 +Subproject commit 0cbee443addf2b3c1fff4ff1852ec215271c2cea diff --git a/H.NotifyIcon b/H.NotifyIcon new file mode 160000 index 000000000..af0df87d6 --- /dev/null +++ b/H.NotifyIcon @@ -0,0 +1 @@ +Subproject commit af0df87d63a061a9f2401a081b17735f7e153bab diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/BitmapFileFormat.cs b/Hi3Helper.CommunityToolkit/ImageCropper/BitmapFileFormat.cs new file mode 100644 index 000000000..b4aa7d9ff --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/BitmapFileFormat.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// This denotes the format used when saving a bitmap to a file. +/// +public enum BitmapFileFormat +{ + /// + /// Indicates Windows Imaging Component's bitmap encoder. + /// + Bmp, + + /// + /// Indicates Windows Imaging Component's PNG encoder. + /// + Png, + + /// + /// Indicates Windows Imaging Component's bitmap JPEG encoder. + /// + Jpeg, + + /// + /// Indicates Windows Imaging Component's TIFF encoder. + /// + Tiff, + + /// + /// Indicates Windows Imaging Component's GIF encoder. + /// + Gif, + + /// + /// Indicates Windows Imaging Component's JPEGXR encoder. + /// + JpegXR +} diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/CropShape.cs b/Hi3Helper.CommunityToolkit/ImageCropper/CropShape.cs new file mode 100644 index 000000000..1a1c4ac42 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/CropShape.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// Crop shape enumeration. +/// Default is +/// +public enum CropShape +{ + /// + /// Use rectangular shape to crop image. + /// + Rectangular, + + /// + /// Use circular shape to crop image. + /// + Circular +} diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/Extension.cs b/Hi3Helper.CommunityToolkit/ImageCropper/Extension.cs new file mode 100644 index 000000000..5614a0a23 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/Extension.cs @@ -0,0 +1,33 @@ +namespace Hi3Helper.CommunityToolkit.WinUI.Controls +{ + internal static class Extension + { + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rect ToRect(this Size size) + { + return new Rect(0, 0, size.Width, size.Height); + } + + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rect ToRect(this Point point, double width, double height) + { + return new Rect(point.X, point.Y, width, height); + } + + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rect ToRect(this Point point, Point end) + { + return new Rect(point, end); + } + + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rect ToRect(this Point point, Size size) + { + return new Rect(point, size); + } + } +} diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/Hi3Helper.CommunityToolkit.WinUI.Controls.ImageCropper.csproj b/Hi3Helper.CommunityToolkit/ImageCropper/Hi3Helper.CommunityToolkit.WinUI.Controls.ImageCropper.csproj new file mode 100644 index 000000000..3fae9a917 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/Hi3Helper.CommunityToolkit.WinUI.Controls.ImageCropper.csproj @@ -0,0 +1,82 @@ + + + + + x64 + net9.0-windows10.0.22621.0 + 10.0.22621.41 + 10.0.17763.0 + win-x64 + true + true + Library + 12.0 + portable + enable + + Hi3Helper.CommunityToolkit.WinUI.Controls + Hi3Helper.CommunityToolkit.WinUI.Controls.ImageCropper + ImageCropper + ImageCropper + 1.0.0 + https://github.com/CollapseLauncher/Collapse + enable + disable + WINAPPSDK;WINUI3 + Debug;Release + true + win-x64 + + + + full + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(DefaultXamlRuntime) + Designer + + + $(DefaultXamlRuntime) + Designer + + + + \ No newline at end of file diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/ImageBlendBrush.cs b/Hi3Helper.CommunityToolkit/ImageCropper/ImageBlendBrush.cs new file mode 100644 index 000000000..0bb3fcf57 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/ImageBlendBrush.cs @@ -0,0 +1,216 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +//// Image loading reference from https://blogs.windows.com/buildingapps/2017/07/18/working-brushes-content-xaml-visual-layer-interop-part-one/#MA0k4EYWzqGKV501.97 + +// ReSharper disable RedundantExtendsListEntry +// ReSharper disable PartialTypeWithSinglePart +using CommunityToolkit.WinUI.Media; +using CanvasBlendEffect = Microsoft.Graphics.Canvas.Effects.BlendEffect; + +#if WINUI2 +using Windows.UI.Composition; +using Windows.UI.Xaml.Media.Imaging; +#endif + +#nullable enable +namespace Hi3Helper.CommunityToolkit.WinUI.Media; + +/// +/// Brush which blends a to the Backdrop in a given mode. See http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_BlendEffect.htm. +/// +public partial class ImageBlendBrush : XamlCompositionBrushBase +{ + private LoadedImageSurface? _surface; + private CompositionSurfaceBrush? _surfaceBrush; + + /// + /// Gets or sets the source of the image to composite. + /// + public Uri SourceUri + { + get => (Uri)GetValue(SourceUriProperty); + set => SetValue(SourceUriProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty SourceUriProperty = DependencyProperty.Register( + nameof(SourceUri), + typeof(Uri), // We use ImageSource type so XAML engine will automatically construct proper object from String. + typeof(ImageBlendBrush), + new PropertyMetadata(null, OnImageSourceChanged)); + + /// + /// Gets or sets how to stretch the image within the brush. + /// + public Stretch Stretch + { + get => (Stretch)GetValue(StretchProperty); + set => SetValue(StretchProperty, value); + } + + /// + /// Identifies the dependency property. + /// Requires 16299 or higher for modes other than None. + /// + public static readonly DependencyProperty StretchProperty = DependencyProperty.Register( + nameof(Stretch), + typeof(Stretch), + typeof(ImageBlendBrush), + new PropertyMetadata(Stretch.None, OnStretchChanged)); + + /// + /// Gets or sets how to blend the image with the backdrop. + /// + public ImageBlendMode Mode + { + get => (ImageBlendMode)GetValue(ModeProperty); + set => SetValue(ModeProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ModeProperty = DependencyProperty.Register( + nameof(Mode), + typeof(ImageBlendMode), + typeof(ImageBlendBrush), + new PropertyMetadata(ImageBlendMode.Multiply, OnModeChanged)); + + private static void OnImageSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var brush = (ImageBlendBrush)d; + + // Unbox and update surface if CompositionBrush exists + if (brush._surfaceBrush != null) + { + // If UriSource is invalid, StartLoadFromUri will return a blank texture. + var uri = (e.NewValue as BitmapImage)?.UriSource ?? new Uri("ms-appx:///"); + var newSurface = LoadedImageSurface.StartLoadFromUri(uri); + + brush._surface = newSurface; + brush._surfaceBrush.Surface = newSurface; + } + else + { + // If we didn't initially have a valid surface, we need to recreate our effect now. + brush.OnDisconnected(); + brush.OnConnected(); + } + } + + private static void OnStretchChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var brush = (ImageBlendBrush)d; + + // Unbox and update surface if CompositionBrush exists + if (brush._surfaceBrush != null) + { + // Modify the stretch property on our brush. + brush._surfaceBrush.Stretch = CompositionStretchFromStretch((Stretch)e.NewValue); + } + } + + private static void OnModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var brush = (ImageBlendBrush)d; + + // We can't animate our enum properties so recreate our internal brush. + brush.OnDisconnected(); + brush.OnConnected(); + } + + /// + protected override void OnConnected() + { +#if WINUI2 + var compositor = Window.Current.Compositor; +#elif WINUI3 + var compositor = CompositionTarget.GetCompositorForCurrentThread(); +#endif + + // Delay creating composition resources until they're required. + if (CompositionBrush != null || SourceUri is not { } bitmapUri) + { + return; + } + + // Use LoadedImageSurface API to get ICompositionSurface from image uri provided + // If UriSource is invalid, StartLoadFromUri will return a blank texture. + _surface = LoadedImageSurface.StartLoadFromUri(bitmapUri); + + // Load Surface onto SurfaceBrush + _surfaceBrush = compositor.CreateSurfaceBrush(_surface); + _surfaceBrush.Stretch = CompositionStretchFromStretch(Stretch); + + #if WINUI2 + var compositionCapabilities = CompositionCapabilities.GetForCurrentView(); + #else + var compositionCapabilities = new CompositionCapabilities(); + #endif + // Abort if effects aren't supported. + if (!compositionCapabilities.AreEffectsSupported()) + { + // Just use image straight-up, if we don't support effects. + CompositionBrush = _surfaceBrush; + return; + } + + var backdrop = compositor.CreateBackdropBrush(); + + // Use a Win2D invert affect applied to a CompositionBackdropBrush. + var graphicsEffect = new CanvasBlendEffect + { + Name = "Invert", + Mode = (BlendEffectMode)(int)Mode, + Background = new CompositionEffectSourceParameter("backdrop"), + Foreground = new CompositionEffectSourceParameter("image") + }; + + var effectFactory = compositor.CreateEffectFactory(graphicsEffect); + var effectBrush = effectFactory.CreateBrush(); + + effectBrush.SetSourceParameter("backdrop", backdrop); + effectBrush.SetSourceParameter("image", _surfaceBrush); + + CompositionBrush = effectBrush; + } + + /// + protected override void OnDisconnected() + { + // Dispose of composition resources when no longer in use. + if (CompositionBrush != null) + { + CompositionBrush.Dispose(); + CompositionBrush = null; + } + + if (_surfaceBrush != null) + { + _surfaceBrush.Dispose(); + _surfaceBrush = null; + } + + if (_surface == null) + { + return; + } + + _surface.Dispose(); + _surface = null; + } + + //// Helper to allow XAML developer to use XAML stretch property rather than another enum. + private static CompositionStretch CompositionStretchFromStretch(Stretch value) => value switch + { + Stretch.None => CompositionStretch.None, + Stretch.Fill => CompositionStretch.Fill, + Stretch.Uniform => CompositionStretch.Uniform, + Stretch.UniformToFill => CompositionStretch.UniformToFill, + _ => CompositionStretch.None + }; +} \ No newline at end of file diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Animations.cs b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Animations.cs new file mode 100644 index 000000000..0a7ab44ec --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Animations.cs @@ -0,0 +1,126 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#if !WINAPPSDK +using Windows.UI.Composition; +using Windows.UI.Xaml.Hosting; +#endif + +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// The control allows user to crop image freely. +/// +public partial class ImageCropper +{ + private static void AnimateUIElementOffset(Point to, TimeSpan duration, UIElement target) + { + var targetVisual = ElementCompositionPreview.GetElementVisual(target); + var compositor = targetVisual.Compositor; + var linear = compositor.CreateLinearEasingFunction(); + var offsetAnimation = compositor.CreateVector3KeyFrameAnimation(); + offsetAnimation.Duration = duration; + offsetAnimation.Target = nameof(Visual.Offset); + offsetAnimation.InsertKeyFrame(1.0f, new Vector3((float)to.X, (float)to.Y, 0), linear); + targetVisual.StartAnimation(nameof(Visual.Offset), offsetAnimation); + } + + private static void AnimateUIElementScale(double to, TimeSpan duration, UIElement target) + { + var targetVisual = ElementCompositionPreview.GetElementVisual(target); + var compositor = targetVisual.Compositor; + var linear = compositor.CreateLinearEasingFunction(); + var scaleAnimation = compositor.CreateVector3KeyFrameAnimation(); + scaleAnimation.Duration = duration; + scaleAnimation.Target = nameof(Visual.Scale); + scaleAnimation.InsertKeyFrame(1.0f, new Vector3((float)to), linear); + targetVisual.StartAnimation(nameof(Visual.Scale), scaleAnimation); + } + + private static DoubleAnimation CreateDoubleAnimation(double to, TimeSpan duration, DependencyObject target, string propertyName, bool enableDependentAnimation) + { + var animation = new DoubleAnimation() + { + To = to, + Duration = duration, + EnableDependentAnimation = enableDependentAnimation + }; + + Storyboard.SetTarget(animation, target); + Storyboard.SetTargetProperty(animation, propertyName); + + return animation; + } + + private static PointAnimation CreatePointAnimation(Point to, TimeSpan duration, DependencyObject target, string propertyName, bool enableDependentAnimation) + { + var animation = new PointAnimation() + { + To = to, + Duration = duration, + EnableDependentAnimation = enableDependentAnimation + }; + + Storyboard.SetTarget(animation, target); + Storyboard.SetTargetProperty(animation, propertyName); + + return animation; + } + + private static ObjectAnimationUsingKeyFrames CreateRectangleAnimation(Rect to, TimeSpan duration, RectangleGeometry rectangle, bool enableDependentAnimation) + { + var animation = new ObjectAnimationUsingKeyFrames + { + Duration = duration, + EnableDependentAnimation = enableDependentAnimation + }; + + var frames = GetRectKeyframes(rectangle.Rect, to, duration); + foreach (var item in frames) + { + animation.KeyFrames.Add(item); + } + + Storyboard.SetTarget(animation, rectangle); + Storyboard.SetTargetProperty(animation, nameof(RectangleGeometry.Rect)); + + return animation; + } + + private static List GetRectKeyframes(Rect from, Rect to, TimeSpan duration) + { + var rectKeyframes = new List(); + var step = TimeSpan.FromMilliseconds(10); + var startPointFrom = new Point(from.X, from.Y); + var endPointFrom = new Point(from.X + from.Width, from.Y + from.Height); + var startPointTo = new Point(to.X, to.Y); + var endPointTo = new Point(to.X + to.Width, to.Y + to.Height); + for (var time = default(TimeSpan); time < duration; time += step) + { + var progress = time.TotalMilliseconds / duration.TotalMilliseconds; + var startPoint = new Point + { + X = startPointFrom.X + (progress * (startPointTo.X - startPointFrom.X)), + Y = startPointFrom.Y + (progress * (startPointTo.Y - startPointFrom.Y)), + }; + var endPoint = new Point + { + X = endPointFrom.X + (progress * (endPointTo.X - endPointFrom.X)), + Y = endPointFrom.Y + (progress * (endPointTo.Y - endPointFrom.Y)), + }; + rectKeyframes.Add(new DiscreteObjectKeyFrame + { + KeyTime = KeyTime.FromTimeSpan(time), + Value = startPoint.ToRect(endPoint) + }); + } + + rectKeyframes.Add(new DiscreteObjectKeyFrame + { + KeyTime = duration, + Value = to + }); + return rectKeyframes; + } +} diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Constants.cs b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Constants.cs new file mode 100644 index 000000000..65cdb450e --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Constants.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// The control allows user to crop image freely. +/// + +public partial class ImageCropper +{ + /// + /// Key of the root layout container. + /// + internal const string LayoutGridName = "PART_LayoutGrid"; + + /// + /// Key of the Canvas that contains the image and ImageCropperThumbs. + /// + internal const string ImageCanvasPartName = "PART_ImageCanvas"; + + /// + /// Key of the Image Control inside the ImageCropper Control. + /// + internal const string SourceImagePartName = "PART_SourceImage"; + + /// + /// Key of the mask layer. + /// + internal const string MaskAreaPathPartName = "PART_MaskAreaPath"; + + /// + /// Key of the overlay layer. + /// + internal const string OverlayAreaPathPartName = "PART_OverlayAreaPath"; + + /// + /// Key of the ImageCropperThumb that on the top. + /// + internal const string TopThumbPartName = "PART_TopThumb"; + + /// + /// Key of the ImageCropperThumb on the bottom. + /// + internal const string BottomThumbPartName = "PART_BottomThumb"; + + /// + /// Key of the ImageCropperThumb on the left. + /// + internal const string LeftThumbPartName = "PART_LeftThumb"; + + /// + /// Key of the ImageCropperThumb on the right. + /// + internal const string RightThumbPartName = "PART_RightThumb"; + + /// + /// Key of the ImageCropperThumb that on the upper left. + /// + internal const string UpperLeftThumbPartName = "PART_UpperLeftThumb"; + + /// + /// Key of the ImageCropperThumb that on the upper right. + /// + internal const string UpperRightThumbPartName = "PART_UpperRightThumb"; + + /// + /// Key of the ImageCropperThumb that on the lower left. + /// + internal const string LowerLeftThumbPartName = "PART_LowerLeftThumb"; + + /// + /// Key of the ImageCropperThumb that on the lower right. + /// + internal const string LowerRightThumbPartName = "PART_LowerRightThumb"; +} \ No newline at end of file diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Events.cs b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Events.cs new file mode 100644 index 000000000..2cbcf0ebb --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Events.cs @@ -0,0 +1,217 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// The control allows user to crop image freely. +/// +public partial class ImageCropper +{ + private void ImageCropperThumb_KeyDown(object sender, KeyRoutedEventArgs e) + { + var changed = false; + var diffPos = default(Point); + if (e.Key == VirtualKey.Left) + { + diffPos.X--; +#if WINAPPSDK + var upKeyState = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Up); + var downKeyState = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Down); +#else + var upKeyState = Window.Current.CoreWindow.GetAsyncKeyState(VirtualKey.Up); + var downKeyState = Window.Current.CoreWindow.GetAsyncKeyState(VirtualKey.Down); +#endif + if (upKeyState == CoreVirtualKeyStates.Down) + { + diffPos.Y--; + } + + if (downKeyState == CoreVirtualKeyStates.Down) + { + diffPos.Y++; + } + + changed = true; + } + else if (e.Key == VirtualKey.Right) + { + diffPos.X++; + +#if WINAPPSDK + var upKeyState = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Up); + var downKeyState = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Down); +#else + var upKeyState = Window.Current.CoreWindow.GetAsyncKeyState(VirtualKey.Up); + var downKeyState = Window.Current.CoreWindow.GetAsyncKeyState(VirtualKey.Down); +#endif + + if (upKeyState == CoreVirtualKeyStates.Down) + { + diffPos.Y--; + } + + if (downKeyState == CoreVirtualKeyStates.Down) + { + diffPos.Y++; + } + + changed = true; + } + else if (e.Key == VirtualKey.Up) + { + diffPos.Y--; + +#if WINAPPSDK + var leftKeyState = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Left); + var rightKeyState = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Right); +#else + var leftKeyState = Window.Current.CoreWindow.GetAsyncKeyState(VirtualKey.Left); + var rightKeyState = Window.Current.CoreWindow.GetAsyncKeyState(VirtualKey.Right); +#endif + + if (leftKeyState == CoreVirtualKeyStates.Down) + { + diffPos.X--; + } + + if (rightKeyState == CoreVirtualKeyStates.Down) + { + diffPos.X++; + } + + changed = true; + } + else if (e.Key == VirtualKey.Down) + { + diffPos.Y++; + +#if WINAPPSDK + var leftKeyState = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Left); + var rightKeyState = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Right); +#else + var leftKeyState = Window.Current.CoreWindow.GetAsyncKeyState(VirtualKey.Left); + var rightKeyState = Window.Current.CoreWindow.GetAsyncKeyState(VirtualKey.Right); +#endif + + if (leftKeyState == CoreVirtualKeyStates.Down) + { + diffPos.X--; + } + + if (rightKeyState == CoreVirtualKeyStates.Down) + { + diffPos.X++; + } + + changed = true; + } + + if (changed) + { + var imageCropperThumb = (ImageCropperThumb)sender; + UpdateCroppedRect(imageCropperThumb.Position, diffPos); + } + } + + private void ImageCropperThumb_KeyUp(object sender, KeyRoutedEventArgs e) + { + var selectedRect = new Point(_startX, _startY).ToRect(new Point(_endX, _endY)); + var croppedRect = _inverseImageTransform.TransformBounds(selectedRect); + if (croppedRect.Width > MinCropSize.Width && croppedRect.Height > MinCropSize.Height) + { + croppedRect.Intersect(_restrictedCropRect); + _currentCroppedRect = croppedRect; + } + + if (TryUpdateImageLayout(true)) + { + UpdateSelectionThumbs(true); + UpdateMaskArea(true); + } + } + + private void ImageCropperThumb_ManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e) + { + var selectedRect = new Point(_startX, _startY).ToRect(new Point(_endX, _endY)); + var croppedRect = _inverseImageTransform.TransformBounds(selectedRect); + if (croppedRect.Width > MinCropSize.Width && croppedRect.Height > MinCropSize.Height) + { + croppedRect.Intersect(_restrictedCropRect); + _currentCroppedRect = croppedRect; + } + + if (TryUpdateImageLayout(true)) + { + UpdateSelectionThumbs(true); + UpdateMaskArea(true); + } + } + + private void ImageCropperThumb_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e) + { + var imageCropperThumb = (ImageCropperThumb)sender; + var currentPointerPosition = new Point( + imageCropperThumb.X + e.Position.X + e.Delta.Translation.X - (imageCropperThumb.ActualWidth / 2), + imageCropperThumb.Y + e.Position.Y + e.Delta.Translation.Y - (imageCropperThumb.ActualHeight / 2)); + var safePosition = GetSafePoint(_restrictedSelectRect, currentPointerPosition); + var safeDiffPoint = new Point(safePosition.X - imageCropperThumb.X, safePosition.Y - imageCropperThumb.Y); + UpdateCroppedRect(imageCropperThumb.Position, safeDiffPoint); + } + + private void SourceImage_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e) + { + var offsetX = -e.Delta.Translation.X; + var offsetY = -e.Delta.Translation.Y; + if (offsetX > 0) + { + offsetX = Math.Min(offsetX, _restrictedSelectRect.X + _restrictedSelectRect.Width - _endX); + } + else + { + offsetX = Math.Max(offsetX, _restrictedSelectRect.X - _startX); + } + + if (offsetY > 0) + { + offsetY = Math.Min(offsetY, _restrictedSelectRect.Y + _restrictedSelectRect.Height - _endY); + } + else + { + offsetY = Math.Max(offsetY, _restrictedSelectRect.Y - _startY); + } + + var selectedRect = new Point(_startX, _startY).ToRect(new Point(_endX, _endY)); + selectedRect.X += offsetX; + selectedRect.Y += offsetY; + var croppedRect = _inverseImageTransform.TransformBounds(selectedRect); + croppedRect.Intersect(_restrictedCropRect); + _currentCroppedRect = croppedRect; + + if (TryUpdateImageLayout()) + { + UpdateSelectionThumbs(); + UpdateMaskArea(); + } + } + + private void ImageCanvas_SizeChanged(object sender, SizeChangedEventArgs e) + { + if (Source == null) + { + return; + } + + if (TryUpdateImageLayout()) + { + UpdateSelectionThumbs(); + } + + if (TryUpdateAspectRatio()) + { + UpdateSelectionThumbs(); + UpdateMaskArea(); + } + } +} diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Helpers.cs b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Helpers.cs new file mode 100644 index 000000000..d8dcb1fe6 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Helpers.cs @@ -0,0 +1,440 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#if WINAPPSDK +using Colors = Microsoft.UI.Colors; +#else +using Windows.UI.Xaml.Media.Imaging; +using Colors = Windows.UI.Colors; +#endif + +#nullable enable +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// The control allows user to crop image freely. +/// +public partial class ImageCropper +{ + private const double ThresholdValue = 0.001; + + private static async Task CropImageAsync(WriteableBitmap writeableBitmap, IRandomAccessStream stream, Rect croppedRect, BitmapFileFormat bitmapFileFormat) + { + croppedRect.X = Math.Max(croppedRect.X, 0); + croppedRect.Y = Math.Max(croppedRect.Y, 0); + var x = (uint)Math.Floor(croppedRect.X); + var y = (uint)Math.Floor(croppedRect.Y); + var width = (uint)Math.Floor(croppedRect.Width); + var height = (uint)Math.Floor(croppedRect.Height); + using (var sourceStream = writeableBitmap.PixelBuffer.AsStream()) + { + var buffer = new byte[sourceStream.Length]; + _ = await sourceStream.ReadAsync(buffer); + var bitmapEncoder = await BitmapEncoder.CreateAsync(GetEncoderId(bitmapFileFormat), stream); + bitmapEncoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied, (uint)writeableBitmap.PixelWidth, (uint)writeableBitmap.PixelHeight, 96.0, 96.0, buffer); + bitmapEncoder.BitmapTransform.Bounds = new BitmapBounds + { + X = x, + Y = y, + Width = width, + Height = height + }; + await bitmapEncoder.FlushAsync(); + } + } + + private static async Task CropImageWithShapeAsync(WriteableBitmap writeableBitmap, IRandomAccessStream stream, Rect croppedRect, BitmapFileFormat bitmapFileFormat, CropShape cropShape) + { + var device = CanvasDevice.GetSharedDevice(); + var clipGeometry = CreateClipGeometry(device, cropShape, new Size(croppedRect.Width, croppedRect.Height)); + if (clipGeometry == null) + { + return; + } + + // WinUI3/Win2D bug: switch back to CanvasBitmap once it works. + CanvasVirtualBitmap? sourceBitmap; + using (var randomAccessStream = new InMemoryRandomAccessStream()) + { + await CropImageAsync(writeableBitmap, randomAccessStream, croppedRect, bitmapFileFormat); + sourceBitmap = await CanvasVirtualBitmap.LoadAsync(device, randomAccessStream); + } + + using (var offScreen = new CanvasRenderTarget(device, (float)croppedRect.Width, (float)croppedRect.Height, 96f)) + { + using (var drawingSession = offScreen.CreateDrawingSession()) + using (var markCommandList = new CanvasCommandList(device)) + { + using (var markDrawingSession = markCommandList.CreateDrawingSession()) + { + markDrawingSession.FillGeometry(clipGeometry, Colors.Black); + } + + var alphaMaskEffect = new AlphaMaskEffect + { + Source = sourceBitmap, + AlphaMask = markCommandList + }; + drawingSession.DrawImage(alphaMaskEffect); + alphaMaskEffect.Dispose(); + } + + clipGeometry.Dispose(); + sourceBitmap.Dispose(); + var pixelBytes = offScreen.GetPixelBytes(); + var bitmapEncoder = await BitmapEncoder.CreateAsync(GetEncoderId(bitmapFileFormat), stream); + bitmapEncoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied, offScreen.SizeInPixels.Width, offScreen.SizeInPixels.Height, 96.0, 96.0, pixelBytes); + await bitmapEncoder.FlushAsync(); + } + } + + private static CanvasGeometry? CreateClipGeometry(ICanvasResourceCreator resourceCreator, CropShape cropShape, Size croppedSize) + { + switch (cropShape) + { + case CropShape.Rectangular: + break; + case CropShape.Circular: + var radiusX = croppedSize.Width / 2; + var radiusY = croppedSize.Height / 2; + var center = new Point(radiusX, radiusY); + return CanvasGeometry.CreateEllipse(resourceCreator, center.ToVector2(), (float)radiusX, (float)radiusY); + default: + throw new ArgumentOutOfRangeException(nameof(cropShape), cropShape, null); + } + + return null; + } + + private static Guid GetEncoderId(BitmapFileFormat bitmapFileFormat) => bitmapFileFormat switch + { + BitmapFileFormat.Bmp => BitmapEncoder.BmpEncoderId, + BitmapFileFormat.Png => BitmapEncoder.PngEncoderId, + BitmapFileFormat.Jpeg => BitmapEncoder.JpegEncoderId, + BitmapFileFormat.Tiff => BitmapEncoder.TiffEncoderId, + BitmapFileFormat.Gif => BitmapEncoder.GifEncoderId, + BitmapFileFormat.JpegXR => BitmapEncoder.JpegXREncoderId, + _ => BitmapEncoder.PngEncoderId + }; + + /// + /// Gets the closest point in the rectangle to a given point. + /// + /// The rectangle. + /// The test point. + /// A point within a rectangle. + private static Point GetSafePoint(Rect targetRect, Point point) + { + var safePoint = new Point(point.X, point.Y); + if (safePoint.X < targetRect.X) + { + safePoint.X = targetRect.X; + } + + if (safePoint.X > targetRect.X + targetRect.Width) + { + safePoint.X = targetRect.X + targetRect.Width; + } + + if (safePoint.Y < targetRect.Y) + { + safePoint.Y = targetRect.Y; + } + + if (safePoint.Y > targetRect.Y + targetRect.Height) + { + safePoint.Y = targetRect.Y + targetRect.Height; + } + + return safePoint; + } + + /// + /// Test whether the point is in the rectangle. + /// Similar to the Rect.Contains method, this method adds redundancy. + /// + /// the rectangle. + /// The test point. + /// bool + private static bool IsSafePoint(Rect targetRect, Point point) + { + if (point.X - targetRect.X < -ThresholdValue) + { + return false; + } + + if (point.X - (targetRect.X + targetRect.Width) > ThresholdValue) + { + return false; + } + + if (point.Y - targetRect.Y < -ThresholdValue) + { + return false; + } + + if (point.Y - (targetRect.Y + targetRect.Height) > ThresholdValue) + { + return false; + } + + return true; + } + + /// + /// Determines whether a rectangle satisfies the minimum size limit. + /// + /// The point in the upper left corner. + /// The point in the lower right corner. + /// The minimum size. + /// bool + private static bool IsSafeRect(Point startPoint, Point endPoint, Size minSize) + { + var checkPoint = new Point(startPoint.X + minSize.Width, startPoint.Y + minSize.Height); + return checkPoint.X - endPoint.X < ThresholdValue + && checkPoint.Y - endPoint.Y < ThresholdValue; + } + + /// + /// Gets a rectangle with a minimum size limit. + /// + /// The point in the upper left corner. + /// The point in the lower right corner. + /// The minimum size. + /// The control point. + /// The right rectangle. + private static Rect GetSafeRect(Point startPoint, Point endPoint, Size minSize, ThumbPosition position) + { + var checkPoint = new Point(startPoint.X + minSize.Width, startPoint.Y + minSize.Height); + switch (position) + { + case ThumbPosition.Top: + if (checkPoint.Y > endPoint.Y) + { + startPoint.Y = endPoint.Y - minSize.Height; + } + + break; + case ThumbPosition.Bottom: + if (checkPoint.Y > endPoint.Y) + { + endPoint.Y = startPoint.Y + minSize.Height; + } + + break; + case ThumbPosition.Left: + if (checkPoint.X > endPoint.X) + { + startPoint.X = endPoint.X - minSize.Width; + } + + break; + case ThumbPosition.Right: + if (checkPoint.X > endPoint.X) + { + endPoint.X = startPoint.X + minSize.Width; + } + + break; + case ThumbPosition.UpperLeft: + if (checkPoint.X > endPoint.X) + { + startPoint.X = endPoint.X - minSize.Width; + } + + if (checkPoint.Y > endPoint.Y) + { + startPoint.Y = endPoint.Y - minSize.Height; + } + + break; + case ThumbPosition.UpperRight: + if (checkPoint.X > endPoint.X) + { + endPoint.X = startPoint.X + minSize.Width; + } + + if (checkPoint.Y > endPoint.Y) + { + startPoint.Y = endPoint.Y - minSize.Height; + } + + break; + case ThumbPosition.LowerLeft: + if (checkPoint.X > endPoint.X) + { + startPoint.X = endPoint.X - minSize.Width; + } + + if (checkPoint.Y > endPoint.Y) + { + endPoint.Y = startPoint.Y + minSize.Height; + } + + break; + case ThumbPosition.LowerRight: + if (checkPoint.X > endPoint.X) + { + endPoint.X = startPoint.X + minSize.Width; + } + + if (checkPoint.Y > endPoint.Y) + { + endPoint.Y = startPoint.Y + minSize.Height; + } + + break; + default: + throw new ArgumentOutOfRangeException(nameof(position), position, null); + } + + return startPoint.ToRect(endPoint); + } + + /// + /// Gets the maximum rectangle embedded in the rectangle by a given aspect ratio. + /// + /// The rectangle. + /// The aspect ratio. + /// The right rectangle. + private static Rect GetUniformRect(Rect targetRect, double aspectRatio) + { + var ratio = targetRect.Width / targetRect.Height; + var cx = targetRect.X + (targetRect.Width / 2); + var cy = targetRect.Y + (targetRect.Height / 2); + double width, height; + if (aspectRatio > ratio) + { + width = targetRect.Width; + height = width / aspectRatio; + } + else + { + height = targetRect.Height; + width = height * aspectRatio; + } + + return new Rect(cx - (width / 2f), cy - (height / 2f), width, height); + } + + private static bool IsValidRect(Rect targetRect) + { + return targetRect is { IsEmpty: false, Width: > 0, Height: > 0 }; + } + + private static Point GetSafeSizeChangeWhenKeepAspectRatio(Rect targetRect, ThumbPosition thumbPosition, Rect selectedRect, Point originSizeChange, double aspectRatio) + { + var safeWidthChange = originSizeChange.X; + var safeHeightChange = originSizeChange.Y; + double maxWidthChange; + double maxHeightChange; + switch (thumbPosition) + { + case ThumbPosition.Top: + maxWidthChange = targetRect.Width - selectedRect.Width; + maxHeightChange = selectedRect.Top - targetRect.Top; + break; + case ThumbPosition.Bottom: + maxWidthChange = targetRect.Width - selectedRect.Width; + maxHeightChange = targetRect.Bottom - selectedRect.Bottom; + break; + case ThumbPosition.Left: + maxWidthChange = selectedRect.Left - targetRect.Left; + maxHeightChange = targetRect.Height - selectedRect.Height; + break; + case ThumbPosition.Right: + maxWidthChange = targetRect.Right - selectedRect.Right; + maxHeightChange = targetRect.Height - selectedRect.Height; + break; + case ThumbPosition.UpperLeft: + maxWidthChange = selectedRect.Left - targetRect.Left; + maxHeightChange = selectedRect.Top - targetRect.Top; + break; + case ThumbPosition.UpperRight: + maxWidthChange = targetRect.Right - selectedRect.Right; + maxHeightChange = selectedRect.Top - targetRect.Top; + break; + case ThumbPosition.LowerLeft: + maxWidthChange = selectedRect.Left - targetRect.Left; + maxHeightChange = targetRect.Bottom - selectedRect.Bottom; + break; + case ThumbPosition.LowerRight: + maxWidthChange = targetRect.Right - selectedRect.Right; + maxHeightChange = targetRect.Bottom - selectedRect.Bottom; + break; + default: + throw new ArgumentOutOfRangeException(nameof(thumbPosition), thumbPosition, null); + } + + if (originSizeChange.X > maxWidthChange) + { + safeWidthChange = maxWidthChange; + safeHeightChange = safeWidthChange / aspectRatio; + } + + if (!(originSizeChange.Y > maxHeightChange)) + { + return new Point(safeWidthChange, safeHeightChange); + } + + safeHeightChange = maxHeightChange; + safeWidthChange = safeHeightChange * aspectRatio; + + return new Point(safeWidthChange, safeHeightChange); + } + + private static bool CanContains(Rect targetRect, Rect testRect) + { + return targetRect.Width - testRect.Width > -ThresholdValue && targetRect.Height - testRect.Height > -ThresholdValue; + } + + private static bool TryGetContainedRect(Rect targetRect, ref Rect testRect) + { + if (!CanContains(targetRect, testRect)) + { + return false; + } + + if (targetRect.Left > testRect.Left) + { + testRect.X += targetRect.Left - testRect.Left; + } + + if (targetRect.Top > testRect.Top) + { + testRect.Y += targetRect.Top - testRect.Top; + } + + if (targetRect.Right < testRect.Right) + { + testRect.X += targetRect.Right - testRect.Right; + } + + if (targetRect.Bottom < testRect.Bottom) + { + testRect.Y += targetRect.Bottom - testRect.Bottom; + } + + return true; + } + + private static bool IsCornerThumb(ThumbPosition thumbPosition) + { + switch (thumbPosition) + { + case ThumbPosition.Top: + case ThumbPosition.Bottom: + case ThumbPosition.Left: + case ThumbPosition.Right: + return false; + case ThumbPosition.UpperLeft: + case ThumbPosition.UpperRight: + case ThumbPosition.LowerLeft: + case ThumbPosition.LowerRight: + return true; + } + + return false; + } +} diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Logic.cs b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Logic.cs new file mode 100644 index 000000000..044433e14 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Logic.cs @@ -0,0 +1,688 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#if !WINAPPSDK +using Windows.UI.Xaml.Hosting; +#endif + +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// The control allows user to crop image freely. +/// +public partial class ImageCropper +{ + /// + /// Initializes image source transform. + /// + /// Whether animation is enabled. + private void InitImageLayout(bool animate = false) + { + if (Source != null) + { + _restrictedCropRect = new Rect(0, 0, Source.PixelWidth, Source.PixelHeight); + if (IsValidRect(_restrictedCropRect)) + { + _currentCroppedRect = KeepAspectRatio ? GetUniformRect(_restrictedCropRect, ActualAspectRatio) : _restrictedCropRect; + + if (TryUpdateImageLayout(animate)) + { + UpdateSelectionThumbs(animate); + UpdateMaskArea(animate); + } + } + } + + UpdateThumbsVisibility(); + } + + /// + /// Update image source transform. + /// + /// Whether animation is enabled. + private bool TryUpdateImageLayout(bool animate = false) + { + if (Source != null && IsValidRect(CanvasRect)) + { + var uniformSelectedRect = GetUniformRect(CanvasRect, _currentCroppedRect.Width / _currentCroppedRect.Height); + + return TryUpdateImageLayoutWithViewport(uniformSelectedRect, _currentCroppedRect, animate); + } + + return false; + } + + /// + /// Update image source transform. + /// + /// Viewport + /// The real image area of viewport. + /// Whether animation is enabled. + private bool TryUpdateImageLayoutWithViewport(Rect viewport, Rect viewportImageRect, bool animate = false) + { + if (!IsValidRect(viewport) || !IsValidRect(viewportImageRect)) + { + return false; + } + + var imageScale = viewport.Width / viewportImageRect.Width; + _imageTransform.ScaleX = _imageTransform.ScaleY = imageScale; + _imageTransform.TranslateX = viewport.X - (viewportImageRect.X * imageScale); + _imageTransform.TranslateY = viewport.Y - (viewportImageRect.Y * imageScale); + _inverseImageTransform.ScaleX = _inverseImageTransform.ScaleY = 1 / imageScale; + _inverseImageTransform.TranslateX = -_imageTransform.TranslateX / imageScale; + _inverseImageTransform.TranslateY = -_imageTransform.TranslateY / imageScale; + _restrictedSelectRect = _imageTransform.TransformBounds(_restrictedCropRect); + + if (animate) + { + AnimateUIElementOffset(new Point(_imageTransform.TranslateX, _imageTransform.TranslateY), _animationDuration, _sourceImage!); + AnimateUIElementScale(imageScale, _animationDuration, _sourceImage!); + } + else + { + var targetVisual = ElementCompositionPreview.GetElementVisual(_sourceImage); + targetVisual.Offset = new Vector3((float)_imageTransform.TranslateX, (float)_imageTransform.TranslateY, 0); + targetVisual.Scale = new Vector3((float)imageScale); + } + + return true; + } + + /// + /// Update cropped area. + /// + /// The control point + /// Position offset + private void UpdateCroppedRect(ThumbPosition position, Point diffPos) + { + if (diffPos == default(Point) || !IsValidRect(CanvasRect)) + { + return; + } + + double radian = 0d, diffPointRadian = 0d; + if (KeepAspectRatio) + { + radian = Math.Atan(ActualAspectRatio); + diffPointRadian = Math.Atan(diffPos.X / diffPos.Y); + } + + var startPoint = new Point(_startX, _startY); + var endPoint = new Point(_endX, _endY); + var currentSelectedRect = startPoint.ToRect(endPoint); + switch (position) + { + case ThumbPosition.Top: + if (KeepAspectRatio) + { + var originSizeChange = new Point(-diffPos.Y * ActualAspectRatio, -diffPos.Y); + var safeChange = GetSafeSizeChangeWhenKeepAspectRatio(_restrictedSelectRect, position, currentSelectedRect, originSizeChange, ActualAspectRatio); + startPoint.X += -safeChange.X / 2; + endPoint.X += safeChange.X / 2; + startPoint.Y += -safeChange.Y; + } + else + { + startPoint.Y += diffPos.Y; + } + + break; + case ThumbPosition.Bottom: + if (KeepAspectRatio) + { + var originSizeChange = new Point(diffPos.Y * ActualAspectRatio, diffPos.Y); + var safeChange = GetSafeSizeChangeWhenKeepAspectRatio(_restrictedSelectRect, position, currentSelectedRect, originSizeChange, ActualAspectRatio); + startPoint.X += -safeChange.X / 2; + endPoint.X += safeChange.X / 2; + endPoint.Y += safeChange.Y; + } + else + { + endPoint.Y += diffPos.Y; + } + + break; + case ThumbPosition.Left: + if (KeepAspectRatio) + { + var originSizeChange = new Point(-diffPos.X, -diffPos.X / ActualAspectRatio); + var safeChange = GetSafeSizeChangeWhenKeepAspectRatio(_restrictedSelectRect, position, currentSelectedRect, originSizeChange, ActualAspectRatio); + startPoint.Y += -safeChange.Y / 2; + endPoint.Y += safeChange.Y / 2; + startPoint.X += -safeChange.X; + } + else + { + startPoint.X += diffPos.X; + } + + break; + case ThumbPosition.Right: + if (KeepAspectRatio) + { + var originSizeChange = new Point(diffPos.X, diffPos.X / ActualAspectRatio); + var safeChange = GetSafeSizeChangeWhenKeepAspectRatio(_restrictedSelectRect, position, currentSelectedRect, originSizeChange, ActualAspectRatio); + startPoint.Y += -safeChange.Y / 2; + endPoint.Y += safeChange.Y / 2; + endPoint.X += safeChange.X; + } + else + { + endPoint.X += diffPos.X; + } + + break; + case ThumbPosition.UpperLeft: + if (KeepAspectRatio) + { + var effectiveLength = diffPos.Y / Math.Cos(diffPointRadian) * Math.Cos(diffPointRadian - radian); + var originSizeChange = new Point(-effectiveLength * Math.Sin(radian), -effectiveLength * Math.Cos(radian)); + var safeChange = GetSafeSizeChangeWhenKeepAspectRatio(_restrictedSelectRect, position, currentSelectedRect, originSizeChange, ActualAspectRatio); + diffPos.X = -safeChange.X; + diffPos.Y = -safeChange.Y; + } + + startPoint.X += diffPos.X; + startPoint.Y += diffPos.Y; + break; + case ThumbPosition.UpperRight: + if (KeepAspectRatio) + { + diffPointRadian = -diffPointRadian; + var effectiveLength = diffPos.Y / Math.Cos(diffPointRadian) * Math.Cos(diffPointRadian - radian); + var originSizeChange = new Point(-effectiveLength * Math.Sin(radian), -effectiveLength * Math.Cos(radian)); + var safeChange = GetSafeSizeChangeWhenKeepAspectRatio(_restrictedSelectRect, position, currentSelectedRect, originSizeChange, ActualAspectRatio); + diffPos.X = safeChange.X; + diffPos.Y = -safeChange.Y; + } + + endPoint.X += diffPos.X; + startPoint.Y += diffPos.Y; + break; + case ThumbPosition.LowerLeft: + if (KeepAspectRatio) + { + diffPointRadian = -diffPointRadian; + var effectiveLength = diffPos.Y / Math.Cos(diffPointRadian) * Math.Cos(diffPointRadian - radian); + var originSizeChange = new Point(effectiveLength * Math.Sin(radian), effectiveLength * Math.Cos(radian)); + var safeChange = GetSafeSizeChangeWhenKeepAspectRatio(_restrictedSelectRect, position, currentSelectedRect, originSizeChange, ActualAspectRatio); + diffPos.X = -safeChange.X; + diffPos.Y = safeChange.Y; + } + + startPoint.X += diffPos.X; + endPoint.Y += diffPos.Y; + break; + case ThumbPosition.LowerRight: + if (KeepAspectRatio) + { + var effectiveLength = diffPos.Y / Math.Cos(diffPointRadian) * Math.Cos(diffPointRadian - radian); + var originSizeChange = new Point(effectiveLength * Math.Sin(radian), effectiveLength * Math.Cos(radian)); + var safeChange = GetSafeSizeChangeWhenKeepAspectRatio(_restrictedSelectRect, position, currentSelectedRect, originSizeChange, ActualAspectRatio); + diffPos.X = safeChange.X; + diffPos.Y = safeChange.Y; + } + + endPoint.X += diffPos.X; + endPoint.Y += diffPos.Y; + break; + } + + if (!IsSafeRect(startPoint, endPoint, MinSelectSize)) + { + if (KeepAspectRatio) + { + if ((endPoint.Y - startPoint.Y) < (_endY - _startY) || + (endPoint.X - startPoint.X) < (_endX - _startX)) + { + return; + } + } + else + { + var safeRect = GetSafeRect(startPoint, endPoint, MinSelectSize, position); + safeRect.Intersect(_restrictedSelectRect); + startPoint = new Point(safeRect.X, safeRect.Y); + endPoint = new Point(safeRect.X + safeRect.Width, safeRect.Y + safeRect.Height); + } + } + + var isEffectiveRegion = IsSafePoint(_restrictedSelectRect, startPoint) && + IsSafePoint(_restrictedSelectRect, endPoint); + var selectedRect = startPoint.ToRect(endPoint); + if (!isEffectiveRegion) + { + if (!IsCornerThumb(position) && TryGetContainedRect(_restrictedSelectRect, ref selectedRect)) + { + startPoint = new Point(selectedRect.Left, selectedRect.Top); + endPoint = new Point(selectedRect.Right, selectedRect.Bottom); + } + else + { + return; + } + } + + selectedRect.Union(CanvasRect); + if (selectedRect != CanvasRect) + { + var croppedRect = _inverseImageTransform.TransformBounds(startPoint.ToRect(endPoint)); + croppedRect.Intersect(_restrictedCropRect); + _currentCroppedRect = croppedRect; + var viewportRect = GetUniformRect(CanvasRect, selectedRect.Width / selectedRect.Height); + var viewportImgRect = _inverseImageTransform.TransformBounds(selectedRect); + + if (TryUpdateImageLayoutWithViewport(viewportRect, viewportImgRect)) + { + UpdateSelectionThumbs(); + UpdateMaskArea(); + } + } + else + { + UpdateSelectionThumbs(startPoint, endPoint); + UpdateMaskArea(); + } + } + + private void UpdateSelectionThumbs(bool animate = false) + { + var selectedRect = _imageTransform.TransformBounds(_currentCroppedRect); + var startPoint = GetSafePoint(_restrictedSelectRect, new Point(selectedRect.X, selectedRect.Y)); + var endPoint = GetSafePoint(_restrictedSelectRect, new Point(selectedRect.X + selectedRect.Width, selectedRect.Y + selectedRect.Height)); + + UpdateSelectionThumbs(startPoint, endPoint, animate); + } + + #nullable enable + /// + /// Positions the thumbs for the selection rectangle. + /// + /// The point on the upper left corner. + /// The point on the lower right corner. + /// Whether animation is enabled. + private void UpdateSelectionThumbs(Point startPoint, Point endPoint, bool animate = false) + { + _startX = startPoint.X; + _startY = startPoint.Y; + _endX = endPoint.X; + _endY = endPoint.Y; + var center = SelectionAreaCenter; + + Storyboard? storyboard = null; + if (animate) + { + storyboard = new Storyboard(); + } + + if (_topThumb != null) + { + if (animate) + { + storyboard?.Children.Add(CreateDoubleAnimation(center.X, _animationDuration, _topThumb, nameof(ImageCropperThumb.X), true)); + storyboard?.Children.Add(CreateDoubleAnimation(_startY, _animationDuration, _topThumb, nameof(ImageCropperThumb.Y), true)); + } + else + { + _topThumb.X = center.X; + _topThumb.Y = _startY; + } + } + + if (_bottomThumb != null) + { + if (animate) + { + storyboard?.Children.Add(CreateDoubleAnimation(center.X, _animationDuration, _bottomThumb, nameof(ImageCropperThumb.X), true)); + storyboard?.Children.Add(CreateDoubleAnimation(_endY, _animationDuration, _bottomThumb, nameof(ImageCropperThumb.Y), true)); + } + else + { + _bottomThumb.X = center.X; + _bottomThumb.Y = _endY; + } + } + + if (_leftThumb != null) + { + if (animate) + { + storyboard?.Children.Add(CreateDoubleAnimation(_startX, _animationDuration, _leftThumb, nameof(ImageCropperThumb.X), true)); + storyboard?.Children.Add(CreateDoubleAnimation(center.Y, _animationDuration, _leftThumb, nameof(ImageCropperThumb.Y), true)); + } + else + { + _leftThumb.X = _startX; + _leftThumb.Y = center.Y; + } + } + + if (_rightThumb != null) + { + if (animate) + { + storyboard?.Children.Add(CreateDoubleAnimation(_endX, _animationDuration, _rightThumb, nameof(ImageCropperThumb.X), true)); + storyboard?.Children.Add(CreateDoubleAnimation(center.Y, _animationDuration, _rightThumb, nameof(ImageCropperThumb.Y), true)); + } + else + { + _rightThumb.X = _endX; + _rightThumb.Y = center.Y; + } + } + + if (_upperLeftThumb != null) + { + if (animate) + { + storyboard?.Children.Add(CreateDoubleAnimation(_startX, _animationDuration, _upperLeftThumb, nameof(ImageCropperThumb.X), true)); + storyboard?.Children.Add(CreateDoubleAnimation(_startY, _animationDuration, _upperLeftThumb, nameof(ImageCropperThumb.Y), true)); + } + else + { + _upperLeftThumb.X = _startX; + _upperLeftThumb.Y = _startY; + } + } + + if (_upperRightThumb != null) + { + if (animate) + { + storyboard?.Children.Add(CreateDoubleAnimation(_endX, _animationDuration, _upperRightThumb, nameof(ImageCropperThumb.X), true)); + storyboard?.Children.Add(CreateDoubleAnimation(_startY, _animationDuration, _upperRightThumb, nameof(ImageCropperThumb.Y), true)); + } + else + { + _upperRightThumb.X = _endX; + _upperRightThumb.Y = _startY; + } + } + + if (_lowerLeftThumb != null) + { + if (animate) + { + storyboard?.Children.Add(CreateDoubleAnimation(_startX, _animationDuration, _lowerLeftThumb, nameof(ImageCropperThumb.X), true)); + storyboard?.Children.Add(CreateDoubleAnimation(_endY, _animationDuration, _lowerLeftThumb, nameof(ImageCropperThumb.Y), true)); + } + else + { + _lowerLeftThumb.X = _startX; + _lowerLeftThumb.Y = _endY; + } + } + + if (_lowerRigthThumb != null) + { + if (animate) + { + storyboard?.Children.Add(CreateDoubleAnimation(_endX, _animationDuration, _lowerRigthThumb, nameof(ImageCropperThumb.X), true)); + storyboard?.Children.Add(CreateDoubleAnimation(_endY, _animationDuration, _lowerRigthThumb, nameof(ImageCropperThumb.Y), true)); + } + else + { + _lowerRigthThumb.X = _endX; + _lowerRigthThumb.Y = _endY; + } + } + + if (animate) + { + storyboard?.Begin(); + } + } + #nullable restore + + /// + /// Update crop shape. + /// + private void UpdateCropShape() + { + _maskAreaGeometryGroup.Children.Clear(); + _outerGeometry = new RectangleGeometry(); + switch (CropShape) + { + case CropShape.Rectangular: + _innerGeometry = new RectangleGeometry(); + _overlayGeometry = new RectangleGeometry(); + break; + case CropShape.Circular: + _innerGeometry = new EllipseGeometry(); + _overlayGeometry = new EllipseGeometry(); + break; + } + + _maskAreaGeometryGroup.Children.Add(_outerGeometry); + _maskAreaGeometryGroup.Children.Add(_innerGeometry); + if (_overlayAreaPath != null) + { + _overlayAreaPath.Data = _overlayGeometry; + } + } + + /// + /// Update the mask layer. + /// + private void UpdateMaskArea(bool animate = false) + { + if (_layoutGrid == null || _maskAreaGeometryGroup.Children.Count < 2) + { + return; + } + + _outerGeometry.Rect = new Rect(-_layoutGrid.Padding.Left, -_layoutGrid.Padding.Top, _layoutGrid.ActualWidth, _layoutGrid.ActualHeight); + + switch (CropShape) + { + case CropShape.Rectangular: + var updateRectangleGeometry = (RectangleGeometry rectangleGeometry) => + { + var to = new Point(_startX, _startY).ToRect(new Point(_endX, _endY)); + if (animate) + { + var storyboard = new Storyboard(); + storyboard.Children.Add(CreateRectangleAnimation(to, _animationDuration, rectangleGeometry, true)); + storyboard.Begin(); + } + else + { + rectangleGeometry.Rect = to; + } + }; + if (_innerGeometry is RectangleGeometry innerRectangleGeometry) + { + updateRectangleGeometry(innerRectangleGeometry); + } + if (_overlayGeometry is RectangleGeometry overlayRectangleGeometry) + { + updateRectangleGeometry(overlayRectangleGeometry); + } + + break; + case CropShape.Circular: + var updateEllipseGeometry = (EllipseGeometry ellipseGeometry) => + { + var center = new Point(((_endX - _startX) / 2) + _startX, ((_endY - _startY) / 2) + _startY); + var radiusX = (_endX - _startX) / 2; + var radiusY = (_endY - _startY) / 2; + if (animate) + { + var storyboard = new Storyboard(); + storyboard.Children.Add(CreatePointAnimation(center, _animationDuration, ellipseGeometry, nameof(EllipseGeometry.Center), true)); + storyboard.Children.Add(CreateDoubleAnimation(radiusX, _animationDuration, ellipseGeometry, nameof(EllipseGeometry.RadiusX), true)); + storyboard.Children.Add(CreateDoubleAnimation(radiusY, _animationDuration, ellipseGeometry, nameof(EllipseGeometry.RadiusY), true)); + storyboard.Begin(); + } + else + { + ellipseGeometry.Center = center; + ellipseGeometry.RadiusX = radiusX; + ellipseGeometry.RadiusY = radiusY; + } + }; + if (_innerGeometry is EllipseGeometry innerEllipseGeometry) + { + updateEllipseGeometry(innerEllipseGeometry); + } + if (_overlayGeometry is EllipseGeometry overlayEllipseGeometry) + { + updateEllipseGeometry(overlayEllipseGeometry); + } + + break; + } + + _layoutGrid.Clip = new RectangleGeometry + { + Rect = new Rect(0, 0, _layoutGrid.ActualWidth, _layoutGrid.ActualHeight) + }; + } + + /// + /// Update image aspect ratio. + /// + private bool TryUpdateAspectRatio() + { + if (KeepAspectRatio is false || Source is null || IsValidRect(_restrictedSelectRect) is false) + { + return false; + } + + var center = SelectionAreaCenter; + var restrictedMinLength = MinCroppedPixelLength * _imageTransform.ScaleX; + var maxSelectedLength = Math.Max(_endX - _startX, _endY - _startY); + var viewRect = new Rect(center.X - (maxSelectedLength / 2), center.Y - (maxSelectedLength / 2), maxSelectedLength, maxSelectedLength); + + var uniformSelectedRect = GetUniformRect(viewRect, ActualAspectRatio); + if (uniformSelectedRect.Width > _restrictedSelectRect.Width || uniformSelectedRect.Height > _restrictedSelectRect.Height) + { + uniformSelectedRect = GetUniformRect(_restrictedSelectRect, ActualAspectRatio); + } + + // If selection area is smaller than allowed. + if (uniformSelectedRect.Width < restrictedMinLength || uniformSelectedRect.Height < restrictedMinLength) + { + // Scale selection area to fit. + var scale = restrictedMinLength / Math.Min(uniformSelectedRect.Width, uniformSelectedRect.Height); + uniformSelectedRect.Width *= scale; + uniformSelectedRect.Height *= scale; + + // If selection area is larger than allowed. + if (uniformSelectedRect.Width > _restrictedSelectRect.Width || uniformSelectedRect.Height > _restrictedSelectRect.Height) + { + // Sentinal value. Equivelant to setting KeepAspectRatio to false. Causes AspectRatio to be recalculated. + AspectRatio = -1; + return false; + } + } + + // Fix positioning + if (_restrictedSelectRect.X > uniformSelectedRect.X) + { + uniformSelectedRect.X += _restrictedSelectRect.X - uniformSelectedRect.X; + } + + if (_restrictedSelectRect.Y > uniformSelectedRect.Y) + { + uniformSelectedRect.Y += _restrictedSelectRect.Y - uniformSelectedRect.Y; + } + + // Fix size + if ((_restrictedSelectRect.X + _restrictedSelectRect.Width) < (uniformSelectedRect.X + uniformSelectedRect.Width)) + { + uniformSelectedRect.X += (_restrictedSelectRect.X + _restrictedSelectRect.Width) - (uniformSelectedRect.X + uniformSelectedRect.Width); + } + + if ((_restrictedSelectRect.Y + _restrictedSelectRect.Height) < (uniformSelectedRect.Y + uniformSelectedRect.Height)) + { + uniformSelectedRect.Y += (_restrictedSelectRect.Y + _restrictedSelectRect.Height) - (uniformSelectedRect.Y + uniformSelectedRect.Height); + } + + // Apply transformation + var croppedRect = _inverseImageTransform.TransformBounds(uniformSelectedRect); + croppedRect.Intersect(_restrictedCropRect); + _currentCroppedRect = croppedRect; + + return true; + } + + /// + /// Update the visibility of the thumbs. + /// + private void UpdateThumbsVisibility() + { + var cornerThumbsVisibility = Visibility.Visible; + var otherThumbsVisibility = Visibility.Visible; + switch (ThumbPlacement) + { + case ThumbPlacement.All: + break; + case ThumbPlacement.Corners: + otherThumbsVisibility = Visibility.Collapsed; + break; + } + + switch (CropShape) + { + case CropShape.Rectangular: + break; + case CropShape.Circular: + cornerThumbsVisibility = Visibility.Collapsed; + otherThumbsVisibility = Visibility.Visible; + break; + } + + if (Source == null) + { + cornerThumbsVisibility = otherThumbsVisibility = Visibility.Collapsed; + } + + if (_topThumb != null) + { + _topThumb.Visibility = otherThumbsVisibility; + } + + if (_bottomThumb != null) + { + _bottomThumb.Visibility = otherThumbsVisibility; + } + + if (_leftThumb != null) + { + _leftThumb.Visibility = otherThumbsVisibility; + } + + if (_rightThumb != null) + { + _rightThumb.Visibility = otherThumbsVisibility; + } + + if (_upperLeftThumb != null) + { + _upperLeftThumb.Visibility = cornerThumbsVisibility; + } + + if (_upperRightThumb != null) + { + _upperRightThumb.Visibility = cornerThumbsVisibility; + } + + if (_lowerLeftThumb != null) + { + _lowerLeftThumb.Visibility = cornerThumbsVisibility; + } + + if (_lowerRigthThumb != null) + { + _lowerRigthThumb.Visibility = cornerThumbsVisibility; + } + } + + /// + /// Gets a value that indicates the center of the visible selection rectangle. + /// + private Point SelectionAreaCenter => new Point(((_endX - _startX) / 2) + _startX, ((_endY - _startY) / 2) + _startY); +} diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Properties.cs b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Properties.cs new file mode 100644 index 000000000..dafeca090 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Properties.cs @@ -0,0 +1,210 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#if !WINAPPSDK +using Windows.UI.Xaml.Media.Imaging; +#endif + +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// The control allows user to crop image freely. +/// +public partial class ImageCropper +{ + /// + /// Gets or sets the minimum cropped length(in pixel). + /// + public double MinCroppedPixelLength { get; set; } = 40; + + /// + /// Gets or sets the minimum selectable length. + /// + public double MinSelectedLength { get; set; } = 40; + + /// + /// Gets the current cropped region. + /// + public Rect CroppedRegion => _currentCroppedRect; + + private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var target = (ImageCropper)d; + if (e.NewValue is WriteableBitmap bitmap) + { + if (bitmap.PixelWidth < target.MinCropSize.Width || bitmap.PixelHeight < target.MinCropSize.Height) + { + target.Source = null; + throw new ArgumentException("The resolution of the image is too small!"); + } + } + + target.InvalidateMeasure(); + target.UpdateCropShape(); + target.InitImageLayout(); + } + + private static void OnAspectRatioChanged( + DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var target = (ImageCropper)d; + + if (target.TryUpdateAspectRatio()) + { + if (target.TryUpdateImageLayout(true)) + { + target.UpdateSelectionThumbs(true); + target.UpdateMaskArea(true); + } + } + } + + private static void OnCropShapeChanged( + DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var target = (ImageCropper)d; + target.UpdateCropShape(); + target.UpdateThumbsVisibility(); + + if (target.TryUpdateAspectRatio()) + { + if (target.TryUpdateImageLayout()) + { + target.UpdateSelectionThumbs(); + } + } + + target.UpdateMaskArea(); + } + + private static void OnThumbPlacementChanged( + DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var target = (ImageCropper)d; + target.UpdateThumbsVisibility(); + } + + #nullable enable + /// + /// Gets or sets the source of the cropped image. + /// + public WriteableBitmap? Source + { + get { return (WriteableBitmap)GetValue(SourceProperty); } + set { SetValue(SourceProperty, value); } + } + #nullable restore + + /// + /// Gets or sets the aspect ratio of the cropped image, the default value is null. + /// Only works when = . + /// + public double? AspectRatio + { + get { return (double?)GetValue(AspectRatioProperty); } + set { SetValue(AspectRatioProperty, value); } + } + + /// + /// Gets or sets the shape to use when cropping. + /// + public CropShape CropShape + { + get { return (CropShape)GetValue(CropShapeProperty); } + set { SetValue(CropShapeProperty, value); } + } + + /// + /// Gets or sets the mask on the cropped image. + /// + public Brush Mask + { + get { return (Brush)GetValue(MaskProperty); } + set { SetValue(MaskProperty, value); } + } + + /// + /// Gets or sets the overlay on the cropped image. + /// + public Brush Overlay + { + get { return (Brush)GetValue(OverlayProperty); } + set { SetValue(OverlayProperty, value); } + } + + /// + /// Gets or sets a value for the style to use for the primary thumbs of the ImageCropper. + /// + public Style PrimaryThumbStyle + { + get { return (Style)GetValue(PrimaryThumbStyleProperty); } + set { SetValue(PrimaryThumbStyleProperty, value); } + } + + /// + /// Gets or sets a value for the style to use for the secondary thumbs of the ImageCropper. + /// + public Style SecondaryThumbStyle + { + get { return (Style)GetValue(SecondaryThumbStyleProperty); } + set { SetValue(SecondaryThumbStyleProperty, value); } + } + + /// + /// Gets or sets a value for thumb placement. + /// + public ThumbPlacement ThumbPlacement + { + get { return (ThumbPlacement)GetValue(ThumbPlacementProperty); } + set { SetValue(ThumbPlacementProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty AspectRatioProperty = + DependencyProperty.Register(nameof(AspectRatio), typeof(double?), typeof(ImageCropper), new PropertyMetadata(null, OnAspectRatioChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty SourceProperty = + DependencyProperty.Register(nameof(Source), typeof(WriteableBitmap), typeof(ImageCropper), new PropertyMetadata(null, OnSourceChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty CropShapeProperty = + DependencyProperty.Register(nameof(CropShape), typeof(CropShape), typeof(ImageCropper), new PropertyMetadata(default(CropShape), OnCropShapeChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty MaskProperty = + DependencyProperty.Register(nameof(Mask), typeof(Brush), typeof(ImageCropper), new PropertyMetadata(default(Brush))); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty OverlayProperty = + DependencyProperty.Register(nameof(Overlay), typeof(Brush), typeof(ImageCropper), new PropertyMetadata(default(Brush))); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty PrimaryThumbStyleProperty = + DependencyProperty.Register(nameof(PrimaryThumbStyle), typeof(Style), typeof(ImageCropper), new PropertyMetadata(default(Style))); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty SecondaryThumbStyleProperty = + DependencyProperty.Register(nameof(SecondaryThumbStyle), typeof(Style), typeof(ImageCropper), new PropertyMetadata(default(Style))); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ThumbPlacementProperty = + DependencyProperty.Register(nameof(ThumbPlacement), typeof(ThumbPlacement), typeof(ImageCropper), new PropertyMetadata(default(ThumbPlacement), OnThumbPlacementChanged)); +} diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.cs b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.cs new file mode 100644 index 000000000..0f01bbd4c --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.cs @@ -0,0 +1,474 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#if WINAPPSDK +using Path = Microsoft.UI.Xaml.Shapes.Path; +// ReSharper disable CompareOfFloatsByEqualityOperator +#else +using Windows.UI.Xaml.Media.Imaging; +using Path = Windows.UI.Xaml.Shapes.Path; +#endif + +#nullable enable +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// The control allows user to crop image freely. +/// +[TemplatePart(Name = LayoutGridName, Type = typeof(Grid))] +[TemplatePart(Name = ImageCanvasPartName, Type = typeof(Canvas))] +[TemplatePart(Name = SourceImagePartName, Type = typeof(Image))] +[TemplatePart(Name = MaskAreaPathPartName, Type = typeof(Path))] +[TemplatePart(Name = OverlayAreaPathPartName, Type = typeof(Path))] +[TemplatePart(Name = TopThumbPartName, Type = typeof(ImageCropperThumb))] +[TemplatePart(Name = BottomThumbPartName, Type = typeof(ImageCropperThumb))] +[TemplatePart(Name = LeftThumbPartName, Type = typeof(ImageCropperThumb))] +[TemplatePart(Name = RightThumbPartName, Type = typeof(ImageCropperThumb))] +[TemplatePart(Name = UpperLeftThumbPartName, Type = typeof(ImageCropperThumb))] +[TemplatePart(Name = UpperRightThumbPartName, Type = typeof(ImageCropperThumb))] +[TemplatePart(Name = LowerLeftThumbPartName, Type = typeof(ImageCropperThumb))] +[TemplatePart(Name = LowerRightThumbPartName, Type = typeof(ImageCropperThumb))] +public partial class ImageCropper : Control +{ + private readonly CompositeTransform _imageTransform = new CompositeTransform(); + private readonly CompositeTransform _inverseImageTransform = new CompositeTransform(); + private readonly GeometryGroup _maskAreaGeometryGroup = new GeometryGroup { FillRule = FillRule.EvenOdd }; + + private Grid? _layoutGrid; + private Canvas? _imageCanvas; + private Image? _sourceImage; + private Path? _maskAreaPath; + private Path? _overlayAreaPath; + private ImageCropperThumb? _topThumb; + private ImageCropperThumb? _bottomThumb; + private ImageCropperThumb? _leftThumb; + private ImageCropperThumb? _rightThumb; + private ImageCropperThumb? _upperLeftThumb; + private ImageCropperThumb? _upperRightThumb; + private ImageCropperThumb? _lowerLeftThumb; + private ImageCropperThumb? _lowerRigthThumb; + + // Selection area + private double _startX; + private double _startY; + private double _endX; + private double _endY; + + private Rect _currentCroppedRect = Rect.Empty; + private Rect _restrictedCropRect = Rect.Empty; + private Rect _restrictedSelectRect = Rect.Empty; + private RectangleGeometry _outerGeometry; + private Geometry _innerGeometry; + private Geometry _overlayGeometry; + private TimeSpan _animationDuration = TimeSpan.FromSeconds(0.3); + + /// + /// Initializes a new instance of the class. + /// +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + public ImageCropper() +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + { + DefaultStyleKey = typeof(ImageCropper); + } + + private Rect CanvasRect => new Rect(0, 0, _imageCanvas?.ActualWidth ?? 0, _imageCanvas?.ActualHeight ?? 0); + + /// + /// Gets a value indicating whether the user-provided is valid and should be kept during manipulation of the image cropper. + /// + private bool KeepAspectRatio => ActualAspectRatio > 0; + + /// + /// Gets the internally used aspect ratio, rather than the user-provided value. Adjusted to handle crop shape and invalid values. + /// + private double ActualAspectRatio + { + get + { + var aspectRatio = CropShape switch + { + CropShape.Rectangular => AspectRatio, + CropShape.Circular => 1, + _ => AspectRatio, + }; + + if (aspectRatio is not null && aspectRatio > 0) + { + // When not null or 0. + return aspectRatio.Value; + } + else + { + // Fallback to sentinal value. + // Used to indicate aspect ratio should be discarded and reset during manipulation of the image cropper. + return -1; + } + } + } + + /// + /// Gets the minimum cropped size. + /// + private Size MinCropSize + { + get + { + var aspectRatio = KeepAspectRatio ? ActualAspectRatio : 1; + var size = new Size(MinCroppedPixelLength, MinCroppedPixelLength); + if (aspectRatio >= 1) + { + size.Width = size.Height * aspectRatio; + } + else + { + size.Height = size.Width / aspectRatio; + } + + return size; + } + } + + /// + /// Gets the minimum selectable size. + /// + private Size MinSelectSize + { + get + { + var realMinSelectSize = _imageTransform.TransformBounds(MinCropSize.ToRect()); + var minLength = Math.Min(realMinSelectSize.Width, realMinSelectSize.Height); + if (minLength < MinSelectedLength) + { + var aspectRatio = KeepAspectRatio ? ActualAspectRatio : 1; + var minSelectSize = new Size(MinSelectedLength, MinSelectedLength); + if (aspectRatio >= 1) + { + minSelectSize.Width = minSelectSize.Height * aspectRatio; + } + else + { + minSelectSize.Height = minSelectSize.Width / aspectRatio; + } + + return minSelectSize; + } + + return new Size(realMinSelectSize.Width, realMinSelectSize.Height); + } + } + + /// + protected override void OnApplyTemplate() + { + UnhookEvents(); + _layoutGrid = GetTemplateChild(LayoutGridName) as Grid; + _imageCanvas = GetTemplateChild(ImageCanvasPartName) as Canvas; + _sourceImage = GetTemplateChild(SourceImagePartName) as Image; + _maskAreaPath = GetTemplateChild(MaskAreaPathPartName) as Path; + _overlayAreaPath = GetTemplateChild(OverlayAreaPathPartName) as Path; + _topThumb = GetTemplateChild(TopThumbPartName) as ImageCropperThumb; + _bottomThumb = GetTemplateChild(BottomThumbPartName) as ImageCropperThumb; + _leftThumb = GetTemplateChild(LeftThumbPartName) as ImageCropperThumb; + _rightThumb = GetTemplateChild(RightThumbPartName) as ImageCropperThumb; + _upperLeftThumb = GetTemplateChild(UpperLeftThumbPartName) as ImageCropperThumb; + _upperRightThumb = GetTemplateChild(UpperRightThumbPartName) as ImageCropperThumb; + _lowerLeftThumb = GetTemplateChild(LowerLeftThumbPartName) as ImageCropperThumb; + _lowerRigthThumb = GetTemplateChild(LowerRightThumbPartName) as ImageCropperThumb; + HookUpEvents(); + UpdateThumbsVisibility(); + } + + private void HookUpEvents() + { + if (_imageCanvas != null) + { + _imageCanvas.SizeChanged += ImageCanvas_SizeChanged; + } + + if (_sourceImage != null) + { + _sourceImage.ManipulationMode = ManipulationModes.TranslateX | ManipulationModes.TranslateY; + _sourceImage.ManipulationDelta += SourceImage_ManipulationDelta; + } + + if (_maskAreaPath != null) + { + _maskAreaPath.Data = _maskAreaGeometryGroup; + } + + if (_overlayAreaPath != null) + { + _overlayAreaPath.Data = _overlayGeometry; + } + + if (_topThumb != null) + { + _topThumb.Position = ThumbPosition.Top; + _topThumb.ManipulationDelta += ImageCropperThumb_ManipulationDelta; + _topThumb.ManipulationCompleted += ImageCropperThumb_ManipulationCompleted; + _topThumb.KeyDown += ImageCropperThumb_KeyDown; + _topThumb.KeyUp += ImageCropperThumb_KeyUp; + } + + if (_bottomThumb != null) + { + _bottomThumb.Position = ThumbPosition.Bottom; + _bottomThumb.ManipulationDelta += ImageCropperThumb_ManipulationDelta; + _bottomThumb.ManipulationCompleted += ImageCropperThumb_ManipulationCompleted; + _bottomThumb.KeyDown += ImageCropperThumb_KeyDown; + _bottomThumb.KeyUp += ImageCropperThumb_KeyUp; + } + + if (_leftThumb != null) + { + _leftThumb.Position = ThumbPosition.Left; + _leftThumb.ManipulationDelta += ImageCropperThumb_ManipulationDelta; + _leftThumb.ManipulationCompleted += ImageCropperThumb_ManipulationCompleted; + _leftThumb.KeyDown += ImageCropperThumb_KeyDown; + _leftThumb.KeyUp += ImageCropperThumb_KeyUp; + } + + if (_rightThumb != null) + { + _rightThumb.Position = ThumbPosition.Right; + _rightThumb.ManipulationDelta += ImageCropperThumb_ManipulationDelta; + _rightThumb.ManipulationCompleted += ImageCropperThumb_ManipulationCompleted; + _rightThumb.KeyDown += ImageCropperThumb_KeyDown; + _rightThumb.KeyUp += ImageCropperThumb_KeyUp; + } + + if (_upperLeftThumb != null) + { + _upperLeftThumb.Position = ThumbPosition.UpperLeft; + _upperLeftThumb.ManipulationDelta += ImageCropperThumb_ManipulationDelta; + _upperLeftThumb.ManipulationCompleted += ImageCropperThumb_ManipulationCompleted; + _upperLeftThumb.KeyDown += ImageCropperThumb_KeyDown; + _upperLeftThumb.KeyUp += ImageCropperThumb_KeyUp; + } + + if (_upperRightThumb != null) + { + _upperRightThumb.Position = ThumbPosition.UpperRight; + _upperRightThumb.ManipulationDelta += ImageCropperThumb_ManipulationDelta; + _upperRightThumb.ManipulationCompleted += ImageCropperThumb_ManipulationCompleted; + _upperRightThumb.KeyDown += ImageCropperThumb_KeyDown; + _upperRightThumb.KeyUp += ImageCropperThumb_KeyUp; + } + + if (_lowerLeftThumb != null) + { + _lowerLeftThumb.Position = ThumbPosition.LowerLeft; + _lowerLeftThumb.ManipulationDelta += ImageCropperThumb_ManipulationDelta; + _lowerLeftThumb.ManipulationCompleted += ImageCropperThumb_ManipulationCompleted; + _lowerLeftThumb.KeyDown += ImageCropperThumb_KeyDown; + _lowerLeftThumb.KeyUp += ImageCropperThumb_KeyUp; + } + + if (_lowerRigthThumb != null) + { + _lowerRigthThumb.Position = ThumbPosition.LowerRight; + _lowerRigthThumb.ManipulationDelta += ImageCropperThumb_ManipulationDelta; + _lowerRigthThumb.ManipulationCompleted += ImageCropperThumb_ManipulationCompleted; + _lowerRigthThumb.KeyDown += ImageCropperThumb_KeyDown; + _lowerRigthThumb.KeyUp += ImageCropperThumb_KeyUp; + } + } + + private void UnhookEvents() + { + if (_imageCanvas != null) + { + _imageCanvas.SizeChanged -= ImageCanvas_SizeChanged; + } + + if (_sourceImage != null) + { + _sourceImage.ManipulationDelta -= SourceImage_ManipulationDelta; + } + + if (_maskAreaPath != null) + { + _maskAreaPath.Data = null; + } + + if (_topThumb != null) + { + _topThumb.ManipulationDelta -= ImageCropperThumb_ManipulationDelta; + _topThumb.ManipulationCompleted -= ImageCropperThumb_ManipulationCompleted; + _topThumb.KeyDown -= ImageCropperThumb_KeyDown; + _topThumb.KeyUp -= ImageCropperThumb_KeyUp; + } + + if (_bottomThumb != null) + { + _bottomThumb.ManipulationDelta -= ImageCropperThumb_ManipulationDelta; + _bottomThumb.ManipulationCompleted -= ImageCropperThumb_ManipulationCompleted; + _bottomThumb.KeyDown -= ImageCropperThumb_KeyDown; + _bottomThumb.KeyUp -= ImageCropperThumb_KeyUp; + } + + if (_leftThumb != null) + { + _leftThumb.ManipulationDelta -= ImageCropperThumb_ManipulationDelta; + _leftThumb.ManipulationCompleted += ImageCropperThumb_ManipulationCompleted; + _leftThumb.KeyDown -= ImageCropperThumb_KeyDown; + _leftThumb.KeyUp -= ImageCropperThumb_KeyUp; + } + + if (_rightThumb != null) + { + _rightThumb.ManipulationDelta -= ImageCropperThumb_ManipulationDelta; + _rightThumb.ManipulationCompleted -= ImageCropperThumb_ManipulationCompleted; + _rightThumb.KeyDown -= ImageCropperThumb_KeyDown; + _rightThumb.KeyUp -= ImageCropperThumb_KeyUp; + } + + if (_upperLeftThumb != null) + { + _upperLeftThumb.ManipulationDelta -= ImageCropperThumb_ManipulationDelta; + _upperLeftThumb.ManipulationCompleted -= ImageCropperThumb_ManipulationCompleted; + _upperLeftThumb.KeyDown -= ImageCropperThumb_KeyDown; + _upperLeftThumb.KeyUp -= ImageCropperThumb_KeyUp; + } + + if (_upperRightThumb != null) + { + _upperRightThumb.ManipulationDelta -= ImageCropperThumb_ManipulationDelta; + _upperRightThumb.ManipulationCompleted -= ImageCropperThumb_ManipulationCompleted; + _upperRightThumb.KeyDown -= ImageCropperThumb_KeyDown; + _upperRightThumb.KeyUp -= ImageCropperThumb_KeyUp; + } + + if (_lowerLeftThumb != null) + { + _lowerLeftThumb.ManipulationDelta -= ImageCropperThumb_ManipulationDelta; + _lowerLeftThumb.ManipulationCompleted -= ImageCropperThumb_ManipulationCompleted; + _lowerLeftThumb.KeyDown -= ImageCropperThumb_KeyDown; + _lowerLeftThumb.KeyUp -= ImageCropperThumb_KeyUp; + } + + if (_lowerRigthThumb != null) + { + _lowerRigthThumb.ManipulationDelta -= ImageCropperThumb_ManipulationDelta; + _lowerRigthThumb.ManipulationCompleted -= ImageCropperThumb_ManipulationCompleted; + _lowerRigthThumb.KeyDown -= ImageCropperThumb_KeyDown; + _lowerRigthThumb.KeyUp -= ImageCropperThumb_KeyUp; + } + } + + /// + protected override Size MeasureOverride(Size availableSize) + { + if (Source == null || Source.PixelWidth == 0 || Source.PixelHeight == 0) + { + return base.MeasureOverride(availableSize); + } + + if (double.IsInfinity(availableSize.Width) || double.IsInfinity(availableSize.Height)) + { + if (!double.IsInfinity(availableSize.Width)) + { + availableSize.Height = availableSize.Width / Source.PixelWidth * Source.PixelHeight; + } + else if (!double.IsInfinity(availableSize.Height)) + { + availableSize.Width = availableSize.Height / Source.PixelHeight * Source.PixelWidth; + } + else + { + availableSize.Width = Source.PixelWidth; + availableSize.Height = Source.PixelHeight; + } + + base.MeasureOverride(availableSize); + return availableSize; + } + + return base.MeasureOverride(availableSize); + } + + /// + /// Load an image from a file. + /// + /// The image file. + /// Task + public async Task LoadImageFromFile(StorageFile imageFile) + { + var writeableBitmap = new WriteableBitmap(1, 1); + using (var stream = await imageFile.OpenReadAsync()) + { + await writeableBitmap.SetSourceAsync(stream); + } + + Source = writeableBitmap; + } + + /// + /// Saves the cropped image to a stream with the specified format. + /// + /// The target stream. + /// the specified format. + /// Whether to always keep rectangular output. + /// Task + public async Task SaveAsync(IRandomAccessStream stream, BitmapFileFormat bitmapFileFormat, bool keepRectangularOutput = false) + { + if (Source == null) + { + return; + } + + if (keepRectangularOutput || CropShape == CropShape.Rectangular) + { + await CropImageAsync(Source, stream, _currentCroppedRect, bitmapFileFormat); + return; + } + + await CropImageWithShapeAsync(Source, stream, _currentCroppedRect, bitmapFileFormat, CropShape); + } + + /// + /// Reset the cropped area. + /// + public void Reset() + { + InitImageLayout(true); + } + + /// + /// Tries to set a new value for the cropped region, returns true if it succeeded, false if the region is invalid + /// + /// The new cropped region. + /// bool + public bool TrySetCroppedRegion(Rect rect) + { + // Reject regions smaller than the minimum size + if (rect.Width < MinCropSize.Width || rect.Height < MinCropSize.Height) + { + return false; + } + + // Reject regions that are not contained in the original picture + if (rect.Left < _restrictedCropRect.Left || rect.Top < _restrictedCropRect.Top || rect.Right > _restrictedCropRect.Right || rect.Bottom > _restrictedCropRect.Bottom) + { + return false; + } + + // If an aspect ratio is set, reject regions that don't respect it + // If cropping a circle, reject regions where the aspect ratio is not 1 + if (KeepAspectRatio && ActualAspectRatio != rect.Width / rect.Height) + { + return false; + } + + _currentCroppedRect = rect; + if (TryUpdateImageLayout(true)) + { + UpdateSelectionThumbs(true); + UpdateMaskArea(true); + } + + return true; + } +} diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.xaml b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.xaml new file mode 100644 index 000000000..01db97744 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.xaml @@ -0,0 +1,74 @@ + + + + + + + + + + diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropperThumb.cs b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropperThumb.cs new file mode 100644 index 000000000..0b04df02c --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropperThumb.cs @@ -0,0 +1,137 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ReSharper disable PartialTypeWithSinglePart +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// The control is used for . +/// +public partial class ImageCropperThumb : Control +{ + private readonly TranslateTransform _layoutTransform = new(); + internal const string NormalState = "Normal"; + internal const string PointerOverState = "PointerOver"; + internal const string PressedState = "Pressed"; + internal const string DisabledState = "Disabled"; + internal ThumbPosition Position { get; set; } + + /// + /// Gets or sets the X coordinate of the ImageCropperThumb. + /// + public double X + { + get { return (double)GetValue(XProperty); } + set { SetValue(XProperty, value); } + } + + /// + /// Gets or sets the Y coordinate of the ImageCropperThumb. + /// + public double Y + { + get { return (double)GetValue(YProperty); } + set { SetValue(YProperty, value); } + } + + /// + /// Initializes a new instance of the class. + /// + public ImageCropperThumb() + { + DefaultStyleKey = typeof(ImageCropperThumb); + RenderTransform = _layoutTransform; + ManipulationMode = ManipulationModes.TranslateX | ManipulationModes.TranslateY; + SizeChanged += ImageCropperThumb_SizeChanged; + } + + protected override void OnApplyTemplate() + { + PointerEntered -= Control_PointerEntered; + PointerExited -= Control_PointerExited; + PointerCaptureLost -= Control_PointerCaptureLost; + PointerCanceled -= Control_PointerCanceled; + + PointerEntered += Control_PointerEntered; + PointerExited += Control_PointerExited; + PointerCaptureLost += Control_PointerCaptureLost; + PointerCanceled += Control_PointerCanceled; + } + + private void ImageCropperThumb_SizeChanged(object sender, SizeChangedEventArgs e) + { + UpdatePosition(); + } + + private void UpdatePosition() + { + if (_layoutTransform != null) + { + _layoutTransform.X = X - (this.ActualWidth / 2); + _layoutTransform.Y = Y - (this.ActualHeight / 2); + } + } + + private static void OnXChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var target = (ImageCropperThumb)d; + target.UpdatePosition(); + } + + + + private static void OnYChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var target = (ImageCropperThumb)d; + target.UpdatePosition(); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty XProperty = + DependencyProperty.Register(nameof(X), typeof(double), typeof(ImageCropperThumb), new PropertyMetadata(0d, OnXChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty YProperty = + DependencyProperty.Register(nameof(Y), typeof(double), typeof(ImageCropperThumb), new PropertyMetadata(0d, OnYChanged)); + + public void Control_PointerEntered(object sender, PointerRoutedEventArgs e) + { + base.OnPointerEntered(e); + VisualStateManager.GoToState(this, PointerOverState, true); + } + + public void Control_PointerExited(object sender, PointerRoutedEventArgs e) + { + base.OnPointerExited(e); + VisualStateManager.GoToState(this, NormalState, true); + } + + private void Control_PointerCaptureLost(object sender, PointerRoutedEventArgs e) + { + base.OnPointerCaptureLost(e); + VisualStateManager.GoToState(this, NormalState, true); + } + + private void Control_PointerCanceled(object sender, PointerRoutedEventArgs e) + { + base.OnPointerCanceled(e); + VisualStateManager.GoToState(this, NormalState, true); + } + + protected override void OnPointerPressed(PointerRoutedEventArgs e) + { + base.OnPointerPressed(e); + VisualStateManager.GoToState(this, PressedState, true); + } + + protected override void OnPointerReleased(PointerRoutedEventArgs e) + { + base.OnPointerReleased(e); + VisualStateManager.GoToState(this, NormalState, true); + } +} diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropperThumb.xaml b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropperThumb.xaml new file mode 100644 index 000000000..6a5398703 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropperThumb.xaml @@ -0,0 +1,67 @@ + + + + + + + + + + diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/ReadMe.md b/Hi3Helper.CommunityToolkit/ImageCropper/ReadMe.md new file mode 100644 index 000000000..07942198d --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/ReadMe.md @@ -0,0 +1,38 @@ + +# Windows Community Toolkit - ImageCropper + +This package is part of the [Windows Community Toolkit](https://aka.ms/toolkit/windows) from the [.NET Foundation](https://dotnetfoundation.org). + +## Package Contents + +This package contains the following controls in the `CommunityToolkit.WinUI.Controls` namespace: + +- ImageCropper + +## Which Package is for me? + +If you're developing with _UWP/WinUI 2 or Uno.UI_ you should be using the `CommunityToolkit.Uwp.Controls.ImageCropper` package. + +If you're developing with _WindowsAppSDK/WinUI 3 or Uno.WinUI_ you should be using the `CommunityToolkit.WinUI.Controls.ImageCropper` package. + +## WinUI Resources (UWP) + +For UWP projects, the WinUI 2 reference requires you include the WinUI XAML Resources in your App.xaml file: + +```xml + + + +``` + +See [Getting Started in WinUI 2](https://learn.microsoft.com/windows/apps/winui/winui2/getting-started) for more information. + +## Documentation + +Further documentation about these components can be found at: https://aka.ms/windowstoolkitdocs + +## License + +MIT + +See License.md in package for more details. diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/Themes/Generic.xaml b/Hi3Helper.CommunityToolkit/ImageCropper/Themes/Generic.xaml new file mode 100644 index 000000000..d39cc8fd0 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/Themes/Generic.xaml @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/ThumbPlacement.cs b/Hi3Helper.CommunityToolkit/ImageCropper/ThumbPlacement.cs new file mode 100644 index 000000000..021365593 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/ThumbPlacement.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// Thumb placement enumeration. +/// Default is +/// +public enum ThumbPlacement +{ + /// + /// Shows the thumbs in all four corners, top, bottom, left and right. + /// + All, + + /// + /// Shows the thumbs in all four corners. + /// + Corners +} diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/ThumbPosition.cs b/Hi3Helper.CommunityToolkit/ImageCropper/ThumbPosition.cs new file mode 100644 index 000000000..0f73feada --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/ThumbPosition.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// Thumb position enumeration. +/// +internal enum ThumbPosition +{ + Top, + Bottom, + Left, + Right, + UpperLeft, + UpperRight, + LowerLeft, + LowerRight +} diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/packages.lock.json b/Hi3Helper.CommunityToolkit/ImageCropper/packages.lock.json new file mode 100644 index 000000000..9e35d4605 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/packages.lock.json @@ -0,0 +1,116 @@ +{ + "version": 1, + "dependencies": { + "net9.0-windows10.0.22621": { + "CommunityToolkit.WinUI.Extensions": { + "type": "Direct", + "requested": "[8.1.240916, )", + "resolved": "8.1.240916", + "contentHash": "y8FEzoy5HF3dBkxe/pU87hlh0QACtGZhnv881x2SFq9fvDjukLQz6VpoOkjxSZjEILmYu1NoI/tYjqXlETRGlw==", + "dependencies": { + "CommunityToolkit.Common": "8.2.1", + "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", + "Microsoft.WindowsAppSDK": "1.5.240311000" + } + }, + "CommunityToolkit.WinUI.Media": { + "type": "Direct", + "requested": "[8.1.240916, )", + "resolved": "8.1.240916", + "contentHash": "ghd4u81dNf5F5knVkww9QDUy03L05TvMHPlUXKOVFER0bl8A3K6I0eYi0bUNtR8VTB7giPy8DFh50t3SJjswrA==", + "dependencies": { + "CommunityToolkit.WinUI.Animations": "8.1.240916", + "CommunityToolkit.WinUI.Extensions": "8.1.240916", + "Microsoft.Graphics.Win2D": "1.1.1", + "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", + "Microsoft.WindowsAppSDK": "1.5.240311000" + } + }, + "Microsoft.Graphics.Win2D": { + "type": "Direct", + "requested": "[1.2.1-experimental2, )", + "resolved": "1.2.1-experimental2", + "contentHash": "jcihghoq78imitWG52m5k3gkPt+GrWTU/CkujU8o2bBHJY982/MG7YpWVOsuA+RKGAn+WIm/OklR99bXZkJjUw==", + "dependencies": { + "Microsoft.WindowsAppSDK": "1.6.240531000-experimental1" + } + }, + "Microsoft.NET.ILLink.Tasks": { + "type": "Direct", + "requested": "[9.0.0-rc.1.24431.7, )", + "resolved": "9.0.0-rc.1.24431.7", + "contentHash": "B8jHIIszeUdDY+O4G1/xPOuhrriwsv9Z8/u3XSOAqc8Z0mQCeLBKg2oLGElDZeRF8wWXDGklAKY3f0IHopyXkQ==" + }, + "Microsoft.Web.WebView2": { + "type": "Direct", + "requested": "[1.0.2783-prerelease, )", + "resolved": "1.0.2783-prerelease", + "contentHash": "sPxd49RLVnOyzplXTvdU9z6NIx0t56MridYoZhw8BOBZt564Ufi168Wfg5G0rIc5EKWmrGihLeZ1OfmjQiqWTw==" + }, + "Microsoft.Windows.CsWinRT": { + "type": "Direct", + "requested": "[2.1.3, )", + "resolved": "2.1.3", + "contentHash": "Nl8A4rQ4l2GNj703GvLSbr0Vo++FjxKxU7CIj1pcKz/sN8XSvD4dIvUCYYgD16o2pG4PSSXNgAxfwDUwLGHLPA==" + }, + "Microsoft.Windows.SDK.BuildTools": { + "type": "Direct", + "requested": "[10.0.26100.1742, )", + "resolved": "10.0.26100.1742", + "contentHash": "ypcHjr4KEi6xQhgClnbXoANHcyyX/QsC4Rky4igs6M4GiDa+weegPo8JuV/VMxqrZCV4zlqDsp2krgkN7ReAAg==" + }, + "Microsoft.WindowsAppSDK": { + "type": "Direct", + "requested": "[1.6.240829007, )", + "resolved": "1.6.240829007", + "contentHash": "Ij0jXARFOlRY+n7M32ZS9Kbf+x7gmnhWVnMP3eKHe83eu/2pzh9f4gjmoZ5fAgoZaKWCa2PvcxzHx0gtRXcKRQ==", + "dependencies": { + "Microsoft.Web.WebView2": "1.0.2651.64", + "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756" + } + }, + "CommunityToolkit.Common": { + "type": "Transitive", + "resolved": "8.2.1", + "contentHash": "LWuhy8cQKJ/MYcy3XafJ916U3gPH/YDvYoNGWyQWN11aiEKCZszzPOTJAOvBjP9yG8vHmIcCyPUt4L82OK47Iw==" + }, + "CommunityToolkit.WinUI.Animations": { + "type": "Transitive", + "resolved": "8.1.240916", + "contentHash": "jHYltimsKSSrYAij1TJC4VXzWZ6RaKCOWzkdDPenkjAcvE6lhSdX68eH6nNRIwZZC9lMpDy2Fba4uAJ1vMt9Ew==", + "dependencies": { + "CommunityToolkit.WinUI.Extensions": "8.1.240916", + "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", + "Microsoft.WindowsAppSDK": "1.5.240311000" + } + } + }, + "net9.0-windows10.0.22621/win-x64": { + "Microsoft.Graphics.Win2D": { + "type": "Direct", + "requested": "[1.2.1-experimental2, )", + "resolved": "1.2.1-experimental2", + "contentHash": "jcihghoq78imitWG52m5k3gkPt+GrWTU/CkujU8o2bBHJY982/MG7YpWVOsuA+RKGAn+WIm/OklR99bXZkJjUw==", + "dependencies": { + "Microsoft.WindowsAppSDK": "1.6.240531000-experimental1" + } + }, + "Microsoft.Web.WebView2": { + "type": "Direct", + "requested": "[1.0.2783-prerelease, )", + "resolved": "1.0.2783-prerelease", + "contentHash": "sPxd49RLVnOyzplXTvdU9z6NIx0t56MridYoZhw8BOBZt564Ufi168Wfg5G0rIc5EKWmrGihLeZ1OfmjQiqWTw==" + }, + "Microsoft.WindowsAppSDK": { + "type": "Direct", + "requested": "[1.6.240829007, )", + "resolved": "1.6.240829007", + "contentHash": "Ij0jXARFOlRY+n7M32ZS9Kbf+x7gmnhWVnMP3eKHe83eu/2pzh9f4gjmoZ5fAgoZaKWCa2PvcxzHx0gtRXcKRQ==", + "dependencies": { + "Microsoft.Web.WebView2": "1.0.2651.64", + "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756" + } + } + } + } +} \ No newline at end of file diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/Helpers/ControlHelper.cs b/Hi3Helper.CommunityToolkit/SettingsControls/Helpers/ControlHelper.cs new file mode 100644 index 000000000..7beb6139b --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/Helpers/ControlHelper.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; +internal static class ControlHelpers +{ + internal static bool IsXamlRootAvailable { get; } = Windows.Foundation.Metadata.ApiInformation.IsPropertyPresent("Microsoft.UI.Xaml.UIElement", nameof(UIElement.XamlRoot)); +} diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/Helpers/CornerRadiusConverter.cs b/Hi3Helper.CommunityToolkit/SettingsControls/Helpers/CornerRadiusConverter.cs new file mode 100644 index 000000000..9ce6bf9da --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/Helpers/CornerRadiusConverter.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ReSharper disable PartialTypeWithSinglePart +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; +internal partial class CornerRadiusConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, string language) + { + if (value is CornerRadius cornerRadius) + { + return new CornerRadius(0, 0, cornerRadius.BottomRight, cornerRadius.BottomLeft); + } + else + { + return value; + } + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + return value; + } +} diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/Helpers/ResourceDictionaryExtensions.cs b/Hi3Helper.CommunityToolkit/SettingsControls/Helpers/ResourceDictionaryExtensions.cs new file mode 100644 index 000000000..8cee3d9ed --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/Helpers/ResourceDictionaryExtensions.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +// Adapted from https://github.com/rudyhuyn/XamlPlus +internal static class ResourceDictionaryExtensions +{ + /// + /// Copies the provided as a parameter into the calling dictionary, includes overwriting the source location, theme dictionaries, and merged dictionaries. + /// + /// ResourceDictionary to copy values to. + /// ResourceDictionary to copy values from. + internal static void CopyFrom(this ResourceDictionary destination, ResourceDictionary source) + { + if (source.Source != null) + { + destination.Source = source.Source; + } + else + { + // Clone theme dictionaries + if (source.ThemeDictionaries != null) + { + foreach (var theme in source.ThemeDictionaries) + { + if (theme.Value is ResourceDictionary themedResource) + { + var themeDictionary = new ResourceDictionary(); + themeDictionary.CopyFrom(themedResource); + destination.ThemeDictionaries[theme.Key] = themeDictionary; + } + else + { + destination.ThemeDictionaries[theme.Key] = theme.Value; + } + } + } + + // Clone merged dictionaries + if (source.MergedDictionaries != null) + { + foreach (var mergedResource in source.MergedDictionaries) + { + var themeDictionary = new ResourceDictionary(); + themeDictionary.CopyFrom(mergedResource); + destination.MergedDictionaries.Add(themeDictionary); + } + } + + // Clone all contents + foreach (var item in source) + { + destination[item.Key] = item.Value; + } + } + } +} diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/Helpers/StyleExtensions.cs b/Hi3Helper.CommunityToolkit/SettingsControls/Helpers/StyleExtensions.cs new file mode 100644 index 000000000..d678c15a3 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/Helpers/StyleExtensions.cs @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +// Adapted from https://github.com/rudyhuyn/XamlPlus +public static class StyleExtensions +{ + // Used to distinct normal ResourceDictionary and the one we add. + private sealed class StyleExtensionResourceDictionary : ResourceDictionary + { + } + + public static ResourceDictionary GetResources(Style obj) + { + return (ResourceDictionary)obj.GetValue(ResourcesProperty); + } + + public static void SetResources(Style obj, ResourceDictionary value) + { + obj.SetValue(ResourcesProperty, value); + } + + public static readonly DependencyProperty ResourcesProperty = + DependencyProperty.RegisterAttached("Resources", typeof(ResourceDictionary), typeof(StyleExtensions), new PropertyMetadata(null, ResourcesChanged)); + + private static void ResourcesChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) + { + if (!(sender is FrameworkElement frameworkElement)) + { + return; + } + + var mergedDictionaries = frameworkElement.Resources?.MergedDictionaries; + if (mergedDictionaries == null) + { + return; + } + + var existingResourceDictionary = + mergedDictionaries.FirstOrDefault(c => c is StyleExtensionResourceDictionary); + if (existingResourceDictionary != null) + { + // Remove the existing resource dictionary + mergedDictionaries.Remove(existingResourceDictionary); + } + + if (e.NewValue is ResourceDictionary resource) + { + var clonedResources = new StyleExtensionResourceDictionary(); + clonedResources.CopyFrom(resource); + mergedDictionaries.Add(clonedResources); + } + + if (frameworkElement.IsLoaded) + { + // Only force if the style was applied after the control was loaded + ForceControlToReloadThemeResources(frameworkElement); + } + } + + private static void ForceControlToReloadThemeResources(FrameworkElement frameworkElement) + { + // To force the refresh of all resource references. + // Note: Doesn't work when in high-contrast. + var currentRequestedTheme = frameworkElement.RequestedTheme; + frameworkElement.RequestedTheme = currentRequestedTheme == ElementTheme.Dark + ? ElementTheme.Light + : ElementTheme.Dark; + frameworkElement.RequestedTheme = currentRequestedTheme; + } +} diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/Hi3Helper.CommunityToolkit.WinUI.Controls.SettingsControls.csproj b/Hi3Helper.CommunityToolkit/SettingsControls/Hi3Helper.CommunityToolkit.WinUI.Controls.SettingsControls.csproj new file mode 100644 index 000000000..0007404cc --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/Hi3Helper.CommunityToolkit.WinUI.Controls.SettingsControls.csproj @@ -0,0 +1,71 @@ + + + + + x64 + net9.0-windows10.0.22621.0 + 10.0.22621.41 + 10.0.17763.0 + win-x64 + true + true + Library + 12.0 + portable + enable + + Hi3Helper.CommunityToolkit.WinUI.Controls + Hi3Helper.CommunityToolkit.WinUI.Controls.SettingsControls + SettingsControls + SettingsControls + 1.0.0 + https://github.com/CollapseLauncher/Collapse + enable + disable + WINAPPSDK;WINUI3 + Debug;Release + true + win-x64 + + + + full + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/MultiTarget.props b/Hi3Helper.CommunityToolkit/SettingsControls/MultiTarget.props new file mode 100644 index 000000000..fb4d71c7c --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/MultiTarget.props @@ -0,0 +1,9 @@ + + + + wasdk; + + \ No newline at end of file diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/ReadMe.md b/Hi3Helper.CommunityToolkit/SettingsControls/ReadMe.md new file mode 100644 index 000000000..a12ae1fa5 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/ReadMe.md @@ -0,0 +1,39 @@ + +# Windows Community Toolkit - SettingsControls + +This package is part of the [Windows Community Toolkit](https://aka.ms/toolkit/windows) from the [.NET Foundation](https://dotnetfoundation.org). + +## Package Contents + +This package contains the following controls in the `CommunityToolkit.WinUI.Controls` namespace: + +- SettingsCard +- SettingsExpander + +## Which Package is for me? + +If you're developing with _UWP/WinUI 2 or Uno.UI_ you should be using the `CommunityToolkit.Uwp.Controls.SettingsControls` package. + +If you're developing with _WindowsAppSDK/WinUI 3 or Uno.WinUI_ you should be using the `CommunityToolkit.WinUI.Controls.SettingsControls` package. + +## WinUI Resources (UWP) + +For UWP projects, the WinUI 2 reference requires you include the WinUI XAML Resources in your App.xaml file: + +```xml + + + +``` + +See [Getting Started in WinUI 2](https://learn.microsoft.com/windows/apps/winui/winui2/getting-started) for more information. + +## Documentation + +Further documentation about these components can be found at: https://aka.ms/windowstoolkitdocs + +## License + +MIT + +See License.md in package for more details. diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/SettingsCard/SettingsCard.Properties.cs b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsCard/SettingsCard.Properties.cs new file mode 100644 index 000000000..d057a8319 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsCard/SettingsCard.Properties.cs @@ -0,0 +1,182 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ReSharper disable RedundantExtendsListEntry +// ReSharper disable PartialTypeWithSinglePart +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +public partial class SettingsCard : ButtonBase +{ + /// + /// The backing for the property. + /// + public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register( + nameof(Header), + typeof(object), + typeof(SettingsCard), + new PropertyMetadata(defaultValue: null, (d, e) => ((SettingsCard)d).OnHeaderPropertyChanged(e.OldValue, e.NewValue))); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register( + nameof(Description), + typeof(object), + typeof(SettingsCard), + new PropertyMetadata(defaultValue: null, (d, e) => ((SettingsCard)d).OnDescriptionPropertyChanged(e.OldValue, e.NewValue))); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty HeaderIconProperty = DependencyProperty.Register( + nameof(HeaderIcon), + typeof(IconElement), + typeof(SettingsCard), + new PropertyMetadata(defaultValue: null, (d, e) => ((SettingsCard)d).OnHeaderIconPropertyChanged((IconElement)e.OldValue, (IconElement)e.NewValue))); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty ActionIconProperty = DependencyProperty.Register( + nameof(ActionIcon), + typeof(IconElement), + typeof(SettingsCard), + new PropertyMetadata(defaultValue: "\ue974")); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty ActionIconToolTipProperty = DependencyProperty.Register( + nameof(ActionIconToolTip), + typeof(string), + typeof(SettingsCard), + new PropertyMetadata(defaultValue: null)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty IsClickEnabledProperty = DependencyProperty.Register( + nameof(IsClickEnabled), + typeof(bool), + typeof(SettingsCard), + new PropertyMetadata(defaultValue: false, (d, e) => ((SettingsCard)d).OnIsClickEnabledPropertyChanged((bool)e.OldValue, (bool)e.NewValue))); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty ContentAlignmentProperty = DependencyProperty.Register( + nameof(ContentAlignment), + typeof(ContentAlignment), + typeof(SettingsCard), + new PropertyMetadata(defaultValue: ContentAlignment.Right)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty IsActionIconVisibleProperty = DependencyProperty.Register( + nameof(IsActionIconVisible), + typeof(bool), + typeof(SettingsCard), + new PropertyMetadata(defaultValue: true, (d, e) => ((SettingsCard)d).OnIsActionIconVisiblePropertyChanged((bool)e.OldValue, (bool)e.NewValue))); + + /// + /// Gets or sets the Header. + /// + public object Header + { + get => GetValue(HeaderProperty); + set => SetValue(HeaderProperty, value); + } + + /// + /// Gets or sets the description. + /// +#pragma warning disable CS0109 // Member does not hide an inherited member; new keyword is not required + public new object Description +#pragma warning restore CS0109 // Member does not hide an inherited member; new keyword is not required + { + get => GetValue(DescriptionProperty); + set => SetValue(DescriptionProperty, value); + } + + /// + /// Gets or sets the icon on the left. + /// + public IconElement HeaderIcon + { + get => (IconElement)GetValue(HeaderIconProperty); + set => SetValue(HeaderIconProperty, value); + } + + /// + /// Gets or sets the icon that is shown when IsClickEnabled is set to true. + /// + public IconElement ActionIcon + { + get => (IconElement)GetValue(ActionIconProperty); + set => SetValue(ActionIconProperty, value); + } + + /// + /// Gets or sets the tooltip of the ActionIcon. + /// + public string ActionIconToolTip + { + get => (string)GetValue(ActionIconToolTipProperty); + set => SetValue(ActionIconToolTipProperty, value); + } + + /// + /// Gets or sets if the card can be clicked. + /// + public bool IsClickEnabled + { + get => (bool)GetValue(IsClickEnabledProperty); + set => SetValue(IsClickEnabledProperty, value); + } + + /// + /// Gets or sets the alignment of the Content + /// + public ContentAlignment ContentAlignment + { + get => (ContentAlignment)GetValue(ContentAlignmentProperty); + set => SetValue(ContentAlignmentProperty, value); + } + + /// + /// Gets or sets if the ActionIcon is shown. + /// + public bool IsActionIconVisible + { + get => (bool)GetValue(IsActionIconVisibleProperty); + set => SetValue(IsActionIconVisibleProperty, value); + } + + protected virtual void OnIsClickEnabledPropertyChanged(bool oldValue, bool newValue) => OnIsClickEnabledChanged(); + + protected virtual void OnHeaderIconPropertyChanged(IconElement oldValue, IconElement newValue) => OnHeaderIconChanged(); + + protected virtual void OnHeaderPropertyChanged(object oldValue, object newValue) => OnHeaderChanged(); + + protected virtual void OnDescriptionPropertyChanged(object oldValue, object newValue) => OnDescriptionChanged(); + + protected virtual void OnIsActionIconVisiblePropertyChanged(bool oldValue, bool newValue) => OnActionIconChanged(); +} + +public enum ContentAlignment +{ + /// + /// The Content is aligned to the right. Default state. + /// + Right, + /// + /// The Content is left-aligned while the Header, HeaderIcon and Description are collapsed. This is commonly used for Content types such as CheckBoxes, RadioButtons and custom layouts. + /// + Left, + /// + /// The Content is vertically aligned. + /// + Vertical +} diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/SettingsCard/SettingsCard.cs b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsCard/SettingsCard.cs new file mode 100644 index 000000000..a3f6ea647 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsCard/SettingsCard.cs @@ -0,0 +1,294 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ReSharper disable RedundantExtendsListEntry +// ReSharper disable PartialTypeWithSinglePart +#nullable enable +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// This is the base control to create consistent settings experiences, inline with the Windows 11 design language. +/// A SettingsCard can also be hosted within a SettingsExpander. +/// + +[TemplatePart(Name = ActionIconPresenterHolder, Type = typeof(Viewbox))] +[TemplatePart(Name = HeaderPresenter, Type = typeof(ContentPresenter))] +[TemplatePart(Name = DescriptionPresenter, Type = typeof(ContentPresenter))] +[TemplatePart(Name = HeaderIconPresenterHolder, Type = typeof(Viewbox))] + +[TemplateVisualState(Name = NormalState, GroupName = CommonStates)] +[TemplateVisualState(Name = PointerOverState, GroupName = CommonStates)] +[TemplateVisualState(Name = PressedState, GroupName = CommonStates)] +[TemplateVisualState(Name = DisabledState, GroupName = CommonStates)] + +[TemplateVisualState(Name = RightState, GroupName = ContentAlignmentStates)] +[TemplateVisualState(Name = RightWrappedState, GroupName = ContentAlignmentStates)] +[TemplateVisualState(Name = RightWrappedNoIconState, GroupName = ContentAlignmentStates)] +[TemplateVisualState(Name = LeftState, GroupName = ContentAlignmentStates)] +[TemplateVisualState(Name = VerticalState, GroupName = ContentAlignmentStates)] + +[TemplateVisualState(Name = NoContentSpacingState, GroupName = ContentSpacingStates)] +[TemplateVisualState(Name = ContentSpacingState, GroupName = ContentSpacingStates)] + +public partial class SettingsCard : ButtonBase +{ + internal const string CommonStates = "CommonStates"; + internal const string NormalState = "Normal"; + internal const string PointerOverState = "PointerOver"; + internal const string PressedState = "Pressed"; + internal const string DisabledState = "Disabled"; + + internal const string ContentAlignmentStates = "ContentAlignmentStates"; + internal const string RightState = "Right"; + internal const string RightWrappedState = "RightWrapped"; + internal const string RightWrappedNoIconState = "RightWrappedNoIcon"; + internal const string LeftState = "Left"; + internal const string VerticalState = "Vertical"; + + internal const string ContentSpacingStates = "ContentSpacingStates"; + internal const string NoContentSpacingState = "NoContentSpacing"; + internal const string ContentSpacingState = "ContentSpacing"; + + internal const string ActionIconPresenterHolder = "PART_ActionIconPresenterHolder"; + internal const string HeaderPresenter = "PART_HeaderPresenter"; + internal const string DescriptionPresenter = "PART_DescriptionPresenter"; + internal const string HeaderIconPresenterHolder = "PART_HeaderIconPresenterHolder"; + + + /// + /// Creates a new instance of the class. + /// + public SettingsCard() + { + DefaultStyleKey = typeof(SettingsCard); + } + + /// + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + IsEnabledChanged -= OnIsEnabledChanged; + OnActionIconChanged(); + OnHeaderChanged(); + OnHeaderIconChanged(); + OnDescriptionChanged(); + OnIsClickEnabledChanged(); + CheckInitialVisualState(); + SetAccessibleContentName(); + RegisterPropertyChangedCallback(ContentProperty, OnContentChanged); + IsEnabledChanged += OnIsEnabledChanged; + } + + private void CheckInitialVisualState() + { + VisualStateManager.GoToState(this, IsEnabled ? NormalState : DisabledState, true); + + if (GetTemplateChild("ContentAlignmentStates") is VisualStateGroup contentAlignmentStatesGroup) + { + contentAlignmentStatesGroup.CurrentStateChanged -= ContentAlignmentStates_Changed; + CheckVerticalSpacingState(contentAlignmentStatesGroup.CurrentState); + contentAlignmentStatesGroup.CurrentStateChanged += ContentAlignmentStates_Changed; + } + } + + // We automatically set the AutomationProperties.Name of the Content if not configured. + private void SetAccessibleContentName() + { + if (Header is string headerString && headerString != string.Empty) + { + // We don't want to override an AutomationProperties.Name that is manually set, or if the Content basetype is of type ButtonBase (the ButtonBase.Content will be used then) + if (Content is UIElement element && string.IsNullOrEmpty(AutomationProperties.GetName(element)) && element.GetType().BaseType != typeof(ButtonBase) && element.GetType() != typeof(TextBlock)) + { + AutomationProperties.SetName(element, headerString); + } + } + } + + private void EnableButtonInteraction() + { + DisableButtonInteraction(); + + IsTabStop = true; + PointerEntered += Control_PointerEntered; + PointerExited += Control_PointerExited; + PointerCaptureLost += Control_PointerCaptureLost; + PointerCanceled += Control_PointerCanceled; + PreviewKeyDown += Control_PreviewKeyDown; + PreviewKeyUp += Control_PreviewKeyUp; + } + + private void DisableButtonInteraction() + { + IsTabStop = false; + PointerEntered -= Control_PointerEntered; + PointerExited -= Control_PointerExited; + PointerCaptureLost -= Control_PointerCaptureLost; + PointerCanceled -= Control_PointerCanceled; + PreviewKeyDown -= Control_PreviewKeyDown; + PreviewKeyUp -= Control_PreviewKeyUp; + } + + private void Control_PreviewKeyUp(object sender, KeyRoutedEventArgs e) + { + if (e.Key == VirtualKey.Enter || e.Key == VirtualKey.Space || e.Key == VirtualKey.GamepadA) + { + VisualStateManager.GoToState(this, NormalState, true); + } + } + + private void Control_PreviewKeyDown(object sender, KeyRoutedEventArgs e) + { + if (e.Key == VirtualKey.Enter || e.Key == VirtualKey.Space || e.Key == VirtualKey.GamepadA) + { + // Check if the active focus is on the card itself - only then we show the pressed state. + if (GetFocusedElement() is SettingsCard) + { + VisualStateManager.GoToState(this, PressedState, true); + } + } + } + + public void Control_PointerEntered(object sender, PointerRoutedEventArgs e) + { + base.OnPointerEntered(e); + VisualStateManager.GoToState(this, PointerOverState, true); + } + + public void Control_PointerExited(object sender, PointerRoutedEventArgs e) + { + base.OnPointerExited(e); + VisualStateManager.GoToState(this, NormalState, true); + } + + private void Control_PointerCaptureLost(object sender, PointerRoutedEventArgs e) + { + base.OnPointerCaptureLost(e); + VisualStateManager.GoToState(this, NormalState, true); + } + + private void Control_PointerCanceled(object sender, PointerRoutedEventArgs e) + { + base.OnPointerCanceled(e); + VisualStateManager.GoToState(this, NormalState, true); + } + + protected override void OnPointerPressed(PointerRoutedEventArgs e) + { + // e.Handled = true; + if (IsClickEnabled) + { + base.OnPointerPressed(e); + VisualStateManager.GoToState(this, PressedState, true); + } + } + + protected override void OnPointerReleased(PointerRoutedEventArgs e) + { + if (IsClickEnabled) + { + base.OnPointerReleased(e); + VisualStateManager.GoToState(this, NormalState, true); + } + } + + /// + /// Creates AutomationPeer + /// + /// An automation peer for . + protected override AutomationPeer OnCreateAutomationPeer() => new SettingsCardAutomationPeer(this); + + private void OnIsClickEnabledChanged() + { + OnActionIconChanged(); + if (IsClickEnabled) + { + EnableButtonInteraction(); + } + else + { + DisableButtonInteraction(); + } + } + + private void OnIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e) + { + VisualStateManager.GoToState(this, IsEnabled ? NormalState : DisabledState, true); + } + + private void OnActionIconChanged() + { + if (GetTemplateChild(ActionIconPresenterHolder) is FrameworkElement actionIconPresenter) + { + if (IsClickEnabled && IsActionIconVisible) + { + actionIconPresenter.Visibility = Visibility.Visible; + } + else + { + actionIconPresenter.Visibility =Visibility.Collapsed; + } + } + } + + private void OnHeaderIconChanged() + { + if (GetTemplateChild(HeaderIconPresenterHolder) is FrameworkElement headerIconPresenter) + { + headerIconPresenter.Visibility = HeaderIcon != null + ? Visibility.Visible + : Visibility.Collapsed; + } + } + + private void OnDescriptionChanged() + { + if (GetTemplateChild(DescriptionPresenter) is FrameworkElement descriptionPresenter) + { + descriptionPresenter.Visibility = IsNullOrEmptyString(Description) + ? Visibility.Collapsed + : Visibility.Visible; + } + + } + + private void OnHeaderChanged() + { + if (GetTemplateChild(HeaderPresenter) is FrameworkElement headerPresenter) + { + headerPresenter.Visibility = IsNullOrEmptyString(Header) + ? Visibility.Collapsed + : Visibility.Visible; + } + + } + + private void ContentAlignmentStates_Changed(object sender, VisualStateChangedEventArgs e) => CheckVerticalSpacingState(e.NewState); + + private void CheckVerticalSpacingState(VisualState? s) + { + // On state change, checking if the Content should be wrapped (e.g. when the card is made smaller or the ContentAlignment is set to Vertical). If the Content and the Header or Description are not null, we add spacing between the Content and the Header/Description. + + if (s != null && s.Name is RightWrappedState or RightWrappedNoIconState or VerticalState && Content != null && (!IsNullOrEmptyString(Header) || !IsNullOrEmptyString(Description))) + { + VisualStateManager.GoToState(this, ContentSpacingState, true); + } + else + { + VisualStateManager.GoToState(this, NoContentSpacingState, true); + } + } + + private FrameworkElement? GetFocusedElement() + { + if (ControlHelpers.IsXamlRootAvailable && XamlRoot != null) + { + return FocusManager.GetFocusedElement(XamlRoot) as FrameworkElement; + } + + return FocusManager.GetFocusedElement() as FrameworkElement; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsNullOrEmptyString(object obj) => obj is string objString && string.IsNullOrEmpty(objString); +} diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/SettingsCard/SettingsCard.xaml b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsCard/SettingsCard.xaml new file mode 100644 index 000000000..7c9d2d9b0 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsCard/SettingsCard.xaml @@ -0,0 +1,1080 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 16,16,16,16 + 148 + 68 + 12 + 20 + 0 + 120 + 2,0,20,0 + 14,0,0,0 + 13 + 8 + 476 + 286 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/SettingsCard/SettingsCardAutomationPeer.cs b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsCard/SettingsCardAutomationPeer.cs new file mode 100644 index 000000000..f59bf8308 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsCard/SettingsCardAutomationPeer.cs @@ -0,0 +1,70 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ReSharper disable RedundantExtendsListEntry +// ReSharper disable PartialTypeWithSinglePart +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// AutomationPeer for SettingsCard +/// +public partial class SettingsCardAutomationPeer : FrameworkElementAutomationPeer +{ + /// + /// Initializes a new instance of the class. + /// + /// SettingsCard + public SettingsCardAutomationPeer(SettingsCard owner) + : base(owner) + { + } + + /// + /// Gets the control type for the element that is associated with the UI Automation peer. + /// + /// The control type. + protected override AutomationControlType GetAutomationControlTypeCore() + { + if (Owner is SettingsCard settingsCard && settingsCard.IsClickEnabled) + { + return AutomationControlType.Button; + } + else + { + return AutomationControlType.Group; + } + } + + /// + /// Called by GetClassName that gets a human readable name that, in addition to AutomationControlType, + /// differentiates the control represented by this AutomationPeer. + /// + /// The string that contains the name. + protected override string GetClassNameCore() + { + return Owner.GetType().Name; + } + + protected override string GetNameCore() + { + // We only want to announce the button card name if it is clickable, else it's just a regular card that does not receive focus + if (Owner is SettingsCard owner && owner.IsClickEnabled) + { + string name = AutomationProperties.GetName(owner); + if (!string.IsNullOrEmpty(name)) + { + return name; + } + else + { + if (owner.Header is string headerString && !string.IsNullOrEmpty(headerString)) + { + return headerString; + } + } + } + + return base.GetNameCore(); + } +} diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.Events.cs b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.Events.cs new file mode 100644 index 000000000..d1a50dbad --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.Events.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +public partial class SettingsExpander +{ + /// + /// Fires when the SettingsExpander is opened + /// + public event EventHandler? Expanded; + + /// + /// Fires when the expander is closed + /// + public event EventHandler? Collapsed; +} diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.ItemsControl.cs b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.ItemsControl.cs new file mode 100644 index 000000000..1eab5c08f --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.ItemsControl.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +//// Implement properties for ItemsControl like behavior. +public partial class SettingsExpander +{ + public IList Items + { + get { return (IList)GetValue(ItemsProperty); } + set { SetValue(ItemsProperty, value); } + } + + public static readonly DependencyProperty ItemsProperty = + DependencyProperty.Register(nameof(Items), typeof(IList), typeof(SettingsExpander), new PropertyMetadata(null, OnItemsConnectedPropertyChanged)); + + public object ItemsSource + { + get { return GetValue(ItemsSourceProperty); } + set { SetValue(ItemsSourceProperty, value); } + } + + public static readonly DependencyProperty ItemsSourceProperty = + DependencyProperty.Register(nameof(ItemsSource), typeof(object), typeof(SettingsExpander), new PropertyMetadata(null, OnItemsConnectedPropertyChanged)); + + public object ItemTemplate + { + get { return GetValue(ItemTemplateProperty); } + set { SetValue(ItemTemplateProperty, value); } + } + + public static readonly DependencyProperty ItemTemplateProperty = + DependencyProperty.Register(nameof(ItemTemplate), typeof(object), typeof(SettingsExpander), new PropertyMetadata(null)); + + public StyleSelector ItemContainerStyleSelector + { + get { return (StyleSelector)GetValue(ItemContainerStyleSelectorProperty); } + set { SetValue(ItemContainerStyleSelectorProperty, value); } + } + + public static readonly DependencyProperty ItemContainerStyleSelectorProperty = + DependencyProperty.Register(nameof(ItemContainerStyleSelector), typeof(StyleSelector), typeof(SettingsExpander), new PropertyMetadata(null)); + + private static void OnItemsConnectedPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args) + { + if (dependencyObject is not SettingsExpander { _itemsRepeater: not null } expander) + { + return; + } + + var datasource = expander.ItemsSource ?? expander.Items; + + expander._itemsRepeater.ItemsSource = datasource; + } + + private void ItemsRepeater_ElementPrepared(ItemsRepeater sender, ItemsRepeaterElementPreparedEventArgs args) + { + if (ItemContainerStyleSelector != null && + args.Element is FrameworkElement element && + element.ReadLocalValue(StyleProperty) == DependencyProperty.UnsetValue) + { + // TODO: Get item from args.Index? + element.Style = ItemContainerStyleSelector.SelectStyle(null, element); + } + } +} diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.Properties.cs b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.Properties.cs new file mode 100644 index 000000000..e806ea7a4 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.Properties.cs @@ -0,0 +1,151 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +[ContentProperty(Name = nameof(Content))] +public partial class SettingsExpander +{ + /// + /// The backing for the property. + /// + public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register( + nameof(Header), + typeof(object), + typeof(SettingsExpander), + new PropertyMetadata(defaultValue: null)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register( + nameof(Description), + typeof(object), + typeof(SettingsExpander), + new PropertyMetadata(defaultValue: null)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty HeaderIconProperty = DependencyProperty.Register( + nameof(HeaderIcon), + typeof(IconElement), + typeof(SettingsExpander), + new PropertyMetadata(defaultValue: null)); + + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty ContentProperty = DependencyProperty.Register( + nameof(Content), + typeof(object), + typeof(SettingsExpander), + new PropertyMetadata(defaultValue: null)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty ItemsHeaderProperty = DependencyProperty.Register( + nameof(ItemsHeader), + typeof(UIElement), + typeof(SettingsExpander), + new PropertyMetadata(defaultValue: null)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty ItemsFooterProperty = DependencyProperty.Register( + nameof(ItemsFooter), + typeof(UIElement), + typeof(SettingsExpander), + new PropertyMetadata(defaultValue: null)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register( + nameof(IsExpanded), + typeof(bool), + typeof(SettingsExpander), + new PropertyMetadata(defaultValue: false, (d, e) => ((SettingsExpander)d).OnIsExpandedPropertyChanged((bool)e.OldValue, (bool)e.NewValue))); + + /// + /// Gets or sets the Header. + /// + public object Header + { + get => GetValue(HeaderProperty); + set => SetValue(HeaderProperty, value); + } + + /// + /// Gets or sets the Description. + /// +#pragma warning disable CS0109 // Member does not hide an inherited member; new keyword is not required + public new object Description +#pragma warning restore CS0109 // Member does not hide an inherited member; new keyword is not required + { + get => GetValue(DescriptionProperty); + set => SetValue(DescriptionProperty, value); + } + + /// + /// Gets or sets the HeaderIcon. + /// + public IconElement HeaderIcon + { + get => (IconElement)GetValue(HeaderIconProperty); + set => SetValue(HeaderIconProperty, value); + } + + /// + /// Gets or sets the Content. + /// + public object Content + { + get => GetValue(ContentProperty); + set => SetValue(ContentProperty, value); + } + + /// + /// Gets or sets the ItemsFooter. + /// + public UIElement ItemsHeader + { + get => (UIElement)GetValue(ItemsHeaderProperty); + set => SetValue(ItemsHeaderProperty, value); + } + + /// + /// Gets or sets the ItemsFooter. + /// + public UIElement ItemsFooter + { + get => (UIElement)GetValue(ItemsFooterProperty); + set => SetValue(ItemsFooterProperty, value); + } + + /// + /// Gets or sets the IsExpanded state. + /// + public bool IsExpanded + { + get => (bool)GetValue(IsExpandedProperty); + set => SetValue(IsExpandedProperty, value); + } + protected virtual void OnIsExpandedPropertyChanged(bool oldValue, bool newValue) + { + OnIsExpandedChanged(oldValue, newValue); + + if (newValue) + { + Expanded?.Invoke(this, EventArgs.Empty); + } + else + { + Collapsed?.Invoke(this, EventArgs.Empty); + } + } +} diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.cs b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.cs new file mode 100644 index 000000000..2ac2787e8 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.cs @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +//// Note: ItemsRepeater will request all the available horizontal space: https://github.com/microsoft/microsoft-ui-xaml/issues/3842 +[TemplatePart(Name = PART_ItemsRepeater, Type = typeof(ItemsRepeater))] +public partial class SettingsExpander : Control +{ + private const string PART_ItemsRepeater = "PART_ItemsRepeater"; + + private ItemsRepeater? _itemsRepeater; + + /// + /// The SettingsExpander is a collapsable control to host multiple SettingsCards. + /// + public SettingsExpander() + { + this.DefaultStyleKey = typeof(SettingsExpander); + Items = new List(); + } + + /// + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + SetAccessibleName(); + + if (_itemsRepeater != null) + { + _itemsRepeater.ElementPrepared -= this.ItemsRepeater_ElementPrepared; + } + + _itemsRepeater = GetTemplateChild(PART_ItemsRepeater) as ItemsRepeater; + + if (_itemsRepeater != null) + { + _itemsRepeater.ElementPrepared += this.ItemsRepeater_ElementPrepared; + + // Update it's source based on our current items properties. + OnItemsConnectedPropertyChanged(this, null!); // Can't get it to accept type here? (DependencyPropertyChangedEventArgs)EventArgs.Empty + } + } + + private void SetAccessibleName() + { + if (string.IsNullOrEmpty(AutomationProperties.GetName(this))) + { + if (Header is string headerString && !string.IsNullOrEmpty(headerString)) + { + AutomationProperties.SetName(this, headerString); + } + } + } + + /// + /// Creates AutomationPeer + /// + /// An automation peer for . + protected override AutomationPeer OnCreateAutomationPeer() + { + return new SettingsExpanderAutomationPeer(this); + } + + private void OnIsExpandedChanged(bool oldValue, bool newValue) + { + var peer = FrameworkElementAutomationPeer.FromElement(this) as SettingsExpanderAutomationPeer; + peer?.RaiseExpandedChangedEvent(newValue); + } +} diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.xaml b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.xaml new file mode 100644 index 000000000..368b0f105 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.xaml @@ -0,0 +1,583 @@ + + + + + + + + + + Show all settings + 16,16,4,16 + 58,8,44,8 + 0,1,0,0 + 58,8,16,8 + 16 + 32 + 32 + + + + + + + + + + + + + + + + + diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpanderAutomationPeer.cs b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpanderAutomationPeer.cs new file mode 100644 index 000000000..ca2de796a --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpanderAutomationPeer.cs @@ -0,0 +1,82 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ReSharper disable RedundantExtendsListEntry +// ReSharper disable PartialTypeWithSinglePart +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// AutomationPeer for SettingsExpander +/// +public partial class SettingsExpanderAutomationPeer : FrameworkElementAutomationPeer +{ + /// + /// Initializes a new instance of the class. + /// + /// SettingsExpander + public SettingsExpanderAutomationPeer(SettingsExpander owner) + : base(owner) + { + } + + /// + /// Gets the control type for the element that is associated with the UI Automation peer. + /// + /// The control type. + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.Group; + } + + /// + /// Called by GetClassName that gets a human-readable name that, in addition to AutomationControlType, + /// differentiates the control represented by this AutomationPeer. + /// + /// The string that contains the name. + protected override string GetClassNameCore() + { + return Owner.GetType().Name; + } + + protected override string GetNameCore() + { + string name = base.GetNameCore(); + + if (Owner is SettingsExpander owner) + { + if (!string.IsNullOrEmpty(AutomationProperties.GetName(owner))) + { + name = AutomationProperties.GetName(owner); + } + else + { + if (owner.Header is string headerString && !string.IsNullOrEmpty(headerString)) + { + name = headerString; + } + } + } + return name; + } + + /// + /// Raises the property changed event for this AutomationPeer for the provided identifier. + /// Narrator does not announce this due to: https://github.com/microsoft/microsoft-ui-xaml/issues/3469 + /// + /// New Expanded state + public void RaiseExpandedChangedEvent(bool newValue) + { + ExpandCollapseState newState = newValue ? + ExpandCollapseState.Expanded : + ExpandCollapseState.Collapsed; + + ExpandCollapseState oldState = newState == ExpandCollapseState.Expanded ? + ExpandCollapseState.Collapsed : + ExpandCollapseState.Expanded; + + #if !HAS_UNO + RaisePropertyChangedEvent(ExpandCollapsePatternIdentifiers.ExpandCollapseStateProperty, oldState, newState); + #endif + } +} diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpanderItemStyleSelector.cs b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpanderItemStyleSelector.cs new file mode 100644 index 000000000..462e3c36f --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpanderItemStyleSelector.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ReSharper disable PartialTypeWithSinglePart +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// used by to choose the proper container style (clickable or not). +/// +public partial class SettingsExpanderItemStyleSelector : StyleSelector +{ + /// + /// Gets or sets the default . + /// + public Style DefaultStyle { get; set; } + + /// + /// Gets or sets the when clickable. + /// + public Style ClickableStyle { get; set; } + + /// + /// Initializes a new instance of the class. + /// +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + public SettingsExpanderItemStyleSelector() +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + { + } + + /// + protected override Style SelectStyleCore(object item, DependencyObject container) + { + if (container is SettingsCard card && card.IsClickEnabled) + { + return ClickableStyle; + } + else + { + return DefaultStyle; + } + } +} diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/Themes/Generic.xaml b/Hi3Helper.CommunityToolkit/SettingsControls/Themes/Generic.xaml new file mode 100644 index 000000000..177830969 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/Themes/Generic.xaml @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/packages.lock.json b/Hi3Helper.CommunityToolkit/SettingsControls/packages.lock.json new file mode 100644 index 000000000..44f97d09f --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/packages.lock.json @@ -0,0 +1,95 @@ +{ + "version": 1, + "dependencies": { + "net9.0-windows10.0.22621": { + "CommunityToolkit.WinUI.Triggers": { + "type": "Direct", + "requested": "[8.1.240916, )", + "resolved": "8.1.240916", + "contentHash": "4XUtIdhjTj9mTWgqUvnVIa3Ox+CW+fBks94DUOndAuxtHlDf9Oc8EguHXp4W8qxp7x4tUeYDoRPmcsnXgjt3Sw==", + "dependencies": { + "CommunityToolkit.WinUI.Helpers": "8.1.240916", + "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", + "Microsoft.WindowsAppSDK": "1.5.240311000" + } + }, + "Microsoft.NET.ILLink.Tasks": { + "type": "Direct", + "requested": "[9.0.0-rc.1.24431.7, )", + "resolved": "9.0.0-rc.1.24431.7", + "contentHash": "B8jHIIszeUdDY+O4G1/xPOuhrriwsv9Z8/u3XSOAqc8Z0mQCeLBKg2oLGElDZeRF8wWXDGklAKY3f0IHopyXkQ==" + }, + "Microsoft.Web.WebView2": { + "type": "Direct", + "requested": "[1.0.2783-prerelease, )", + "resolved": "1.0.2783-prerelease", + "contentHash": "sPxd49RLVnOyzplXTvdU9z6NIx0t56MridYoZhw8BOBZt564Ufi168Wfg5G0rIc5EKWmrGihLeZ1OfmjQiqWTw==" + }, + "Microsoft.Windows.CsWinRT": { + "type": "Direct", + "requested": "[2.1.3, )", + "resolved": "2.1.3", + "contentHash": "Nl8A4rQ4l2GNj703GvLSbr0Vo++FjxKxU7CIj1pcKz/sN8XSvD4dIvUCYYgD16o2pG4PSSXNgAxfwDUwLGHLPA==" + }, + "Microsoft.Windows.SDK.BuildTools": { + "type": "Direct", + "requested": "[10.0.26100.1742, )", + "resolved": "10.0.26100.1742", + "contentHash": "ypcHjr4KEi6xQhgClnbXoANHcyyX/QsC4Rky4igs6M4GiDa+weegPo8JuV/VMxqrZCV4zlqDsp2krgkN7ReAAg==" + }, + "Microsoft.WindowsAppSDK": { + "type": "Direct", + "requested": "[1.6.240829007, )", + "resolved": "1.6.240829007", + "contentHash": "Ij0jXARFOlRY+n7M32ZS9Kbf+x7gmnhWVnMP3eKHe83eu/2pzh9f4gjmoZ5fAgoZaKWCa2PvcxzHx0gtRXcKRQ==", + "dependencies": { + "Microsoft.Web.WebView2": "1.0.2651.64", + "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756" + } + }, + "CommunityToolkit.Common": { + "type": "Transitive", + "resolved": "8.2.1", + "contentHash": "LWuhy8cQKJ/MYcy3XafJ916U3gPH/YDvYoNGWyQWN11aiEKCZszzPOTJAOvBjP9yG8vHmIcCyPUt4L82OK47Iw==" + }, + "CommunityToolkit.WinUI.Extensions": { + "type": "Transitive", + "resolved": "8.1.240916", + "contentHash": "y8FEzoy5HF3dBkxe/pU87hlh0QACtGZhnv881x2SFq9fvDjukLQz6VpoOkjxSZjEILmYu1NoI/tYjqXlETRGlw==", + "dependencies": { + "CommunityToolkit.Common": "8.2.1", + "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", + "Microsoft.WindowsAppSDK": "1.5.240311000" + } + }, + "CommunityToolkit.WinUI.Helpers": { + "type": "Transitive", + "resolved": "8.1.240916", + "contentHash": "Q2eipdR9ntktYT1b3Dn927lZjdwQsmSU9cq6Pf2GwXW+R2IwkbGn7uYKJhmr5athOT6mnNgc7pMmfwFHIsFPLQ==", + "dependencies": { + "CommunityToolkit.WinUI.Extensions": "8.1.240916", + "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", + "Microsoft.WindowsAppSDK": "1.5.240311000" + } + } + }, + "net9.0-windows10.0.22621/win-x64": { + "Microsoft.Web.WebView2": { + "type": "Direct", + "requested": "[1.0.2783-prerelease, )", + "resolved": "1.0.2783-prerelease", + "contentHash": "sPxd49RLVnOyzplXTvdU9z6NIx0t56MridYoZhw8BOBZt564Ufi168Wfg5G0rIc5EKWmrGihLeZ1OfmjQiqWTw==" + }, + "Microsoft.WindowsAppSDK": { + "type": "Direct", + "requested": "[1.6.240829007, )", + "resolved": "1.6.240829007", + "contentHash": "Ij0jXARFOlRY+n7M32ZS9Kbf+x7gmnhWVnMP3eKHe83eu/2pzh9f4gjmoZ5fAgoZaKWCa2PvcxzHx0gtRXcKRQ==", + "dependencies": { + "Microsoft.Web.WebView2": "1.0.2651.64", + "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756" + } + } + } + } +} \ No newline at end of file diff --git a/Hi3Helper.CommunityToolkit/commitinfo.txt b/Hi3Helper.CommunityToolkit/commitinfo.txt new file mode 100644 index 000000000..c4d6cb691 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/commitinfo.txt @@ -0,0 +1 @@ +https://github.com/CommunityToolkit/Windows/commit/44d3d9d3601bb543625078fbbab55a2a867b0662 \ No newline at end of file diff --git a/Hi3Helper.Core/Classes/Shared/Region/LauncherConfig.cs b/Hi3Helper.Core/Classes/Shared/Region/LauncherConfig.cs index 0fe8a523a..a3ea3dfb0 100644 --- a/Hi3Helper.Core/Classes/Shared/Region/LauncherConfig.cs +++ b/Hi3Helper.Core/Classes/Shared/Region/LauncherConfig.cs @@ -137,7 +137,7 @@ private static void InitScreenResSettings() new CDNURLProperty { Name = "GitHub", - URLPrefix = "https://github.com/neon-nyan/CollapseLauncher-ReleaseRepo/raw/main", + URLPrefix = "https://github.com/CollapseLauncher/CollapseLauncher-ReleaseRepo/raw/main", Description = Lang!._Misc!.CDNDescription_Github, PartialDownloadSupport = true }, @@ -347,6 +347,12 @@ public static int DownloadChunkSize set => SetAndSaveConfigValue("DownloadChunkSize", value); } + public static bool IsEnforceToUse7zipOnExtract + { + get => GetAppConfigValue("EnforceToUse7zipOnExtract").ToBool(); + set => SetAndSaveConfigValue("EnforceToUse7zipOnExtract", value); + } + private static long _downloadSpeedLimitCached = 0; // Default: 0 == Unlimited public static long DownloadSpeedLimitCached { @@ -441,6 +447,8 @@ public static Guid GetGuid(int sessionNum) { "SophonHttpConnInt", 0}, { "SophonPreloadApplyPerfMode", false }, + { "EnforceToUse7zipOnExtract", false }, + { "IsUseProxy", false }, { "IsAllowHttpRedirections", true }, { "IsAllowHttpCookies", false }, diff --git a/Hi3Helper.Core/Hi3Helper.Core.csproj b/Hi3Helper.Core/Hi3Helper.Core.csproj index bba442d8b..f65319e99 100644 --- a/Hi3Helper.Core/Hi3Helper.Core.csproj +++ b/Hi3Helper.Core/Hi3Helper.Core.csproj @@ -1,7 +1,7 @@  - net8.0 + net9.0-windows10.0.22621.0 x64 Debug;Release;Publish 6 @@ -18,6 +18,7 @@ Collapse Launcher Team $(Company). neon-nyan, Cry0, bagusnl, shatyuka, gablm. Copyright 2022-2024 $(Company) + true @@ -44,9 +45,12 @@ + + + + - diff --git a/Hi3Helper.Core/Lang/Locale/LangAppNotification.cs b/Hi3Helper.Core/Lang/Locale/LangAppNotification.cs index dd7031aa6..a0dbb67ba 100644 --- a/Hi3Helper.Core/Lang/Locale/LangAppNotification.cs +++ b/Hi3Helper.Core/Lang/Locale/LangAppNotification.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,6 +8,8 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangAppNotification _AppNotification { get; set; } = LangFallback?._AppNotification; + + [GeneratedBindableCustomProperty] public sealed class LangAppNotification { public string NotifMetadataUpdateTitle { get; set; } = LangFallback?._AppNotification.NotifMetadataUpdateTitle; diff --git a/Hi3Helper.Core/Lang/Locale/LangBackgroundNotification.cs b/Hi3Helper.Core/Lang/Locale/LangBackgroundNotification.cs index e77808a03..7c791428a 100644 --- a/Hi3Helper.Core/Lang/Locale/LangBackgroundNotification.cs +++ b/Hi3Helper.Core/Lang/Locale/LangBackgroundNotification.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangBackgroundNotification _BackgroundNotification { get; set; } = LangFallback?._BackgroundNotification; - public sealed class LangBackgroundNotification + + [GeneratedBindableCustomProperty] + public sealed partial class LangBackgroundNotification { public string LoadingTitle { get; set; } = LangFallback?._BackgroundNotification.LoadingTitle; public string Placeholder { get; set; } = LangFallback?._BackgroundNotification.Placeholder; diff --git a/Hi3Helper.Core/Lang/Locale/LangCachesPage.cs b/Hi3Helper.Core/Lang/Locale/LangCachesPage.cs index 13b84e519..3f27c41aa 100644 --- a/Hi3Helper.Core/Lang/Locale/LangCachesPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangCachesPage.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangCachesPage _CachesPage { get; set; } = LangFallback?._CachesPage; - public sealed class LangCachesPage + + [GeneratedBindableCustomProperty] + public sealed partial class LangCachesPage { public string PageTitle { get; set; } = LangFallback?._CachesPage.PageTitle; public string ListCol1 { get; set; } = LangFallback?._CachesPage.ListCol1; diff --git a/Hi3Helper.Core/Lang/Locale/LangCutscenesPage.cs b/Hi3Helper.Core/Lang/Locale/LangCutscenesPage.cs index 122766d05..19907e237 100644 --- a/Hi3Helper.Core/Lang/Locale/LangCutscenesPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangCutscenesPage.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangCutscenesPage _CutscenesPage { get; set; } = LangFallback?._CutscenesPage; - public sealed class LangCutscenesPage + + [GeneratedBindableCustomProperty] + public sealed partial class LangCutscenesPage { public string PageTitle { get; set; } = LangFallback?._CutscenesPage.PageTitle; } diff --git a/Hi3Helper.Core/Lang/Locale/LangDialogs.cs b/Hi3Helper.Core/Lang/Locale/LangDialogs.cs index 1f3fab129..0e8761c50 100644 --- a/Hi3Helper.Core/Lang/Locale/LangDialogs.cs +++ b/Hi3Helper.Core/Lang/Locale/LangDialogs.cs @@ -1,3 +1,5 @@ +using WinRT; + namespace Hi3Helper { public sealed partial class Locale @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangDialogs _Dialogs { get; set; } = LangFallback?._Dialogs; - public sealed class LangDialogs + + [GeneratedBindableCustomProperty] + public sealed partial class LangDialogs { public string DeltaPatchDetectedTitle { get; set; } = LangFallback?._Dialogs.PreloadVerifiedTitle; public string DeltaPatchDetectedSubtitle { get; set; } = LangFallback?._Dialogs.PreloadVerifiedTitle; diff --git a/Hi3Helper.Core/Lang/Locale/LangDisconnectedPage.cs b/Hi3Helper.Core/Lang/Locale/LangDisconnectedPage.cs index 45c652e1b..40a78d6a0 100644 --- a/Hi3Helper.Core/Lang/Locale/LangDisconnectedPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangDisconnectedPage.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangDisconnectedPage _DisconnectedPage { get; set; } = LangFallback?._DisconnectedPage; - public sealed class LangDisconnectedPage + + [GeneratedBindableCustomProperty] + public sealed partial class LangDisconnectedPage { public string PageTitle { get; set; } = LangFallback?._DisconnectedPage.PageTitle; public string Header1 { get; set; } = LangFallback?._DisconnectedPage.Header1; diff --git a/Hi3Helper.Core/Lang/Locale/LangFileCleanupPage.cs b/Hi3Helper.Core/Lang/Locale/LangFileCleanupPage.cs index 875538170..ee12fa153 100644 --- a/Hi3Helper.Core/Lang/Locale/LangFileCleanupPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangFileCleanupPage.cs @@ -1,3 +1,5 @@ +using WinRT; + namespace Hi3Helper { public sealed partial class Locale @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangFileCleanupPage _FileCleanupPage { get; set; } = LangFallback?._FileCleanupPage; - public sealed class LangFileCleanupPage + + [GeneratedBindableCustomProperty] + public sealed partial class LangFileCleanupPage { public string Title { get; set; } = LangFallback?._FileCleanupPage.Title; public string TopButtonRescan { get; set; } = LangFallback?._FileCleanupPage.TopButtonRescan; diff --git a/Hi3Helper.Core/Lang/Locale/LangFileMigrationProcess.cs b/Hi3Helper.Core/Lang/Locale/LangFileMigrationProcess.cs index 41ec1e3b7..f896f0fe6 100644 --- a/Hi3Helper.Core/Lang/Locale/LangFileMigrationProcess.cs +++ b/Hi3Helper.Core/Lang/Locale/LangFileMigrationProcess.cs @@ -1,3 +1,5 @@ +using WinRT; + namespace Hi3Helper { public sealed partial class Locale @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangFileMigrationProcess _FileMigrationProcess { get; set; } = LangFallback?._FileMigrationProcess; - public sealed class LangFileMigrationProcess + + [GeneratedBindableCustomProperty] + public sealed partial class LangFileMigrationProcess { public string PathActivityPanelTitle { get; set; } = LangFallback?._FileMigrationProcess.PathActivityPanelTitle; public string SpeedIndicatorTitle { get; set; } = LangFallback?._FileMigrationProcess.SpeedIndicatorTitle; diff --git a/Hi3Helper.Core/Lang/Locale/LangGameRepairPage.cs b/Hi3Helper.Core/Lang/Locale/LangGameRepairPage.cs index fae1f8789..79f61a1b8 100644 --- a/Hi3Helper.Core/Lang/Locale/LangGameRepairPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangGameRepairPage.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangGameRepairPage _GameRepairPage { get; set; } = LangFallback?._GameRepairPage; - public sealed class LangGameRepairPage + + [GeneratedBindableCustomProperty] + public sealed partial class LangGameRepairPage { public string PageTitle { get; set; } = LangFallback?._GameRepairPage.PageTitle; public string ListCol1 { get; set; } = LangFallback?._GameRepairPage.ListCol1; diff --git a/Hi3Helper.Core/Lang/Locale/LangGameSettingsPage.cs b/Hi3Helper.Core/Lang/Locale/LangGameSettingsPage.cs index fe305638a..cf57547eb 100644 --- a/Hi3Helper.Core/Lang/Locale/LangGameSettingsPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangGameSettingsPage.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangGameSettingsPage _GameSettingsPage { get; set; } = LangFallback?._GameSettingsPage; - public sealed class LangGameSettingsPage + + [GeneratedBindableCustomProperty] + public sealed partial class LangGameSettingsPage { public string PageTitle { get; set; } = LangFallback?._GameSettingsPage.PageTitle; public string Graphics_Title { get; set; } = LangFallback?._GameSettingsPage.Graphics_Title; diff --git a/Hi3Helper.Core/Lang/Locale/LangGenshinGameSettingsPage.cs b/Hi3Helper.Core/Lang/Locale/LangGenshinGameSettingsPage.cs index b87994065..217926040 100644 --- a/Hi3Helper.Core/Lang/Locale/LangGenshinGameSettingsPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangGenshinGameSettingsPage.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangGenshinGameSettingsPage _GenshinGameSettingsPage { get; set; } = LangFallback?._GenshinGameSettingsPage; - public sealed class LangGenshinGameSettingsPage + + [GeneratedBindableCustomProperty] + public sealed partial class LangGenshinGameSettingsPage { public string PageTitle { get; set; } = LangFallback?._GenshinGameSettingsPage.PageTitle; #region Overlay diff --git a/Hi3Helper.Core/Lang/Locale/LangHomePage.cs b/Hi3Helper.Core/Lang/Locale/LangHomePage.cs index 006281cb0..363d1784c 100644 --- a/Hi3Helper.Core/Lang/Locale/LangHomePage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangHomePage.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangHomePage _HomePage { get; set; } = LangFallback?._HomePage; - public sealed class LangHomePage + + [GeneratedBindableCustomProperty] + public sealed partial class LangHomePage { public string PageTitle { get; set; } = LangFallback?._HomePage.PageTitle; public string PreloadTitle { get; set; } = LangFallback?._HomePage.PreloadTitle; diff --git a/Hi3Helper.Core/Lang/Locale/LangInstallConvert.cs b/Hi3Helper.Core/Lang/Locale/LangInstallConvert.cs index 26a6346e4..9a2db987e 100644 --- a/Hi3Helper.Core/Lang/Locale/LangInstallConvert.cs +++ b/Hi3Helper.Core/Lang/Locale/LangInstallConvert.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangInstallConvert _InstallConvert { get; set; } = LangFallback?._InstallConvert; - public sealed class LangInstallConvert + + [GeneratedBindableCustomProperty] + public sealed partial class LangInstallConvert { public string PageTitle { get; set; } = LangFallback?._InstallConvert.PageTitle; public string Step1Title { get; set; } = LangFallback?._InstallConvert.Step1Title; diff --git a/Hi3Helper.Core/Lang/Locale/LangInstallManagement.cs b/Hi3Helper.Core/Lang/Locale/LangInstallManagement.cs index ef84b3d90..3dc6bad6f 100644 --- a/Hi3Helper.Core/Lang/Locale/LangInstallManagement.cs +++ b/Hi3Helper.Core/Lang/Locale/LangInstallManagement.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangInstallManagement _InstallMgmt { get; set; } = LangFallback?._InstallMgmt; - public sealed class LangInstallManagement + + [GeneratedBindableCustomProperty] + public sealed partial class LangInstallManagement { public string IntegrityCheckTitle { get; set; } = LangFallback?._InstallMgmt.IntegrityCheckTitle; public string PreparePatchTitle { get; set; } = LangFallback?._InstallMgmt.PreparePatchTitle; diff --git a/Hi3Helper.Core/Lang/Locale/LangInstallMigrateSteam.cs b/Hi3Helper.Core/Lang/Locale/LangInstallMigrateSteam.cs index 02411e8eb..f1eb255c2 100644 --- a/Hi3Helper.Core/Lang/Locale/LangInstallMigrateSteam.cs +++ b/Hi3Helper.Core/Lang/Locale/LangInstallMigrateSteam.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangInstallMigrateSteam _InstallMigrateSteam { get; set; } = LangFallback?._InstallMigrateSteam; - public sealed class LangInstallMigrateSteam + + [GeneratedBindableCustomProperty] + public sealed partial class LangInstallMigrateSteam { public string PageTitle { get; set; } = LangFallback?._InstallMigrateSteam.PageTitle; public string Step1Title { get; set; } = LangFallback?._InstallMigrateSteam.Step1Title; diff --git a/Hi3Helper.Core/Lang/Locale/LangKeyboardShortcuts.cs b/Hi3Helper.Core/Lang/Locale/LangKeyboardShortcuts.cs index b7e172f40..c02d84448 100644 --- a/Hi3Helper.Core/Lang/Locale/LangKeyboardShortcuts.cs +++ b/Hi3Helper.Core/Lang/Locale/LangKeyboardShortcuts.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangKeyboardShortcuts _KbShortcuts { get; set; } = LangFallback?._KbShortcuts; - public sealed class LangKeyboardShortcuts + + [GeneratedBindableCustomProperty] + public sealed partial class LangKeyboardShortcuts { public string DialogTitle { get; set; } = LangFallback?._KbShortcuts.DialogTitle; diff --git a/Hi3Helper.Core/Lang/Locale/LangMainPage.cs b/Hi3Helper.Core/Lang/Locale/LangMainPage.cs index 4cc2772f8..d4f749b2e 100644 --- a/Hi3Helper.Core/Lang/Locale/LangMainPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangMainPage.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangMainPage _MainPage { get; set; } = LangFallback?._MainPage; - public sealed class LangMainPage + + [GeneratedBindableCustomProperty] + public sealed partial class LangMainPage { public string PageTitle { get; set; } = LangFallback?._MainPage.PageTitle; public string RegionChangeConfirm { get; set; } = LangFallback?._MainPage.RegionChangeConfirm; diff --git a/Hi3Helper.Core/Lang/Locale/LangMisc.cs b/Hi3Helper.Core/Lang/Locale/LangMisc.cs index 5f78d227b..89202459f 100644 --- a/Hi3Helper.Core/Lang/Locale/LangMisc.cs +++ b/Hi3Helper.Core/Lang/Locale/LangMisc.cs @@ -1,3 +1,5 @@ +using WinRT; + namespace Hi3Helper { public sealed partial class Locale @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangMisc _Misc { get; set; } = LangFallback?._Misc; - public sealed class LangMisc + + [GeneratedBindableCustomProperty] + public sealed partial class LangMisc { public string SizePrefixes1000U { get; set; } = LangFallback?._Misc.SizePrefixes1000U; public string UpdateCompleteTitle { get; set; } = LangFallback?._Misc.UpdateCompleteTitle; diff --git a/Hi3Helper.Core/Lang/Locale/LangNotificationToast.cs b/Hi3Helper.Core/Lang/Locale/LangNotificationToast.cs index bd53a561b..5cd978cf3 100644 --- a/Hi3Helper.Core/Lang/Locale/LangNotificationToast.cs +++ b/Hi3Helper.Core/Lang/Locale/LangNotificationToast.cs @@ -1,3 +1,5 @@ +using WinRT; + namespace Hi3Helper { public sealed partial class Locale @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangNotificationToast _NotificationToast { get; set; } = LangFallback?._NotificationToast; - public sealed class LangNotificationToast + + [GeneratedBindableCustomProperty] + public sealed partial class LangNotificationToast { public string WindowHiddenToTray_Title { get; set; } = LangFallback?._NotificationToast.WindowHiddenToTray_Title; public string WindowHiddenToTray_Subtitle { get; set; } = LangFallback?._NotificationToast.WindowHiddenToTray_Subtitle; diff --git a/Hi3Helper.Core/Lang/Locale/LangOOBEAgreementMenu.cs b/Hi3Helper.Core/Lang/Locale/LangOOBEAgreementMenu.cs index 19e31b6b7..3f80ea9d0 100644 --- a/Hi3Helper.Core/Lang/Locale/LangOOBEAgreementMenu.cs +++ b/Hi3Helper.Core/Lang/Locale/LangOOBEAgreementMenu.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangOOBEAgreementMenu _OOBEAgreementMenu { get; set; } = LangFallback?._OOBEAgreementMenu; - public sealed class LangOOBEAgreementMenu + + [GeneratedBindableCustomProperty] + public sealed partial class LangOOBEAgreementMenu { public string AgreementTitle { get; set; } = LangFallback?._OOBEAgreementMenu.AgreementTitle; } diff --git a/Hi3Helper.Core/Lang/Locale/LangOOBEStartUpMenu.cs b/Hi3Helper.Core/Lang/Locale/LangOOBEStartUpMenu.cs index 1133dd900..47db25cc9 100644 --- a/Hi3Helper.Core/Lang/Locale/LangOOBEStartUpMenu.cs +++ b/Hi3Helper.Core/Lang/Locale/LangOOBEStartUpMenu.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using WinRT; namespace Hi3Helper { @@ -8,7 +9,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangOOBEStartUpMenu _OOBEStartUpMenu { get; set; } = LangFallback?._OOBEStartUpMenu; - public sealed class LangOOBEStartUpMenu + + [GeneratedBindableCustomProperty] + public sealed partial class LangOOBEStartUpMenu { public Dictionary WelcomeTitleString { get; set; } = LangFallback?._OOBEStartUpMenu.WelcomeTitleString; public string SetupNextButton { get; set; } = LangFallback?._OOBEStartUpMenu.SetupNextButton; diff --git a/Hi3Helper.Core/Lang/Locale/LangSettingsPage.cs b/Hi3Helper.Core/Lang/Locale/LangSettingsPage.cs index 8a6efdb0b..e45272013 100644 --- a/Hi3Helper.Core/Lang/Locale/LangSettingsPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangSettingsPage.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangSettingsPage _SettingsPage { get; set; } = LangFallback?._SettingsPage; - public sealed class LangSettingsPage + + [GeneratedBindableCustomProperty] + public sealed partial class LangSettingsPage { public string PageTitle { get; set; } = LangFallback?._SettingsPage.PageTitle; public string Debug { get; set; } = LangFallback?._SettingsPage.Debug; @@ -97,10 +101,11 @@ public sealed class LangSettingsPage public string AppChangeReleaseChannel { get; set; } = LangFallback?._SettingsPage.AppChangeReleaseChannel; public string EnableAcrylicEffect { get; set; } = LangFallback?._SettingsPage.EnableAcrylicEffect; public string EnableDownloadChunksMerging { get; set; } = LangFallback?._SettingsPage.EnableDownloadChunksMerging; + public string Enforce7ZipExtract { get; set; } = LangFallback?._SettingsPage.Enforce7ZipExtract; public string LowerCollapsePrioOnGameLaunch { get; set; } = LangFallback?._SettingsPage.LowerCollapsePrioOnGameLaunch; public string LowerCollapsePrioOnGameLaunch_Tooltip { get; set; } = LangFallback?._SettingsPage.LowerCollapsePrioOnGameLaunch_Tooltip; public string UseExternalBrowser { get; set; } = LangFallback?._SettingsPage.UseExternalBrowser; - public string KbShortcuts_Title { get; set; } = LangFallback?._SettingsPage.KbShortcuts_Title; + public string KbShortcuts_Title { get; set; } = LangFallback?._SettingsPage.KbShortcuts_Title; public string KbShortcuts_ShowBtn { get; set; } = LangFallback?._SettingsPage.KbShortcuts_ShowBtn; public string KbShortcuts_ResetBtn { get; set; } = LangFallback?._SettingsPage.KbShortcuts_ResetBtn; public string AppBehavior_Title { get; set; } = LangFallback?._SettingsPage.AppBehavior_Title; diff --git a/Hi3Helper.Core/Lang/Locale/LangStarRailGameSettingsPage.cs b/Hi3Helper.Core/Lang/Locale/LangStarRailGameSettingsPage.cs index 4d97495b7..c39a0f939 100644 --- a/Hi3Helper.Core/Lang/Locale/LangStarRailGameSettingsPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangStarRailGameSettingsPage.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangStarRailGameSettingsPage _StarRailGameSettingsPage { get; set; } = LangFallback?._StarRailGameSettingsPage; - public sealed class LangStarRailGameSettingsPage + + [GeneratedBindableCustomProperty] + public sealed partial class LangStarRailGameSettingsPage { public string PageTitle { get; set; } = LangFallback?._StarRailGameSettingsPage.PageTitle; public string Graphics_Title { get; set; } = LangFallback?._StarRailGameSettingsPage.Graphics_Title; diff --git a/Hi3Helper.Core/Lang/Locale/LangStartupPage.cs b/Hi3Helper.Core/Lang/Locale/LangStartupPage.cs index 0ee2c2473..63f5defab 100644 --- a/Hi3Helper.Core/Lang/Locale/LangStartupPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangStartupPage.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangStartupPage _StartupPage { get; set; } = LangFallback?._StartupPage; - public sealed class LangStartupPage + + [GeneratedBindableCustomProperty] + public sealed partial class LangStartupPage { public string SelectLang { get; set; } = LangFallback?._StartupPage.SelectLang; public string SelectLangDesc { get; set; } = LangFallback?._StartupPage.SelectLangDesc; diff --git a/Hi3Helper.Core/Lang/Locale/LangUnhandledExceptionPage.cs b/Hi3Helper.Core/Lang/Locale/LangUnhandledExceptionPage.cs index dcc6238e1..df01b1be1 100644 --- a/Hi3Helper.Core/Lang/Locale/LangUnhandledExceptionPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangUnhandledExceptionPage.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangUnhandledExceptionPage _UnhandledExceptionPage { get; set; } = LangFallback?._UnhandledExceptionPage; - public sealed class LangUnhandledExceptionPage + + [GeneratedBindableCustomProperty] + public sealed partial class LangUnhandledExceptionPage { public string UnhandledTitle1 { get; set; } = LangFallback?._UnhandledExceptionPage.UnhandledTitle1; public string UnhandledSubtitle1 { get; set; } = LangFallback?._UnhandledExceptionPage.UnhandledSubtitle1; diff --git a/Hi3Helper.Core/Lang/Locale/LangUpdatePage.cs b/Hi3Helper.Core/Lang/Locale/LangUpdatePage.cs index d7e5561b2..2dbb3100e 100644 --- a/Hi3Helper.Core/Lang/Locale/LangUpdatePage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangUpdatePage.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangUpdatePage _UpdatePage { get; set; } = LangFallback?._UpdatePage; - public sealed class LangUpdatePage + + [GeneratedBindableCustomProperty] + public sealed partial class LangUpdatePage { public string PageTitle1 { get; set; } = LangFallback?._UpdatePage.PageTitle1; public string PageTitle2 { get; set; } = LangFallback?._UpdatePage.PageTitle2; diff --git a/Hi3Helper.Core/Lang/Locale/LangZenlessGameSettingsPage.cs b/Hi3Helper.Core/Lang/Locale/LangZenlessGameSettingsPage.cs index 58c09a10a..10b16ce56 100644 --- a/Hi3Helper.Core/Lang/Locale/LangZenlessGameSettingsPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangZenlessGameSettingsPage.cs @@ -1,4 +1,6 @@ // ReSharper disable InconsistentNaming +using WinRT; + namespace Hi3Helper { public sealed partial class Locale @@ -8,7 +10,8 @@ public sealed partial class LocalizationParams public LangZenlessGameSettingsPage _ZenlessGameSettingsPage { get; set; } = LangFallback?._ZenlessGameSettingsPage; - public sealed class LangZenlessGameSettingsPage + [GeneratedBindableCustomProperty] + public sealed partial class LangZenlessGameSettingsPage { public string Graphics_ColorFilter { get; set; } = LangFallback?._ZenlessGameSettingsPage.Graphics_ColorFilter; diff --git a/Hi3Helper.Core/Lang/Localization.cs b/Hi3Helper.Core/Lang/Localization.cs index 37dfe374f..cd8774103 100644 --- a/Hi3Helper.Core/Lang/Localization.cs +++ b/Hi3Helper.Core/Lang/Localization.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Text.Json; +using WinRT; using static Hi3Helper.Locale; using static Hi3Helper.Logger; #if !APPLYUPDATE @@ -106,6 +107,7 @@ public LocalizationParams LoadLang(Stream langStream) public bool LangIsLoaded; } + [GeneratedBindableCustomProperty] public sealed partial class Locale { public const string FallbackLangID = "en-us"; @@ -226,6 +228,8 @@ private static void TryLoadSizePrefix(LocalizationParams langData) public static LocalizationParams Lang; #nullable enable public static LocalizationParams? LangFallback; + + [GeneratedBindableCustomProperty] public sealed partial class LocalizationParams { public string LanguageName { get; set; } = ""; diff --git a/Hi3Helper.Core/Lang/en_US.json b/Hi3Helper.Core/Lang/en_US.json index d829d360b..691163876 100644 --- a/Hi3Helper.Core/Lang/en_US.json +++ b/Hi3Helper.Core/Lang/en_US.json @@ -534,6 +534,7 @@ "EnableAcrylicEffect": "Use Acrylic Blur Effect", "EnableDownloadChunksMerging": "Merge Downloaded Package Chunks", + "Enforce7ZipExtract": "Always Use 7-zip for Game Installation/Update", "UseExternalBrowser": "Always Use External Browser", "LowerCollapsePrioOnGameLaunch": "Lower Collapse process resource usage when a game is launched", diff --git a/Hi3Helper.Core/packages.lock.json b/Hi3Helper.Core/packages.lock.json index dbd9fe431..d75e3dfc1 100644 --- a/Hi3Helper.Core/packages.lock.json +++ b/Hi3Helper.Core/packages.lock.json @@ -1,23 +1,35 @@ { "version": 1, "dependencies": { - "net8.0": { + "net9.0-windows10.0.22621": { + "Microsoft.NET.ILLink.Tasks": { + "type": "Direct", + "requested": "[9.0.0-rc.1.24431.7, )", + "resolved": "9.0.0-rc.1.24431.7", + "contentHash": "B8jHIIszeUdDY+O4G1/xPOuhrriwsv9Z8/u3XSOAqc8Z0mQCeLBKg2oLGElDZeRF8wWXDGklAKY3f0IHopyXkQ==" + }, + "Microsoft.Windows.CsWinRT": { + "type": "Direct", + "requested": "[2.1.3, )", + "resolved": "2.1.3", + "contentHash": "Nl8A4rQ4l2GNj703GvLSbr0Vo++FjxKxU7CIj1pcKz/sN8XSvD4dIvUCYYgD16o2pG4PSSXNgAxfwDUwLGHLPA==" + }, "Google.Protobuf": { "type": "Transitive", - "resolved": "3.28.1", - "contentHash": "i4EN7Z+OUdoRBNiVMIG6CfMh6UowXiUx+BKgE+GHLbAX5ArSmpUTFUDwgRNwNfYdosl6GXuBlDiHCcXSHw43+A==" + "resolved": "3.28.2", + "contentHash": "Z86ZKAB+v1B/m0LTM+EVamvZlYw/g3VND3/Gs4M/+aDIxa2JE9YPKjDxTpf0gv2sh26hrve3eI03brxBmzn92g==" }, "System.IO.Hashing": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "ne1843evDugl0md7Fjzy6QjJrzsjh46ZKbhf8GwBXb5f/gw97J4bxMs0NQKifDuThh/f0bZ0e62NPl1jzTuRqA==" + "resolved": "9.0.0-rc.1.24431.7", + "contentHash": "giMiDNlne8WcLG7rsEfrETaukDUTBPn8a97QRo9LYR71O0Rb4SJ6TbfGdQ9oiBYldVPSAbSQANb5ThLZOo408Q==" }, "hi3helper.enctool": { "type": "Project", "dependencies": { - "Google.Protobuf": "[3.28.1, )", + "Google.Protobuf": "[3.28.2, )", "Hi3Helper.Http": "[2.0.0, )", - "System.IO.Hashing": "[8.0.0, )" + "System.IO.Hashing": "[9.0.0-rc.1.24431.7, )" } }, "hi3helper.http": { diff --git a/Hi3Helper.EncTool b/Hi3Helper.EncTool index f90f1f961..d692d38e9 160000 --- a/Hi3Helper.EncTool +++ b/Hi3Helper.EncTool @@ -1 +1 @@ -Subproject commit f90f1f961b2fe5e47383d5f797c6ab48382a9c3b +Subproject commit d692d38e9770e75a2b11c1cee5253f9be128517f diff --git a/Hi3Helper.EncTool.Test/Hi3Helper.EncTool.Test.csproj b/Hi3Helper.EncTool.Test/Hi3Helper.EncTool.Test.csproj index 7b361f868..06bde6394 100644 --- a/Hi3Helper.EncTool.Test/Hi3Helper.EncTool.Test.csproj +++ b/Hi3Helper.EncTool.Test/Hi3Helper.EncTool.Test.csproj @@ -2,15 +2,11 @@ Exe - net8.0 + net9.0 enable enable x64 + true - - - - - diff --git a/Hi3Helper.EncTool.Test/Program.cs b/Hi3Helper.EncTool.Test/Program.cs index 4ef7d0905..141a21edb 100644 --- a/Hi3Helper.EncTool.Test/Program.cs +++ b/Hi3Helper.EncTool.Test/Program.cs @@ -1,5 +1,4 @@ -using Hi3Helper.EncTool.Parser.AssetMetadata; -using System.IO.Compression; +using System.IO.Compression; namespace Hi3Helper.EncTool.Test { diff --git a/Hi3Helper.EncTool.Test/packages.lock.json b/Hi3Helper.EncTool.Test/packages.lock.json index dbd9fe431..eda3602b4 100644 --- a/Hi3Helper.EncTool.Test/packages.lock.json +++ b/Hi3Helper.EncTool.Test/packages.lock.json @@ -1,27 +1,12 @@ { "version": 1, "dependencies": { - "net8.0": { - "Google.Protobuf": { - "type": "Transitive", - "resolved": "3.28.1", - "contentHash": "i4EN7Z+OUdoRBNiVMIG6CfMh6UowXiUx+BKgE+GHLbAX5ArSmpUTFUDwgRNwNfYdosl6GXuBlDiHCcXSHw43+A==" - }, - "System.IO.Hashing": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "ne1843evDugl0md7Fjzy6QjJrzsjh46ZKbhf8GwBXb5f/gw97J4bxMs0NQKifDuThh/f0bZ0e62NPl1jzTuRqA==" - }, - "hi3helper.enctool": { - "type": "Project", - "dependencies": { - "Google.Protobuf": "[3.28.1, )", - "Hi3Helper.Http": "[2.0.0, )", - "System.IO.Hashing": "[8.0.0, )" - } - }, - "hi3helper.http": { - "type": "Project" + "net9.0": { + "Microsoft.NET.ILLink.Tasks": { + "type": "Direct", + "requested": "[9.0.0-rc.1.24431.7, )", + "resolved": "9.0.0-rc.1.24431.7", + "contentHash": "B8jHIIszeUdDY+O4G1/xPOuhrriwsv9Z8/u3XSOAqc8Z0mQCeLBKg2oLGElDZeRF8wWXDGklAKY3f0IHopyXkQ==" } } } diff --git a/Hi3Helper.Http b/Hi3Helper.Http index bc6c11a16..3e0b024c9 160000 --- a/Hi3Helper.Http +++ b/Hi3Helper.Http @@ -1 +1 @@ -Subproject commit bc6c11a161fdee7f9c18616d6e1ef5a6d3dbbd55 +Subproject commit 3e0b024c9a2a33edeebe8125661e60b05965d58e diff --git a/Hi3Helper.SharpDiscordRPC b/Hi3Helper.SharpDiscordRPC index c9a5866a3..98c4551cf 160000 --- a/Hi3Helper.SharpDiscordRPC +++ b/Hi3Helper.SharpDiscordRPC @@ -1 +1 @@ -Subproject commit c9a5866a38c59b81d40311683890052df78f3166 +Subproject commit 98c4551cfcab9ffff0112b728d68fadb68964c08 diff --git a/Hi3Helper.Sophon b/Hi3Helper.Sophon index a13c3ed0a..f7f4db797 160000 --- a/Hi3Helper.Sophon +++ b/Hi3Helper.Sophon @@ -1 +1 @@ -Subproject commit a13c3ed0abb418060afe9069b7a163c9d0b3450e +Subproject commit f7f4db79701991bd1b80d0ebcd2c29fdb930c9a4 diff --git a/Hi3Helper.TaskScheduler/FodyWeavers.xml b/Hi3Helper.TaskScheduler/FodyWeavers.xml new file mode 100644 index 000000000..5029e7060 --- /dev/null +++ b/Hi3Helper.TaskScheduler/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Hi3Helper.TaskScheduler/FodyWeavers.xsd b/Hi3Helper.TaskScheduler/FodyWeavers.xsd new file mode 100644 index 000000000..05e92c114 --- /dev/null +++ b/Hi3Helper.TaskScheduler/FodyWeavers.xsd @@ -0,0 +1,141 @@ + + + + + + + + + + + + A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks + + + + + A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. + + + + + A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks + + + + + A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. + + + + + A list of unmanaged 32 bit assembly names to include, delimited with line breaks. + + + + + A list of unmanaged 64 bit assembly names to include, delimited with line breaks. + + + + + The order of preloaded assemblies, delimited with line breaks. + + + + + + This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file. + + + + + Controls if .pdbs for reference assemblies are also embedded. + + + + + Controls if runtime assemblies are also embedded. + + + + + Controls whether the runtime assemblies are embedded with their full path or only with their assembly name. + + + + + Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option. + + + + + As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off. + + + + + Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code. + + + + + Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior. + + + + + A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with | + + + + + A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |. + + + + + A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with | + + + + + A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with |. + + + + + A list of unmanaged 32 bit assembly names to include, delimited with |. + + + + + A list of unmanaged 64 bit assembly names to include, delimited with |. + + + + + The order of preloaded assemblies, delimited with |. + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/Hi3Helper.TaskScheduler/Hi3Helper.TaskScheduler.csproj b/Hi3Helper.TaskScheduler/Hi3Helper.TaskScheduler.csproj new file mode 100644 index 000000000..365f8fce4 --- /dev/null +++ b/Hi3Helper.TaskScheduler/Hi3Helper.TaskScheduler.csproj @@ -0,0 +1,34 @@ + + + + Exe + netframework462 + disable + x64 + en + portable + Task Scheduler Shell + Task Scheduler Shell + Collapse Launcher's Task Scheduler Shell + Collapse Launcher's Task Scheduler Shell + Collapse Launcher Team + $(Company). neon-nyan, Cry0, bagusnl, shatyuka, gablm. + Copyright 2022-2024 $(Company) + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/Hi3Helper.TaskScheduler/Program.cs b/Hi3Helper.TaskScheduler/Program.cs new file mode 100644 index 000000000..8fc3fc71c --- /dev/null +++ b/Hi3Helper.TaskScheduler/Program.cs @@ -0,0 +1,340 @@ +using Microsoft.Win32.TaskScheduler; +using NuGet.Versioning; +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using Velopack; +using Velopack.Locators; +using Velopack.NuGet; +using Velopack.Windows; +using TaskSched = Microsoft.Win32.TaskScheduler.Task; + +namespace Hi3Helper.TaskScheduler +{ + public static class ReturnExtension + { + public static int ReturnValAsConsole(this int returnVal) + { + Console.WriteLine($"RETURNVAL_{returnVal}"); + return returnVal; + } + } + + public class Program + { + static int PrintUsage() + { + string executableName = Path.GetFileNameWithoutExtension(Process.GetCurrentProcess().MainModule.FileName); + Console.WriteLine($"Usage:\r\n{executableName} [IsEnabled] \"Scheduler name\" \"Executable path\""); + Console.WriteLine($"{executableName} [Enable] \"Scheduler name\" \"Executable path\""); + Console.WriteLine($"{executableName} [EnableToTray] \"Scheduler name\" \"Executable path\""); + Console.WriteLine($"{executableName} [Disable] \"Scheduler name\" \"Executable path\""); + Console.WriteLine($"{executableName} [DisableToTray] \"Scheduler name\" \"Executable path\""); + Console.WriteLine($"{executableName} [RecreateIcons] \"Executable path\""); + return int.MaxValue; + } + + static int Main(string[] args) + { + try + { + if (args.Length == 2 && args[0].ToLower() == "recreateicons") + return RecreateIcons(args[1]); + + if (args.Length < 3) + return PrintUsage().ReturnValAsConsole(); + + string action = args[0].ToLower(); + string schedName = args[1]; + string execPath = args[2]; + + switch (action) + { + case "isenabled": + return IsEnabled(schedName, execPath).ReturnValAsConsole(); + case "enable": + ToggleTask(true, false, schedName, execPath); + break; + case "enabletotray": + ToggleTask(true, true, schedName, execPath); + break; + case "disable": + ToggleTask(false, false, schedName, execPath); + break; + case "disabletotray": + ToggleTask(false, true, schedName, execPath); + break; + default: + return PrintUsage().ReturnValAsConsole(); + } + + WriteConsole($"Operation: {action} \"{schedName}\" \"{execPath}\" has been executed!"); + } + catch (Exception ex) + { + WriteConsole($"An unexpected error has occurred!\r\n{ex}"); + return int.MinValue.ReturnValAsConsole(); + } + + return 0.ReturnValAsConsole(); + } + + static int RecreateIcons(string executablePath) + { +#pragma warning disable CS0618 // Type or member is obsolete + new Shortcuts(null, new CollapseVelopackLocator(executablePath)).CreateShortcut(executablePath, ShortcutLocation.Desktop | ShortcutLocation.StartMenuRoot, false, null); +#pragma warning restore CS0618 // Type or member is obsolete + return 0; + } + + static TaskSched Create(TaskService taskService, string schedName, string execPath) + { + using (TaskDefinition taskDefinition = TaskService.Instance.NewTask()) + { + taskDefinition.RegistrationInfo.Author = "CollapseLauncher"; + taskDefinition.RegistrationInfo.Description = "Run Collapse Launcher automatically when computer starts"; + taskDefinition.Principal.LogonType = TaskLogonType.InteractiveToken; + taskDefinition.Principal.RunLevel = TaskRunLevel.Highest; + taskDefinition.Settings.Enabled = false; + taskDefinition.Triggers.Add(new LogonTrigger()); + taskDefinition.Actions.Add(new ExecAction(execPath)); + + TaskSched task = taskService.RootFolder.RegisterTaskDefinition(schedName, taskDefinition); + WriteConsole($"New task schedule has been created!"); + return task; + } + } + + static void TryDelete(TaskService taskService, string schedName) + { + // Try get the tasks + TaskSched[] tasks = taskService.FindAllTasks(new System.Text.RegularExpressions.Regex(schedName), false); + + // If null, then ignore + if (tasks == null || tasks.Length == 0) + { + WriteConsole($"None of the existing task: {schedName} exist but trying to delete, ignoring!"); + return; + } + + // Remove possible tasks + foreach (TaskSched task in tasks) + { + using (task) + { + WriteConsole($"Deleting redundant task: {task.Name}"); + taskService.RootFolder.DeleteTask(task.Name); + } + } + } + + static TaskSched GetExistingTask(TaskService taskService, string schedName, string execPath) + { + // Try get the tasks + TaskSched[] tasks = taskService.FindAllTasks(new System.Text.RegularExpressions.Regex(schedName), false); + + // Try get the first task + TaskSched task = tasks? + .FirstOrDefault(x => + x.Name.Equals(schedName, StringComparison.OrdinalIgnoreCase)); + + // Return null as empty + if (task == null) + { + return null; + } + + // Get actionPath + string actionPath = task.Definition.Actions?.FirstOrDefault()?.ToString(); + + // If actionPath is null, then return null as empty + if (string.IsNullOrEmpty(actionPath)) + { + return null; + } + + // if actionPath isn't matched, then replace with current executable path + if (!actionPath.StartsWith(execPath, StringComparison.OrdinalIgnoreCase)) + { + // Check if the last action path runs on tray + bool isLastHasTray = actionPath.EndsWith("tray", StringComparison.OrdinalIgnoreCase); + + // Register changes + task.Definition.Actions.Clear(); + task.Definition.Actions.Add(new ExecAction(execPath, isLastHasTray ? "tray" : null)); + task.RegisterChanges(); + } + + // If the task matches, then return the task + return task; + } + + static int IsEnabled(string schedName, string execPath) + { + using (TaskService taskService = new TaskService()) + { + // Get the task + TaskSched task = GetExistingTask(taskService, schedName, execPath); + + // If the task is not null, then do further check + if (task != null) + { + // Check if it's enabled with tray + bool isOnTray = task.Definition?.Actions?.FirstOrDefault()?.ToString()?.EndsWith("tray", StringComparison.OrdinalIgnoreCase) ?? false; + + // If the task definition is enabled, then return 1 (true) or 2 (true with tray) + if (task.Definition.Settings.Enabled) + return isOnTray ? 2 : 1; + + // Otherwise, if the task exist but not enabled, then return 0 (false) or -1 (false with tray) + return isOnTray ? -1 : 0; + } + } + + // Otherwise, return 0 + return 0; + } + + static void ToggleTask(bool isEnabled, bool isStartupToTray, string schedName, string execPath) + { + using (TaskService taskService = new TaskService()) + { + // Try get existing task + TaskSched task = GetExistingTask(taskService, schedName, execPath); + + try + { + // If the task is null due to its' non existence or + // there are some unmatched tasks, then try recreate the task + // by try deleting and create a new one. + if (task == null) + { + TryDelete(taskService, schedName); + task = Create(taskService, schedName, execPath); + } + + // Try clear the existing actions and set the new one + task.Definition.Actions.Clear(); + task.Definition.Actions.Add(new ExecAction(execPath, isStartupToTray ? "tray" : null)); + task.Definition.Settings.Enabled = isEnabled; + } + finally + { + // Register the changes if it's not null. + if (task != null) + { + task.RegisterChanges(); + task.Dispose(); + WriteConsole($"ToggledStatus: isEnabled -> {isEnabled} & isStartupToTray -> {isStartupToTray}"); + } + } + } + } + + static void WriteConsole(string message) => + Console.WriteLine(message); + } + + internal class CollapseVelopackLocator : VelopackLocator + { + const string SpecVersionFileName = "sq.version"; + + /// + public override string AppId { get; } + + /// + public override string RootAppDir { get; } + + /// + public override string UpdateExePath { get; } + + /// + public override string AppContentDir { get; } + + /// + public override SemanticVersion CurrentlyInstalledVersion { get; } + + /// + public override string PackagesDir => CreateSubDirIfDoesNotExist(RootAppDir, "packages"); + + /// + public override bool IsPortable => + RootAppDir != null ? File.Exists(Path.Combine(RootAppDir, ".portable")) : false; + + /// + public override string Channel { get; } + + /// + /// Internal use only. Auto detect app details from the specified EXE path. + /// + internal CollapseVelopackLocator(string ourExePath) + : base(null) + { + if (!VelopackRuntimeInfo.IsWindows) + throw new NotSupportedException("Cannot instantiate WindowsLocator on a non-Windows system."); + + // We try various approaches here. Firstly, if Update.exe is in the parent directory, + // we use that. If it's not present, we search for a parent "current" or "app-{ver}" directory, + // which could designate that this executable is running in a nested sub-directory. + // There is some legacy code here, because it's possible that we're running in an "app-{ver}" + // directory which is NOT containing a sq.version, in which case we need to infer a lot of info. + + ourExePath = Path.GetFullPath(ourExePath); + string myDirPath = Path.GetDirectoryName(ourExePath); + var myDirName = Path.GetFileName(myDirPath); + var possibleUpdateExe = Path.GetFullPath(Path.Combine(myDirPath, "..", "Update.exe")); + var ixCurrent = ourExePath.LastIndexOf("/current/", StringComparison.InvariantCultureIgnoreCase); + + Console.WriteLine($"Initializing {nameof(CollapseVelopackLocator)}"); + + if (File.Exists(possibleUpdateExe)) + { + Console.WriteLine("Update.exe found in parent directory"); + // we're running in a directory with an Update.exe in the parent directory + var manifestFile = Path.Combine(myDirPath, SpecVersionFileName); + if (PackageManifest.TryParseFromFile(manifestFile, out var manifest)) + { + // ideal, the info we need is in a manifest file. + Console.WriteLine("Located valid manifest file at: " + manifestFile); + AppId = manifest.Id; + CurrentlyInstalledVersion = manifest.Version; + RootAppDir = Path.GetDirectoryName(possibleUpdateExe); + UpdateExePath = possibleUpdateExe; + AppContentDir = myDirPath; + Channel = manifest.Channel; + } + else if (myDirName.StartsWith("app-", StringComparison.OrdinalIgnoreCase) && NuGetVersion.TryParse(myDirName.Substring(4), out var version)) + { + // this is a legacy case, where we're running in an 'root/app-*/' directory, and there is no manifest. + Console.WriteLine("Legacy app-* directory detected, sq.version not found. Using directory name for AppId and Version."); + AppId = Path.GetFileName(Path.GetDirectoryName(possibleUpdateExe)); + CurrentlyInstalledVersion = version; + RootAppDir = Path.GetDirectoryName(possibleUpdateExe); + UpdateExePath = possibleUpdateExe; + AppContentDir = myDirPath; + } + } + else if (ixCurrent > 0) + { + // this is an attempt to handle the case where we are running in a nested current directory. + var rootDir = ourExePath.Substring(0, ixCurrent); + var currentDir = Path.Combine(rootDir, "current"); + var manifestFile = Path.Combine(currentDir, SpecVersionFileName); + possibleUpdateExe = Path.GetFullPath(Path.Combine(rootDir, "Update.exe")); + // we only support parsing a manifest when we're in a nested current directory. no legacy fallback. + if (File.Exists(possibleUpdateExe) && PackageManifest.TryParseFromFile(manifestFile, out var manifest)) + { + Console.WriteLine("Running in deeply nested directory. This is not an advised use-case."); + Console.WriteLine("Located valid manifest file at: " + manifestFile); + RootAppDir = Path.GetDirectoryName(possibleUpdateExe); + UpdateExePath = possibleUpdateExe; + AppId = manifest.Id; + CurrentlyInstalledVersion = manifest.Version; + AppContentDir = currentDir; + Channel = manifest.Channel; + } + } + } + } +} diff --git a/Hi3Helper.TaskScheduler/Properties/PublishProfiles/AsRelease.pubxml b/Hi3Helper.TaskScheduler/Properties/PublishProfiles/AsRelease.pubxml new file mode 100644 index 000000000..7d3019a67 --- /dev/null +++ b/Hi3Helper.TaskScheduler/Properties/PublishProfiles/AsRelease.pubxml @@ -0,0 +1,15 @@ + + + + + Release + x64 + bin\publish + FileSystem + <_TargetId>Folder + netframework462 + win-x64 + + \ No newline at end of file diff --git a/Hi3Helper.TaskScheduler/Properties/launchSettings.json b/Hi3Helper.TaskScheduler/Properties/launchSettings.json new file mode 100644 index 000000000..633b3ccd4 --- /dev/null +++ b/Hi3Helper.TaskScheduler/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Hi3Helper.TaskScheduler": { + "commandName": "Project", + "commandLineArgs": "IsEnabled \"CollapseLauncherStartupTask\" \"E:\\myGit\\Collapse\\CollapseLauncher\\bin\\x64\\Debug\\net9.0-windows10.0.22621.0\\win-x64\\CollapseLauncher.exe\"" + } + } +} \ No newline at end of file diff --git a/Hi3Helper.TaskScheduler/packages.lock.json b/Hi3Helper.TaskScheduler/packages.lock.json new file mode 100644 index 000000000..37ea088e2 --- /dev/null +++ b/Hi3Helper.TaskScheduler/packages.lock.json @@ -0,0 +1,401 @@ +{ + "version": 1, + "dependencies": { + ".NETFramework,Version=v4.6.2": { + "Costura.Fody": { + "type": "Direct", + "requested": "[5.8.0-alpha0098, )", + "resolved": "5.8.0-alpha0098", + "contentHash": "/waTTBqcZioV+2Fn2fWBO/EfyGhYN4xpBB828hfzdjkq8WJE3iiwm30cFAKJWYrwhCSD3piHasDGfBMGg6Gvqw==", + "dependencies": { + "Fody": "6.6.0", + "NETStandard.Library": "1.6.1" + } + }, + "Fody": { + "type": "Direct", + "requested": "[6.8.2, )", + "resolved": "6.8.2", + "contentHash": "sjGHrtGS1+kcrv99WXCvujOFBTQp4zCH3ZC9wo2LAtVaJkuLpHghQx3y4k1Q8ZKuDAbEw+HE6ZjPUJQK3ejepQ==" + }, + "Microsoft.NETFramework.ReferenceAssemblies": { + "type": "Direct", + "requested": "[1.0.3, )", + "resolved": "1.0.3", + "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", + "dependencies": { + "Microsoft.NETFramework.ReferenceAssemblies.net462": "1.0.3" + } + }, + "System.Net.Http": { + "type": "Direct", + "requested": "[4.3.4, )", + "resolved": "4.3.4", + "contentHash": "aOa2d51SEbmM+H+Csw7yJOuNZoHkrP2XnAurye5HWYgGVVU54YZDvsLUYRv6h18X3sPnjNCANmN7ZhIPiqMcjA==", + "dependencies": { + "System.Security.Cryptography.X509Certificates": "4.3.0" + } + }, + "System.Text.RegularExpressions": { + "type": "Direct", + "requested": "[4.3.1, )", + "resolved": "4.3.1", + "contentHash": "N0kNRrWe4+nXOWlpLT4LAY5brb8caNFlUuIRpraCVMDLYutKkol1aV079rQjLuSxKMJT2SpBQsYX9xbcTMmzwg==" + }, + "TaskScheduler": { + "type": "Direct", + "requested": "[2.11.0, )", + "resolved": "2.11.0", + "contentHash": "p9wH58XSNIyUtO7PIFAEldaKUzpYmlj+YWAfnUqBKnGxIZRY51I9BrsBGJijUVwlxrgmLLPUigRIv2ZTD4uPJA==" + }, + "Velopack": { + "type": "Direct", + "requested": "[0.0.626, )", + "resolved": "0.0.626", + "contentHash": "el6qqNyJM3GA11j6SFZgcQJ/AFg7ocQQSu+Tk/+OIzejZlROGLK+RDWPb4tcp9E9ug9aT4HV0m35zISMSC++/Q==", + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "2.2.0", + "Newtonsoft.Json": "13.0.1", + "NuGet.Versioning": "6.10.1" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "B2WqEox8o+4KUOpL7rZPyh6qYjik8tHi2tN8Z9jZkHzED8ElYgZa/h6K+xliB435SqUcWT290Fr2aa8BtZjn8A==" + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" + }, + "Microsoft.NETFramework.ReferenceAssemblies.net462": { + "type": "Transitive", + "resolved": "1.0.3", + "contentHash": "IzAV30z22ESCeQfxP29oVf4qEo8fBGXLXSU6oacv/9Iqe6PzgHDKCaWfwMBak7bSJQM0F5boXWoZS+kChztRIQ==" + }, + "Microsoft.Win32.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==" + }, + "NETStandard.Library": { + "type": "Transitive", + "resolved": "1.6.1", + "contentHash": "WcSp3+vP+yHNgS8EV5J7pZ9IRpeDuARBPN28by8zqff1wJQXm26PVU8L3/fYLBJVU7BtDyqNVWq2KlCVvSSR4A==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.Win32.Primitives": "4.3.0", + "System.AppContext": "4.3.0", + "System.Collections": "4.3.0", + "System.Collections.Concurrent": "4.3.0", + "System.Console": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Diagnostics.Tools": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Globalization": "4.3.0", + "System.Globalization.Calendars": "4.3.0", + "System.IO": "4.3.0", + "System.IO.Compression": "4.3.0", + "System.IO.Compression.ZipFile": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Linq": "4.3.0", + "System.Linq.Expressions": "4.3.0", + "System.Net.Http": "4.3.0", + "System.Net.Primitives": "4.3.0", + "System.Net.Sockets": "4.3.0", + "System.ObjectModel": "4.3.0", + "System.Reflection": "4.3.0", + "System.Reflection.Extensions": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Security.Cryptography.X509Certificates": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Text.Encoding.Extensions": "4.3.0", + "System.Text.RegularExpressions": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0", + "System.Threading.Timer": "4.3.0", + "System.Xml.ReaderWriter": "4.3.0", + "System.Xml.XDocument": "4.3.0" + } + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.1", + "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" + }, + "NuGet.Versioning": { + "type": "Transitive", + "resolved": "6.10.1", + "contentHash": "tovHZ3OlMVmsTdhv2z5nwnnhoA1ryhfJMyVQ9/+iv6d3h78fp230XaGy3K/iVcLwB50DdfNfIsitW97KSOWDFg==" + }, + "System.AppContext": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "fKC+rmaLfeIzUhagxY17Q9siv/sPrjjKcfNg1Ic8IlQkZLipo8ljcaZQu4VtI4Jqbzjc2VTjzGLF6WmsRXAEgA==" + }, + "System.Collections": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==" + }, + "System.Collections.Concurrent": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==" + }, + "System.Console": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "DHDrIxiqk1h03m6khKWV2X8p/uvN79rgSqpilL6uzpmSfxfU5ng8VcPtW4qsDsQDHiTv6IPV9TmD5M/vElPNLg==" + }, + "System.Diagnostics.Debug": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==" + }, + "System.Diagnostics.Tools": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "UUvkJfSYJMM6x527dJg2VyWPSRqIVB0Z7dbjHst1zmwTXz5CcXSYJFWRpuigfbO1Lf7yfZiIaEUesfnl/g5EyA==" + }, + "System.Diagnostics.Tracing": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==" + }, + "System.Globalization": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==" + }, + "System.Globalization.Calendars": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==" + }, + "System.IO": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==" + }, + "System.IO.Compression": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg==" + }, + "System.IO.Compression.ZipFile": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "G4HwjEsgIwy3JFBduZ9quBkAu+eUwjIdJleuNSgmUojbH6O3mlvEIme+GHx/cLlTAPcrnnL7GqvB9pTlWRfhOg==" + }, + "System.IO.FileSystem": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", + "dependencies": { + "System.IO.FileSystem.Primitives": "4.3.0" + } + }, + "System.IO.FileSystem.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==" + }, + "System.Linq": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==" + }, + "System.Linq.Expressions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==" + }, + "System.Net.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==" + }, + "System.Net.Sockets": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "m6icV6TqQOAdgt5N/9I5KNpjom/5NFtkmGseEH+AK/hny8XrytLH3+b5M8zL/Ycg3fhIocFpUMyl/wpFnVRvdw==" + }, + "System.ObjectModel": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==" + }, + "System.Reflection": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==" + }, + "System.Reflection.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==" + }, + "System.Reflection.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==" + }, + "System.Resources.ResourceManager": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==" + }, + "System.Runtime": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==" + }, + "System.Runtime.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==" + }, + "System.Runtime.Handles": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==" + }, + "System.Runtime.InteropServices": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", + "dependencies": { + "System.Runtime": "4.3.0" + } + }, + "System.Runtime.InteropServices.RuntimeInformation": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==" + }, + "System.Runtime.Numerics": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==" + }, + "System.Security.Cryptography.Algorithms": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", + "dependencies": { + "System.Security.Cryptography.Primitives": "4.3.0" + } + }, + "System.Security.Cryptography.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==" + }, + "System.Security.Cryptography.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==" + }, + "System.Security.Cryptography.X509Certificates": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", + "dependencies": { + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0" + } + }, + "System.Text.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==" + }, + "System.Text.Encoding.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==" + }, + "System.Threading": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==" + }, + "System.Threading.Tasks": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==" + }, + "System.Threading.Timer": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "Z6YfyYTCg7lOZjJzBjONJTFKGN9/NIYKSxhU5GRd+DTwHSZyvWp1xuI5aR+dLg+ayyC5Xv57KiY4oJ0tMO89fQ==" + }, + "System.Xml.ReaderWriter": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==" + }, + "System.Xml.XDocument": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5zJ0XDxAIg8iy+t4aMnQAu0MqVbqyvfoUVl1yDV61xdo3Vth45oA2FoY4pPkxYAH5f8ixpmTqXeEIya95x0aCQ==" + } + }, + ".NETFramework,Version=v4.6.2/win-x64": { + "System.Net.Http": { + "type": "Direct", + "requested": "[4.3.4, )", + "resolved": "4.3.4", + "contentHash": "aOa2d51SEbmM+H+Csw7yJOuNZoHkrP2XnAurye5HWYgGVVU54YZDvsLUYRv6h18X3sPnjNCANmN7ZhIPiqMcjA==", + "dependencies": { + "System.Security.Cryptography.X509Certificates": "4.3.0" + } + }, + "System.IO.Compression": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg==" + }, + "System.Runtime.InteropServices.RuntimeInformation": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==" + }, + "System.Security.Cryptography.Algorithms": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", + "dependencies": { + "System.Security.Cryptography.Primitives": "4.3.0" + } + }, + "System.Security.Cryptography.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==" + }, + "System.Security.Cryptography.X509Certificates": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", + "dependencies": { + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0" + } + } + } + } +} \ No newline at end of file diff --git a/ImageEx b/ImageEx index 84a9ecb92..9840e922b 160000 --- a/ImageEx +++ b/ImageEx @@ -1 +1 @@ -Subproject commit 84a9ecb92bc11ed08f14a4906644cd1b55fb5b72 +Subproject commit 9840e922bde6e7329b1ff541c6e33a0b3a01d7a4 diff --git a/InnoSetupHelper/InnoSetupHelper.csproj b/InnoSetupHelper/InnoSetupHelper.csproj index a71a6a376..64e2258da 100644 --- a/InnoSetupHelper/InnoSetupHelper.csproj +++ b/InnoSetupHelper/InnoSetupHelper.csproj @@ -1,7 +1,7 @@  - net8.0 + net9.0 disable enable true @@ -15,14 +15,11 @@ Collapse Launcher Team $(Company). neon-nyan, Cry0, bagusnl, shatyuka, gablm. Copyright 2022-2024 $(Company) + true - - - - - + diff --git a/InnoSetupHelper/InnoSetupLogUpdate.cs b/InnoSetupHelper/InnoSetupLogUpdate.cs index b42409722..bd6ddd9ff 100644 --- a/InnoSetupHelper/InnoSetupLogUpdate.cs +++ b/InnoSetupHelper/InnoSetupLogUpdate.cs @@ -1,17 +1,29 @@ -using Hi3Helper; -using Hi3Helper.EncTool.Parser.InnoUninstallerLog; +using Hi3Helper.EncTool.Parser.InnoUninstallerLog; using LibISULR; using LibISULR.Flags; using LibISULR.Records; using System; using System.IO; using System.Linq; -using static Hi3Helper.Logger; namespace InnoSetupHelper { + public enum InnoSetupLogType + { + Default, Warning, Error + } + + public struct InnoSetupLogStruct + { + public InnoSetupLogType LogType; + public bool IsWriteToLog; + public string Message; + } + public class InnoSetupLogUpdate { + public static event EventHandler? LoggerEvent; + private static readonly string[] excludeDeleteFile = new string[] { // Generic Files @@ -38,10 +50,10 @@ public class InnoSetupLogUpdate public static void UpdateInnoSetupLog(string path) { - string directoryPath = Path.GetDirectoryName(path)!; - string searchValue = GetPathWithoutDriveLetter(directoryPath); + string directoryPath = Path.GetDirectoryName(path)!; + string searchValue = GetPathWithoutDriveLetter(directoryPath); - LogWriteLine($"[InnoSetupLogUpdate::UpdateInnoSetupLog()] Updating Inno Setup file located at: {path}", LogType.Default, true); + LogWriteLine($"[InnoSetupLogUpdate::UpdateInnoSetupLog()] Updating Inno Setup file located at: {path}", InnoSetupLogType.Default, true); try { using InnoUninstallLog innoLog = InnoUninstallLog.Load(path, true); @@ -56,11 +68,11 @@ public static void UpdateInnoSetupLog(string path) // Save the Inno Setup log innoLog.Save(path); - LogWriteLine($"[InnoSetupLogUpdate::UpdateInnoSetupLog()] Inno Setup file: {path} has been successfully updated!", LogType.Default, true); + LogWriteLine($"[InnoSetupLogUpdate::UpdateInnoSetupLog()] Inno Setup file: {path} has been successfully updated!", InnoSetupLogType.Default, true); } catch (Exception ex) { - LogWriteLine($"[InnoSetupLogUpdate::UpdateInnoSetupLog()] Inno Setup file: {path} was failed due to an error: {ex}", LogType.Warning, true); + LogWriteLine($"[InnoSetupLogUpdate::UpdateInnoSetupLog()] Inno Setup file: {path} was failed due to an error: {ex}", InnoSetupLogType.Warning, true); } } @@ -75,7 +87,7 @@ private static void RegisterDirOrFilesRecord(InnoUninstallLog innoLog, string pa DirectoryInfo currentDirectory = new DirectoryInfo(pathToRegister); if (innoLog.Records == null) { - LogWriteLine("[InnoSetupLogUpdate::RegisterDirOrFilesRecord()] Records is uninitialized!", LogType.Error, true); + LogWriteLine("[InnoSetupLogUpdate::RegisterDirOrFilesRecord()] Records is uninitialized!", InnoSetupLogType.Error, true); } else { @@ -84,14 +96,14 @@ private static void RegisterDirOrFilesRecord(InnoUninstallLog innoLog, string pa if (excludeDeleteFile.Any(x => x.IndexOf(fileInfo.FullName, StringComparison.OrdinalIgnoreCase) > -1)) continue; fileInfo.IsReadOnly = false; LogWriteLine($"[InnoSetupLogUpdate::RegisterDirOrFilesRecord()] " + - $"Registering Inno Setup record: (DeleteFileRecord){fileInfo.FullName}", LogType.Default, true); + $"Registering Inno Setup record: (DeleteFileRecord){fileInfo.FullName}", InnoSetupLogType.Default, true); innoLog.Records.Add(DeleteFileRecord.Create(fileInfo.FullName)); } LogWriteLine($"[InnoSetupLogUpdate::RegisterDirOrFilesRecord()] " + - $"Registering Inno Setup record: (DeleteDirOrFilesRecord){pathToRegister}", LogType.Default, true); + $"Registering Inno Setup record: (DeleteDirOrFilesRecord){pathToRegister}", InnoSetupLogType.Default, true); innoLog.Records.Add(DeleteDirOrFilesRecord.Create(pathToRegister)); } - + foreach (DirectoryInfo subdirectories in currentDirectory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)) { RegisterDirOrFilesRecord(innoLog, subdirectories.FullName); @@ -106,7 +118,7 @@ private static void CleanUpInnoDirOrFilesRecord(InnoUninstallLog innoLog, string if (innoLog.Records == null) { LogWriteLine("[InnoSetupLogUpdate::RegisterDirOrFilesRecord()] Records is uninitialized!", - LogType.Error, true); + InnoSetupLogType.Error, true); return; } BaseRecord baseRecord = innoLog.Records[index]; @@ -116,12 +128,12 @@ private static void CleanUpInnoDirOrFilesRecord(InnoUninstallLog innoLog, string case RecordType.DeleteDirOrFiles: isRecordValid = IsInnoRecordContainsPath(baseRecord, searchValue) && IsDeleteDirOrFilesFlagsValid((DeleteDirOrFilesRecord)baseRecord); - LogWriteLine($"[InnoSetupLogUpdate::CleanUpInnoDirOrFilesRecord()] Removing outdated Inno Setup record: (DeleteDirOrFilesRecord){((DeleteDirOrFilesRecord)baseRecord).Paths[0]}", LogType.Default, true); + LogWriteLine($"[InnoSetupLogUpdate::CleanUpInnoDirOrFilesRecord()] Removing outdated Inno Setup record: (DeleteDirOrFilesRecord){((DeleteDirOrFilesRecord)baseRecord).Paths[0]}", InnoSetupLogType.Default, true); break; case RecordType.DeleteFile: isRecordValid = IsInnoRecordContainsPath(baseRecord, searchValue) && IsDeleteFileFlagsValid((DeleteFileRecord)baseRecord); - LogWriteLine($"[InnoSetupLogUpdate::CleanUpInnoDirOrFilesRecord()] Removing outdated Inno Setup record: (DeleteFileRecord){((DeleteFileRecord)baseRecord).Paths[0]}", LogType.Default, true); + LogWriteLine($"[InnoSetupLogUpdate::CleanUpInnoDirOrFilesRecord()] Removing outdated Inno Setup record: (DeleteFileRecord){((DeleteFileRecord)baseRecord).Paths[0]}", InnoSetupLogType.Default, true); break; } if (isRecordValid) @@ -139,5 +151,8 @@ private static bool IsInnoRecordContainsPath(BaseRecord record, string? where TFlags : Enum => ((BasePathListRecord)record) .Paths[0]! .IndexOf(searchValue!, StringComparison.OrdinalIgnoreCase) > -1; + + private static void LogWriteLine(string message, InnoSetupLogType logType, bool isWriteLog) + => LoggerEvent?.Invoke(null, new InnoSetupLogStruct { IsWriteToLog = isWriteLog, LogType = logType, Message = message }); } } diff --git a/InnoSetupHelper/packages.lock.json b/InnoSetupHelper/packages.lock.json index 8b552740e..8c8a49a68 100644 --- a/InnoSetupHelper/packages.lock.json +++ b/InnoSetupHelper/packages.lock.json @@ -1,35 +1,18 @@ { "version": 1, "dependencies": { - "net8.0": { - "System.IO.Hashing": { + "net9.0": { + "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "ne1843evDugl0md7Fjzy6QjJrzsjh46ZKbhf8GwBXb5f/gw97J4bxMs0NQKifDuThh/f0bZ0e62NPl1jzTuRqA==" - }, - "Google.Protobuf": { - "type": "Transitive", - "resolved": "3.28.1", - "contentHash": "i4EN7Z+OUdoRBNiVMIG6CfMh6UowXiUx+BKgE+GHLbAX5ArSmpUTFUDwgRNwNfYdosl6GXuBlDiHCcXSHw43+A==" - }, - "hi3helper.core": { - "type": "Project", - "dependencies": { - "Hi3Helper.EncTool": "[1.0.0, )", - "Hi3Helper.Http": "[2.0.0, )" - } + "requested": "[9.0.0-rc.1.24431.7, )", + "resolved": "9.0.0-rc.1.24431.7", + "contentHash": "B8jHIIszeUdDY+O4G1/xPOuhrriwsv9Z8/u3XSOAqc8Z0mQCeLBKg2oLGElDZeRF8wWXDGklAKY3f0IHopyXkQ==" }, - "hi3helper.enctool": { - "type": "Project", - "dependencies": { - "Google.Protobuf": "[3.28.1, )", - "Hi3Helper.Http": "[2.0.0, )", - "System.IO.Hashing": "[8.0.0, )" - } - }, - "hi3helper.http": { - "type": "Project" + "System.IO.Hashing": { + "type": "Direct", + "requested": "[9.0.0-rc.1.24431.7, )", + "resolved": "9.0.0-rc.1.24431.7", + "contentHash": "giMiDNlne8WcLG7rsEfrETaukDUTBPn8a97QRo9LYR71O0Rb4SJ6TbfGdQ9oiBYldVPSAbSQANb5ThLZOo408Q==" } } } diff --git a/SevenZipExtractor b/SevenZipExtractor index e649b7466..9c299172a 160000 --- a/SevenZipExtractor +++ b/SevenZipExtractor @@ -1 +1 @@ -Subproject commit e649b7466464a7ba9d5b478ea945575c75f7190f +Subproject commit 9c299172ae7e68c516f6de6ae3b3f40e6ff5ef8b diff --git a/XamlStyler.bat b/XamlStyler.bat index 509aff430..273851b66 100644 --- a/XamlStyler.bat +++ b/XamlStyler.bat @@ -11,4 +11,4 @@ if %ERRORLEVEL% neq 0 ( echo This tool will apply XamlStyler configured in settings.xamlstyler file for all Xamls in ./CollapseLauncher/XAMLs/ directory. pause -xstyler --config ./settings.xamlstyler --directory ./CollapseLauncher/XAMLs/ --recursive +xstyler --config ./settings.xamlstyler --directory ./CollapseLauncher/ --recursive diff --git a/qodana.yaml b/qodana.yaml index 20f536c8a..5cd0e0c45 100644 --- a/qodana.yaml +++ b/qodana.yaml @@ -16,6 +16,8 @@ exclude: - name: Xaml.ConstructorWarning - name: Xaml.InvalidResourceType - name: Xaml.StaticResourceNotResolved + - name: RedundantExtendsListEntry + - name: PartialTypeWithSinglePart include: - name: CheckDependencyLicenses licenseRules: